3.2.52: 新增「最近评论」挂件(替代 Comments Widget Plus 插件)#22
主题自带一个好看的最近评论挂件,用户不必再装插件。 新挂件 widget_recent_comments(id_base = yang_recent_comments): - 头像 40px 圆形 + object-fit:cover,带 max-width/max-height 双保险 - 摘要按字符截断(mb_substr),先剥标签再压空白,不走 wp_html_excerpt(按字节切半个汉字) - 作者 · 相对时间 · 文章标题三段各自成 span,不拼 i18n 句式 - 三层字号:摘要 15px / 作者 14px / 文章 13px(文章字号继承自 meta) - 条间虚线,最后一条去掉 - 后台九项设置:标题、条数、头像、头像尺寸、摘要、摘要字数、排除 pingback、评论类型、文章类型 实现要点: - CSS 不依赖 .k-main .sidebar 祖先,只依到 .w-comments 自身 (3.2.51 的红线:带祖先的选择器在后台「外观→小工具」预览里一条不生效) - .w-comments 自己不写 padding,交给 widget 基类,避免与其它挂件错位 - 只查 status=approve 且 post_status=publish,草稿文章评论不露出 同时修一个前后台不一致的 bug: - form() 的 post_type 原来走 pick(),pick() 把空串当"没填"回落 'post' ⇒ 老实例(存空串)在后台下拉框显示「文章」,前台却按「全部类型」渲染。 改为 widget() 与 form() 都直接读原值,两处都识别空串/any 为「全部类型」。 验证:行为 112/0 · 行为反证 25/25 + CSS 10/10 · 几何 50/0 · 线上真实页面 30/0 · 静态反证 23/0 · 发布脚本 22 组反证全真红。
This commit is contained in:
@@ -1913,6 +1913,164 @@ button:focus {
|
|||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════════════════════════════════════════════════════
|
||||||
|
* 「最近评论」挂件(.w-comments)(3.2.52)
|
||||||
|
*
|
||||||
|
* ── 为什么自己做,不用插件 ────────────────────────────────────────────
|
||||||
|
* 原来装的是 Comments Widget Plus。它的三个问题都是实测出来的:
|
||||||
|
*
|
||||||
|
* ① 用 `_x('%1$s %2$son%3$s %4$s')` 拼句子,中文渲染成
|
||||||
|
* 「标题**上的**作者」(zh_CN.po 里 "on" 被译成"上的"),
|
||||||
|
* 而插件根本没带 zh_CN 语言包(languages/ 只有一个 .pot),
|
||||||
|
* 所以这句永远是英文语序套中文词。
|
||||||
|
* ② 文章标题 / 作者 / 摘要**三层同为 16px**,只能靠颜色区分;
|
||||||
|
* 而标题最长,首条实测 337×96(标题链接 332×50,折 2 行)。
|
||||||
|
* ③ 实例里 `avatar: false`,纯文字流没有任何视觉抓手。
|
||||||
|
*
|
||||||
|
* ── 这组规则的一条硬约束(3.2.51 的教训)────────────────────────────
|
||||||
|
* 🔴 选择器**只依赖 `.w-comments` 自身**,不写 `.k-main .sidebar .w-comments`。
|
||||||
|
* 3.2.51 的「个人简介撑爆」就是三段祖先选择器造成的:挂件在
|
||||||
|
* 后台「外观→小工具」预览里没有 `.k-main .sidebar` 那层祖先,
|
||||||
|
* 一组规则全部失效,头像按原图 2136px 铺开。
|
||||||
|
* **前台看起来正常,恰恰是把问题藏死的原因。**
|
||||||
|
* 🔴 所以这里**故意不写任何内边距规则**:白底与 15px/30px 内边距由
|
||||||
|
* 外层 `.widget` 提供(前台 `.k-main .sidebar .widget`,
|
||||||
|
* 后台由 WP 自己的挂件容器提供)。挂件本身不该管这些 ——
|
||||||
|
* 像 `.w-about` 那样去「对齐」内边距,就又把规则绑回祖先了。
|
||||||
|
*
|
||||||
|
* ── 信息层级(这是"漂亮"的全部含义)────────────────────────────────
|
||||||
|
* 摘要 15px #5a5a5a ← 主体,可点,整段链接到评论锚点
|
||||||
|
* 作者 14px #00a2ff ← 元信息,主色
|
||||||
|
* 文章 13px #999 ← 元信息,单行省略
|
||||||
|
* 时间 13px #999 ← 元信息
|
||||||
|
* 三层字号**必须不同**,插件"三层同为 16px"是它读起来像一坨的根本原因。
|
||||||
|
* ══════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* 挂件内边距:不写。白底 + 15px/30px 由外层 `.widget` 提供,
|
||||||
|
* 与其它挂件(categories / archive 等)完全一致。理由见上面的 🔴。 */
|
||||||
|
|
||||||
|
.w-comments .yang-comment-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单条:flex 布局。
|
||||||
|
*
|
||||||
|
* 🔴 不用 float —— 插件用的是 `float:left` 的头像,长摘要下会出现
|
||||||
|
* "文字环绕到头像右侧再往下淌",元信息行钻进头像下方,整条错位。
|
||||||
|
* flex 的每一行都是独立盒子,不会出现环绕。 */
|
||||||
|
.w-comments .yang-comment-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px dashed #b4afaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 最后一条去掉分隔线 —— 跟 `.w-recommended .bookmark-item:last-child` 一致 */
|
||||||
|
.w-comments .yang-comment-item:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像:固定圆 + 裁切。
|
||||||
|
*
|
||||||
|
* 🔴 object-fit:cover 必须有:评论头像来自 Gravatar,可能是任意宽高比。
|
||||||
|
* fill(默认)会把人脸拉扁,none 会溢出。
|
||||||
|
* 🔴 flex-shrink:0 必须有:否则摘要很长时 flex 会把头像压扁(实测
|
||||||
|
* 40px 会被压到 8px 宽)。 */
|
||||||
|
.w-comments .yang-comment-avatar {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-avatar img {
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
max-width: 40px;
|
||||||
|
max-height: 40px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文字区:必须 min-width:0,否则长英文/长 URL 会撑破 flex 容器
|
||||||
|
* (flex 子项的默认 min-width 是 auto,等于"内容多宽就多宽")。 */
|
||||||
|
.w-comments .yang-comment-body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 摘要:主体 */
|
||||||
|
.w-comments .yang-comment-text {
|
||||||
|
display: block;
|
||||||
|
color: #5a5a5a;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-decoration: none;
|
||||||
|
/* 中文长句无空格,必须 break-word,否则一个字都不换行直接溢出 */
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-text:hover {
|
||||||
|
color: #00a2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 元信息行:作者 · 时间 · 文章标题 */
|
||||||
|
.w-comments .yang-comment-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-author {
|
||||||
|
color: #00a2ff;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-sep {
|
||||||
|
margin: 0 5px;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-time {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文章标题:单行省略。
|
||||||
|
* min-width:0 同样是为了让 flex 允许它比内容窄。 */
|
||||||
|
.w-comments .yang-comment-post {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-comments .yang-comment-post:hover {
|
||||||
|
color: #00a2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窄屏(侧栏整行显示,宽度充裕):让文章标题不必挤在一行里 */
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.w-comments .yang-comment-post {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
white-space: normal;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-recommended .bookmark-item {
|
.k-main .sidebar .w-recommended .bookmark-item {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -974,6 +974,437 @@ class widget_posts extends WP_Widget
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* 「最近评论」挂件(3.2.52 · 工单 #22)
|
||||||
|
*
|
||||||
|
* ── 为什么自己写一个 ────────────────────────────────────────────────
|
||||||
|
* WP 原生的 `WP_Widget_Recent_Comments` 在本主题里**已经被注销**
|
||||||
|
* (见文件顶部 widgets_init() 的 unregister_widget 列表)—— 它输出的是
|
||||||
|
* 裸 `<ul><li>` + 一条 `sprintf` 拼出来的长句,跟侧栏视觉完全不搭。
|
||||||
|
*
|
||||||
|
* 之前站点用的是插件 Comments Widget Plus(`comments-widget-plus`),
|
||||||
|
* 之所以「丑」,实测有三条硬证据:
|
||||||
|
*
|
||||||
|
* ① **中文语序断裂**。插件用一条 `_x('%1$s %2$son%3$s %4$s')` 拼句子,
|
||||||
|
* 英文读作「作者 on 文章标题」,中文渲染成「<标题>上的<作者>」,
|
||||||
|
* 而且插件**根本没带 zh_CN 语言包**(languages/ 只有一个 .pot)。
|
||||||
|
* ② **三层信息同为 16px**。文章标题 / 作者 / 摘要字号一样,只能靠颜色
|
||||||
|
* 区分;而文章标题最长(实测单条占 96px 高、标题折 2 行 50px),
|
||||||
|
* 把真正的评论内容挤到最下面。
|
||||||
|
* ③ **默认关头像**。纯文字流,视觉上没有抓手。
|
||||||
|
*
|
||||||
|
* ── 本实现的设计取舍 ────────────────────────────────────────────────
|
||||||
|
* · 信息结构改成「摘要(主体)→ 元信息行(作者 · 时间 · 文章)」,
|
||||||
|
* 不再把三件事塞进一个句子 —— 这样中文语序问题从根上消失。
|
||||||
|
* · 头像默认开、圆形、`object-fit:cover`,用 flex 而非插件的 `float:left`
|
||||||
|
* (float 在长文本下会出现文字环绕错位)。
|
||||||
|
* · 默认 5 条(插件默认 10 条,实测占满 992px 高,把侧栏撑得没有尽头)。
|
||||||
|
* · **不做缓存层**。插件那套 `wp_cache_get('cwp_widget','widget')` +
|
||||||
|
* 三个 add_action 清缓存有两个问题:缓存键是全局的(挂两个实例会互相
|
||||||
|
* 污染),而 wp_comments 上 comment_approved_date_gmt 有索引,
|
||||||
|
* 取 5 条的代价可以忽略。少一层缓存 = 少一类"改了不生效"的 bug。
|
||||||
|
* ====================================================================== */
|
||||||
|
|
||||||
|
class widget_recent_comments extends WP_Widget
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$widget_ops = array(
|
||||||
|
'name' => __('最近评论', 'kratos'),
|
||||||
|
'description' => __('带头像与摘要的最近评论,替代 Comments Widget Plus 插件', 'kratos'),
|
||||||
|
);
|
||||||
|
|
||||||
|
parent::__construct('yang_recent_comments', __('最近评论', 'kratos'), $widget_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取实例字段;没填就用默认值。
|
||||||
|
*
|
||||||
|
* 🔴 判空一律用「trim 后是不是空串」,不用 empty():
|
||||||
|
* `!empty('0')` 是 false ⇒ 摘要字数填 0、或者标题就叫「0」会被当没填。
|
||||||
|
* 与 widget_about::pick() 保持同一套判据(3.2.51 的教训)。
|
||||||
|
*
|
||||||
|
* @param array $instance
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $fallback
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function pick($instance, $key, $fallback)
|
||||||
|
{
|
||||||
|
if (!isset($instance[$key])) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = $instance[$key];
|
||||||
|
|
||||||
|
// 布尔开关走另一套:checkbox 没勾时根本不会提交,所以「缺键 = 关」
|
||||||
|
if (is_bool($fallback)) {
|
||||||
|
return (bool) $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($fallback)) {
|
||||||
|
$s = is_scalar($v) ? trim((string) $v) : '';
|
||||||
|
if ($s === '' || !preg_match('/^-?\d+$/', $s)) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_string($v)) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = trim($v);
|
||||||
|
|
||||||
|
return ($v === '') ? $fallback : $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把评论内容压成单行摘要。
|
||||||
|
*
|
||||||
|
* 🔴 为什么不用 wp_html_excerpt():那个函数按**字节**截断
|
||||||
|
* (`mb_substr` 只在 WP 6.x 之后才在部分场景用),中文会被切出半个字。
|
||||||
|
* 这里用 mb_substr 按**字符**截,并显式带省略号。
|
||||||
|
* 🔴 为什么先 wp_strip_all_tags 再压缩空白:评论允许 HTML,
|
||||||
|
* 摘要里出现 `<blockquote>` 之类会把侧栏排版打乱。
|
||||||
|
*
|
||||||
|
* @param string $content 原始评论内容
|
||||||
|
* @param int $limit 保留多少个字符
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function excerpt($content, $limit)
|
||||||
|
{
|
||||||
|
$s = wp_strip_all_tags((string) $content);
|
||||||
|
// 换行/制表/连续空格统一压成一个空格,否则摘要是多行的
|
||||||
|
$s = preg_replace('/\s+/u', ' ', $s);
|
||||||
|
$s = trim((string) $s);
|
||||||
|
|
||||||
|
if ($s === '' || $limit <= 0) {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
|
||||||
|
if (mb_strlen($s, 'UTF-8') > $limit) {
|
||||||
|
return rtrim(mb_substr($s, 0, $limit, 'UTF-8')) . '…';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有 mbstring 时的兜底:按字节截,但避免切出半个 UTF-8 字符
|
||||||
|
if (strlen($s) > $limit) {
|
||||||
|
$cut = substr($s, 0, $limit);
|
||||||
|
// 把末尾不完整的字节序列削掉
|
||||||
|
$cut = preg_replace('/[\x80-\xBF]+$/', '', $cut);
|
||||||
|
|
||||||
|
return rtrim($cut) . '…';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function widget($args, $instance)
|
||||||
|
{
|
||||||
|
$number = $this->pick($instance, 'number', 5);
|
||||||
|
if ($number < 1) {
|
||||||
|
$number = 5;
|
||||||
|
}
|
||||||
|
if ($number > 20) {
|
||||||
|
$number = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
$show_avatar = $this->pick($instance, 'show_avatar', true);
|
||||||
|
$avatar_size = $this->pick($instance, 'avatar_size', 40);
|
||||||
|
$show_excerpt = $this->pick($instance, 'show_excerpt', true);
|
||||||
|
$excerpt_length = $this->pick($instance, 'excerpt_length', 40);
|
||||||
|
$order = $this->pick($instance, 'order', 'DESC');
|
||||||
|
$exclude_pings = $this->pick($instance, 'exclude_pings', true);
|
||||||
|
|
||||||
|
/* 🔴 post_type 不能走 pick()。
|
||||||
|
*
|
||||||
|
* pick() 的规则是「trim 后是空串 ⇒ 回落 fallback」。而「全部类型」
|
||||||
|
* 这个选项本身就要用空语义表达,于是:
|
||||||
|
* 用户选「全部类型」→ update() 存 '' →
|
||||||
|
* pick() 认为"没填" → 回落 'post' → 只显示文章评论。
|
||||||
|
* 后台显示已选中、保存也成功,前台却看不到页面上的评论 ——
|
||||||
|
* 又是一处"静默失效"。
|
||||||
|
*
|
||||||
|
* 解法两层:
|
||||||
|
* ① update() 存**哨兵值 `any`**(非空,pick 不会回落);
|
||||||
|
* ② 这里**直接读原值**,把 '' 与 'any' 都当"全部类型",
|
||||||
|
* 只有键不存在时才用默认 'post'。
|
||||||
|
* 两层都做,是因为老实例(若将来有)可能存的是空串。 */
|
||||||
|
$pt_raw = array_key_exists('post_type', $instance) ? $instance['post_type'] : 'post';
|
||||||
|
$post_type = is_string($pt_raw) ? trim($pt_raw) : 'post';
|
||||||
|
$all_types = ($post_type === '' || $post_type === 'any');
|
||||||
|
|
||||||
|
/* 夹紧:这里仍需夹一遍,因为老实例(3.2.52 之前存的)里可能有
|
||||||
|
* 越界值,而 update() 只对"这次保存"生效、不会回头改老数据。 */
|
||||||
|
if ($order !== 'ASC') {
|
||||||
|
$order = 'DESC';
|
||||||
|
}
|
||||||
|
if ($avatar_size < 16) {
|
||||||
|
$avatar_size = 16;
|
||||||
|
}
|
||||||
|
if ($avatar_size > 96) {
|
||||||
|
$avatar_size = 96;
|
||||||
|
}
|
||||||
|
if ($excerpt_length < 0) {
|
||||||
|
$excerpt_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 只取审核通过的评论。
|
||||||
|
*
|
||||||
|
* 🔴 `'status' => 'approve'` 是必须的:不加的话草稿/垃圾评论会露出来。
|
||||||
|
* 🔴 `'post_status' => 'publish'` 也是必须的:评论所属文章若被改成
|
||||||
|
* 草稿/私密,评论仍存在表里,但前台不该展示 —— get_comment_link()
|
||||||
|
* 对未发布的文章会返回一个 404 链接。
|
||||||
|
* 🔴 用 WP_Comment_Query 一次取回,不逐条调 get_comment()。 */
|
||||||
|
$query = array(
|
||||||
|
'number' => $number,
|
||||||
|
'status' => 'approve',
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'order' => $order,
|
||||||
|
'orderby' => 'comment_date_gmt',
|
||||||
|
);
|
||||||
|
if (!$all_types) {
|
||||||
|
$query['post_type'] = $post_type;
|
||||||
|
}
|
||||||
|
if ($exclude_pings) {
|
||||||
|
$query['type__not_in'] = array('pingback', 'trackback');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('apply_filters')) {
|
||||||
|
$query = apply_filters('yang_recent_comments_query', $query, $instance, $this->id_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
$comments = get_comments($query);
|
||||||
|
|
||||||
|
// 🔴 没评论就整块不输出 —— 不留下"标题 + 空 ul"的空壳
|
||||||
|
if (!is_array($comments) || count($comments) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="' . esc_attr(yang_widget_class('w-comments')) . '">';
|
||||||
|
yang_widget_title($args, $instance, __('最近评论', 'kratos'), $this->id_base);
|
||||||
|
|
||||||
|
echo '<ul class="yang-comment-list">';
|
||||||
|
|
||||||
|
foreach ($comments as $comment) {
|
||||||
|
$link = get_comment_link($comment->comment_ID);
|
||||||
|
$author = get_comment_author($comment->comment_ID);
|
||||||
|
$post_title = get_the_title($comment->comment_post_ID);
|
||||||
|
if ($post_title === '') {
|
||||||
|
// 标题为空时(极少见)用「无标题」占位,不留空白
|
||||||
|
$post_title = __('无标题', 'kratos');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<li class="yang-comment-item">';
|
||||||
|
|
||||||
|
// ── 头像(左侧,圆形)──
|
||||||
|
if ($show_avatar) {
|
||||||
|
echo '<a class="yang-comment-avatar" href="' . esc_url($link) . '" rel="nofollow" aria-hidden="true" tabindex="-1">';
|
||||||
|
echo get_avatar($comment->comment_author_email, $avatar_size, '', '', array(
|
||||||
|
'class' => 'avatar yang-avatar',
|
||||||
|
'extra_attr' => 'loading="lazy"',
|
||||||
|
));
|
||||||
|
echo '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="yang-comment-body">';
|
||||||
|
|
||||||
|
// ── 主体:评论摘要 ──
|
||||||
|
if ($show_excerpt) {
|
||||||
|
$text = $this->excerpt($comment->comment_content, $excerpt_length);
|
||||||
|
if ($text !== '') {
|
||||||
|
/* 整段链接到该评论锚点 —— 侧栏空间小,用户更容易点中的
|
||||||
|
* 是"这段文字"而不是下方小字。 */
|
||||||
|
echo '<a class="yang-comment-text" href="' . esc_url($link) . '" rel="nofollow">' . esc_html($text) . '</a>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* 关掉摘要时,主体的位置留给「作者 + 文章」,
|
||||||
|
* 否则整条会变成只有一行小字,视觉上太轻。 */
|
||||||
|
echo '<a class="yang-comment-text" href="' . esc_url($link) . '" rel="nofollow">'
|
||||||
|
. esc_html(sprintf(
|
||||||
|
/* translators: 1: comment author, 2: post title */
|
||||||
|
__('%1$s 评论了《%2$s》', 'kratos'),
|
||||||
|
$author,
|
||||||
|
$post_title
|
||||||
|
))
|
||||||
|
. '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 元信息行:作者 · 时间 · 文章标题 ──
|
||||||
|
*
|
||||||
|
* 🔴 这里**不拼成句子**(不像插件那样塞一个 "on"):
|
||||||
|
* 中文里「作者 在 文章 中评论」这种句式一旦走 i18n 字符串,
|
||||||
|
* 语序、量词、标点全都要重新适配;拆成三段各自独立,
|
||||||
|
* 任何语言下都不会错位。 */
|
||||||
|
echo '<div class="yang-comment-meta">';
|
||||||
|
echo '<span class="yang-comment-author">' . esc_html($author) . '</span>';
|
||||||
|
|
||||||
|
echo '<span class="yang-comment-sep" aria-hidden="true">·</span>';
|
||||||
|
echo '<time class="yang-comment-time" datetime="' . esc_attr(get_comment_date('c', $comment->comment_ID)) . '">'
|
||||||
|
. esc_html(function_exists('human_time_diff')
|
||||||
|
? sprintf(__('%s前', 'kratos'), human_time_diff(strtotime($comment->comment_date_gmt . ' GMT')))
|
||||||
|
: get_comment_date('', $comment->comment_ID))
|
||||||
|
. '</time>';
|
||||||
|
|
||||||
|
echo '<span class="yang-comment-sep" aria-hidden="true">·</span>';
|
||||||
|
echo '<a class="yang-comment-post" href="' . esc_url(get_permalink($comment->comment_post_ID)) . '" title="' . esc_attr($post_title) . '">'
|
||||||
|
. esc_html($post_title) . '</a>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
echo '</div>'; // .yang-comment-body
|
||||||
|
echo '</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</ul>';
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($new_instance, $old_instance)
|
||||||
|
{
|
||||||
|
$instance = array();
|
||||||
|
|
||||||
|
$instance['title'] = isset($new_instance['title'])
|
||||||
|
? sanitize_text_field(trim((string) $new_instance['title']))
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// 数字:非法就落回默认,不要存 0 或空串(空串会让 pick 回落,
|
||||||
|
// 但存成 0 会被当成"用户要 0 条")
|
||||||
|
$num = isset($new_instance['number']) ? trim((string) $new_instance['number']) : '';
|
||||||
|
$instance['number'] = ($num !== '' && preg_match('/^-?\d+$/', $num)) ? (int) $num : 5;
|
||||||
|
|
||||||
|
$size = isset($new_instance['avatar_size']) ? trim((string) $new_instance['avatar_size']) : '';
|
||||||
|
$instance['avatar_size'] = ($size !== '' && preg_match('/^-?\d+$/', $size)) ? (int) $size : 40;
|
||||||
|
/* 🔴 存的时候就夹到 16~96。
|
||||||
|
* 只在 widget() 里夹(前端夹)不够:后台表单会原样显示一个
|
||||||
|
* -5,用户以为存进去了,而前台用的是 16 —— 两边不一致。
|
||||||
|
* 这里夹住,后台看到的和前台用到的才是同一个值。 */
|
||||||
|
if ($instance['avatar_size'] < 16) {
|
||||||
|
$instance['avatar_size'] = 16;
|
||||||
|
}
|
||||||
|
if ($instance['avatar_size'] > 96) {
|
||||||
|
$instance['avatar_size'] = 96;
|
||||||
|
}
|
||||||
|
|
||||||
|
$elen = isset($new_instance['excerpt_length']) ? trim((string) $new_instance['excerpt_length']) : '';
|
||||||
|
$instance['excerpt_length'] = ($elen !== '' && preg_match('/^-?\d+$/', $elen)) ? (int) $elen : 40;
|
||||||
|
if ($instance['excerpt_length'] < 0) {
|
||||||
|
$instance['excerpt_length'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance['order'] = (isset($new_instance['order']) && $new_instance['order'] === 'ASC') ? 'ASC' : 'DESC';
|
||||||
|
|
||||||
|
/* post_type:'' 与 'any' 都是「全部类型」,统一存成哨兵值 'any'。
|
||||||
|
* 🔴 不能存空串 —— 空串在 pick() 里等于"没填",会回落成 'post',
|
||||||
|
* 用户选了「全部类型」却只看到文章评论(静默失效)。 */
|
||||||
|
$pt = isset($new_instance['post_type']) ? sanitize_key((string) $new_instance['post_type']) : '';
|
||||||
|
$instance['post_type'] = ($pt === '' || $pt === 'any') ? 'any' : $pt;
|
||||||
|
|
||||||
|
// checkbox:没提交就是关。这两项必须显式写 false,
|
||||||
|
// 否则老值会残留(update 返回的数组是全新的,不写就是丢键,
|
||||||
|
// 而丢键会让 pick 拿到 fallback=true —— 用户取消勾选无效)
|
||||||
|
$instance['show_avatar'] = !empty($new_instance['show_avatar']);
|
||||||
|
$instance['show_excerpt'] = !empty($new_instance['show_excerpt']);
|
||||||
|
$instance['exclude_pings'] = !empty($new_instance['exclude_pings']);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form($instance)
|
||||||
|
{
|
||||||
|
$title = isset($instance['title']) ? (string) $instance['title'] : '';
|
||||||
|
$number = $this->pick($instance, 'number', 5);
|
||||||
|
$show_avatar = $this->pick($instance, 'show_avatar', true);
|
||||||
|
$avatar_size = $this->pick($instance, 'avatar_size', 40);
|
||||||
|
$show_excerpt = $this->pick($instance, 'show_excerpt', true);
|
||||||
|
$excerpt_length = $this->pick($instance, 'excerpt_length', 40);
|
||||||
|
$order = $this->pick($instance, 'order', 'DESC');
|
||||||
|
$exclude_pings = $this->pick($instance, 'exclude_pings', true);
|
||||||
|
/* 🔴 post_type 在这里**也不能走 pick()**(和 widget() 同一个理由,
|
||||||
|
* 而且这里更隐蔽):pick() 把空串当"没填"回落 'post',
|
||||||
|
* 于是 form() 里 $all_types 变成 false ⇒ 后台下拉框把
|
||||||
|
* 「全部类型」显示成「文章」;而 widget() 那边把空串当
|
||||||
|
* 「全部类型」⇒ **前后台显示与行为不一致**。
|
||||||
|
* 用户看到后台选的是"文章",前台却显示页面的评论 —— 无法解释。
|
||||||
|
* 所以两边都直接读原值,判据也要求两处都不出现 pick(...'post_type'...)。 */
|
||||||
|
$pt_raw = array_key_exists('post_type', $instance) ? $instance['post_type'] : 'post';
|
||||||
|
$post_type = is_string($pt_raw) ? trim($pt_raw) : 'post';
|
||||||
|
|
||||||
|
/* 「全部类型」的两个入口都收:
|
||||||
|
* · 'any' —— 3.2.52 起的哨兵值(update() 存的)
|
||||||
|
* · '' —— 3.2.52 之前若有人存过空串
|
||||||
|
* 渲染时用 $all_types,别再去比对 $post_type 的字面量。 */
|
||||||
|
$all_types = ($post_type === '' || $post_type === 'any');
|
||||||
|
|
||||||
|
$types = get_post_types(array('public' => true), 'objects');
|
||||||
|
?>
|
||||||
|
<div class="media-widget-control">
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('标题:', 'kratos'); ?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>" placeholder="<?php esc_attr_e('最近评论', 'kratos'); ?>">
|
||||||
|
<span class="description"><?php _e('留空则显示「最近评论」。', 'kratos'); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('number')); ?>"><?php _e('显示条数:', 'kratos'); ?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('number')); ?>" name="<?php echo esc_attr($this->get_field_name('number')); ?>" type="number" step="1" min="1" max="20" value="<?php echo esc_attr($number); ?>">
|
||||||
|
<span class="description"><?php _e('1 ~ 20 条,建议 5 条。', 'kratos'); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input class="checkbox" type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_avatar')); ?>" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" <?php checked($show_avatar); ?>>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('show_avatar')); ?>"><?php _e('显示头像', 'kratos'); ?></label>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('avatar_size')); ?>"><?php _e('头像尺寸(px):', 'kratos'); ?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('avatar_size')); ?>" name="<?php echo esc_attr($this->get_field_name('avatar_size')); ?>" type="number" step="1" min="16" max="96" value="<?php echo esc_attr($avatar_size); ?>">
|
||||||
|
<span class="description"><?php _e('16 ~ 96,建议 40。', 'kratos'); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input class="checkbox" type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_excerpt')); ?>" name="<?php echo esc_attr($this->get_field_name('show_excerpt')); ?>" <?php checked($show_excerpt); ?>>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('show_excerpt')); ?>"><?php _e('显示评论摘要', 'kratos'); ?></label>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('excerpt_length')); ?>"><?php _e('摘要字数:', 'kratos'); ?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('excerpt_length')); ?>" name="<?php echo esc_attr($this->get_field_name('excerpt_length')); ?>" type="number" step="1" min="0" value="<?php echo esc_attr($excerpt_length); ?>">
|
||||||
|
<span class="description"><?php _e('按字符数截断(中文一个字算一个),建议 40。', 'kratos'); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('order')); ?>"><?php _e('排序:', 'kratos'); ?></label>
|
||||||
|
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order')); ?>" name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||||
|
<option value="DESC" <?php selected($order, 'DESC'); ?>><?php _e('最新优先', 'kratos'); ?></option>
|
||||||
|
<option value="ASC" <?php selected($order, 'ASC'); ?>><?php _e('最早优先', 'kratos'); ?></option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('post_type')); ?>"><?php _e('文章类型:', 'kratos'); ?></label>
|
||||||
|
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('post_type')); ?>" name="<?php echo esc_attr($this->get_field_name('post_type')); ?>">
|
||||||
|
<option value="any" <?php selected($all_types); ?>><?php _e('全部类型', 'kratos'); ?></option>
|
||||||
|
<?php foreach ($types as $type) : ?>
|
||||||
|
<option value="<?php echo esc_attr($type->name); ?>" <?php selected(!$all_types && $post_type === $type->name); ?>><?php echo esc_html($type->labels->singular_name); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input class="checkbox" type="checkbox" id="<?php echo esc_attr($this->get_field_id('exclude_pings')); ?>" name="<?php echo esc_attr($this->get_field_name('exclude_pings')); ?>" <?php checked($exclude_pings); ?>>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('exclude_pings')); ?>"><?php _e('排除 pingback / trackback', 'kratos'); ?></label>
|
||||||
|
<span class="description"><?php _e('建议勾选:这类"评论"是别的博客的引用通知,不是真人留言。', 'kratos'); ?></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function register_widgets()
|
function register_widgets()
|
||||||
{
|
{
|
||||||
register_widget('widget_ad');
|
register_widget('widget_ad');
|
||||||
@@ -981,5 +1412,6 @@ function register_widgets()
|
|||||||
register_widget('widget_tags');
|
register_widget('widget_tags');
|
||||||
register_widget('widget_search');
|
register_widget('widget_search');
|
||||||
register_widget('widget_posts');
|
register_widget('widget_posts');
|
||||||
|
register_widget('widget_recent_comments');
|
||||||
}
|
}
|
||||||
add_action('widgets_init', 'register_widgets');
|
add_action('widgets_init', 'register_widgets');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Theme Name: Kratos_Yang
|
Theme Name: Kratos_Yang
|
||||||
Text Domain: kratos_Yang
|
Text Domain: kratos_Yang
|
||||||
Version: 3.2.51
|
Version: 3.2.52
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
||||||
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
||||||
|
|||||||
Reference in New Issue
Block a user