feat(comments): 评论区按 Zibll 1:1 全量复刻

- 评论项/表单/工具栏/筛选/分页/空状态 DOM 全部替换为 zibll 原始类名
  (li.comment、comt-main、comt-meta、list-inline、dropup 工具栏、
  theme-pagination/pagenav、post_ajax_loader 骨架等)
- 移植 zibll main.css L9412–9751 桌面 + 移动端评论样式,
  缺失基础组件(title-theme/dropdown-menu/line-form/tab-nav-theme 等)
  以 #comments 作用域补齐
- 删除全部 aksrt-comment__* 旧类名与旧样式,无兼容层
- JS 选择器对齐 zibll DOM:片段整块替换 #postcomments、骨架加载、
  跳页输入、prepend 插入、addComment onclick 剥离,
  移除 WP comment-reply enqueue 避免双 moveForm
- 表情资源替换为 zibll 全套 gif(内容逐字节一致,git 无差异)
- REST 后端 (CommentActions) 不变

验证:php -l 全部通过;5 个改动 JS 语法校验通过;
node --test tests/widget-system/post-visual-contract.test.cjs 12/12 通过
This commit is contained in:
2026-08-22 13:29:51 +08:00
parent 251fe86527
commit e1f176429b
15 changed files with 1286 additions and 1439 deletions
+2 -132
View File
@@ -1,140 +1,10 @@
/**
* AKSRT Comment Tools
*
* 评论工具栏样式:表情、代码、图片上传、快捷回复。
* 所有选择器均带 .comment- 或 .aksrt-comment- 命名空间。
* 评论内容中的代码块样式。工具栏(表情/代码/图片/快捷回复)的 UI
* 由 assets/css/frontend/comments/shell.css 按 zibll 选择器提供。
*/
/* ========== 评论工具栏 ========== */
.comment-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 8px;
margin-bottom: 8px;
background: var(--surface-secondary, #f8f9fa);
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 8px;
}
.comment-toolbar__btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--text-secondary, #666);
font-size: 14px;
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
}
.comment-toolbar__btn:hover {
background: var(--color-primary-soft, rgba(240, 68, 148, 0.08));
color: var(--color-primary);
}
.comment-toolbar__btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.comment-toolbar__separator {
width: 1px;
height: 20px;
margin: 0 4px;
background: var(--color-line, #e8e8e8);
}
/* Server-side toolbar gating (comment_smilie / comment_code / comment_img):
the .comment-toolbar__btn display rule outranks the UA [hidden] rule, so an
attribute-qualified selector is needed to honor the rendered hidden state. */
.comment-toolbar__btn[hidden],
.comment-toolbar__separator[hidden] {
display: none;
}
/* ========== 表情选择器 ========== */
.comment-emoji-picker {
position: absolute;
bottom: 100%;
left: 0;
z-index: 100;
width: 320px;
max-height: 240px;
padding: 12px;
background: var(--surface-page, #fff);
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
overflow-y: auto;
}
.comment-emoji-picker__grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 4px;
}
.comment-emoji-picker__item {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 4px;
background: transparent;
font-size: 18px;
cursor: pointer;
transition: background-color 0.1s ease;
}
.comment-emoji-picker__item:hover {
background: var(--color-primary-soft, rgba(240, 68, 148, 0.08));
}
/* ========== 快捷回复 ========== */
.comment-quick-reply {
padding: 8px 12px;
margin-top: 8px;
background: var(--surface-secondary, #f8f9fa);
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 8px;
}
.comment-quick-reply__title {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary, #666);
}
.comment-quick-reply__templates {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 6px;
}
.comment-quick-reply__template {
padding: 4px 10px;
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 16px;
background: var(--surface-page, #fff);
font-size: 12px;
color: var(--text-secondary, #666);
cursor: pointer;
transition: border-color 0.15s ease, color 0.15s ease;
}
.comment-quick-reply__template:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
/* ========== 评论代码块 ========== */
.comment-form textarea code,
.comment-content code {
+159 -96
View File
@@ -1,112 +1,175 @@
/* Zibll 1:1 FixedInput / virtual-input / item chrome */
.virtual-input { display: none; }
/* ============================================================
* AKSRT Comments — Zibll 1:1 移动端 + FixedInput / virtual-input
*
* 移动端规则来源:zibll/css/main.css 移动端媒体查询中的评论段。
* ============================================================ */
.virtual-input {
display: none;
}
/* 移动端虚拟输入条 + 吸底表单(FixedInput) */
@media (max-width: 767.98px) {
.virtual-input {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; margin-bottom: 12px; border-radius: 999px;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
margin-bottom: 12px;
border-radius: 999px;
background: var(--surface-1, #fff);
box-shadow: var(--shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));
cursor: pointer;
}
.virtual-input .avatar-img { width: 32px; height: 32px; border-radius: 50%; }
.virtual-input .simulation { flex: 1; color: var(--color-ink-4, #999); font-size: 13px; }
.virtual-input .avatar,
.virtual-input .avatar-img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.virtual-input .simulation {
flex: 1;
color: var(--color-ink-4, #999);
font-size: 13px;
}
#respond.mobile-fixed {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 1050;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1050;
background: var(--surface-1, #fff);
border-radius: 12px 12px 0 0;
box-shadow: 0 -8px 28px rgba(0, 0, 0, .12);
padding: 12px 14px max(12px, env(safe-area-inset-bottom));
transform: translateY(110%); transition: transform .25s ease;
max-height: 85vh; overflow: auto;
transform: translateY(110%);
transition: transform .25s ease;
max-height: 85vh;
overflow: auto;
}
#respond.mobile-fixed.show { transform: translateY(0); }
#respond.mobile-fixed .fixed-body {
position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: -1;
opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
#respond.mobile-fixed.show .fixed-body { opacity: 1; pointer-events: auto; }
#respond.mobile-fixed .comt-title { display: none; }
}
.comment-header .author-box { display: flex; align-items: center; gap: 8px; width: 100%; }
.comment-header .author-box name,
.comment-header .author-box .flex1 { flex: 1; min-width: 0; }
/* Keep name + level badge on one line; clamp the name so the badge never wraps. */
.comment-header .author-box name { flex-wrap: nowrap; }
.comment-header .author-box .aksrt-comment__author { max-width: 14em; }
.comment-header .aksrt-comment__like,
.comment-header .action-comment-like {
margin-left: auto; display: inline-flex; align-items: center; gap: 4px;
color: var(--color-ink-3, #888); text-decoration: none; font-size: 13px;
}
.comment-header .aksrt-comment__like.is-active { color: var(--color-primary, #2060ef); }
.comt-meta {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
font-size: 12px; margin-top: 6px;
}
.comt-meta .reply-link a, .comt-meta .comment-reply-link { color: var(--color-ink-3, #777); }
.comment-action-dropdown .dropdown-menu { min-width: 140px; padding: 6px 0; }
.comment-action-dropdown .dropdown-menu a {
display: block; padding: 6px 14px; color: var(--color-ink, #333);
text-decoration: none; font-size: 13px;
}
.comment-action-dropdown .dropdown-menu a:hover { background: var(--surface-2, #f5f5f5); }
.smilie-icon { width: 30px; height: 30px; vertical-align: -8px; display: inline-block; }
.comment-inline-img, .comment-content .box-img {
max-width: 260px; max-height: 350px; border-radius: 6px; display: block; margin: 6px 0;
}
.comment-code {
background: var(--surface-2, #f6f8fa); padding: 10px 12px; border-radius: 6px;
overflow: auto; font-size: 12px;
}
.commentlist .children { margin-left: 86px; padding-left: 0; list-style: none; }
.commentlist .children .children { margin-left: 23px; }
.commentlist .children .comt-avatar,
.commentlist .children .avatar-img.comt-avatar { width: 26px !important; height: 26px !important; }
.input-expand-wrap { display: inline-block; position: relative; }
.input-expand-wrap .dropdown-menu,
.input-expand-wrap .dropdown-smilie {
display: none; position: absolute; bottom: 100%; left: 0; z-index: 20;
background: var(--surface-1, #fff); box-shadow: 0 6px 24px rgba(0,0,0,.12);
border-radius: 8px; padding: 8px;
}
.input-expand-wrap.open > .dropdown-menu,
.input-expand-wrap.open .dropdown-smilie,
.dropup.open > .dropdown-menu { display: block; }
.dropdown-smilie {
display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px;
max-width: 280px; max-height: 200px; overflow: auto;
}
.dropdown-smilie a img { width: 28px; height: 28px; }
.quick-reply-item {
display: block; padding: 6px 10px; color: var(--color-ink, #333);
text-decoration: none; font-size: 13px;
}
.quick-reply-item:hover { background: var(--surface-2, #f5f5f5); }
.comt-ctrl { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.comt-tips-left { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; flex: 1; }
.comt-tips-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
#comments .comt-title { width: 90px; }
#comments .comt-title .comt-avatar img,
#comments .comt-avatar .avatar-img { width: 60px; height: 60px; border-radius: 50%; }
#comments .comt-box #comment { min-height: 88px; resize: vertical; }
@media (min-width: 768px) { .hide-pc { display: none !important; } }
/* IP geo badges */
.comt-meta .comment-city-badge,
.comt-meta .aksrt-ip-geo-badge {
margin-left: 0.35em;
vertical-align: middle;
#respond.mobile-fixed.show {
transform: translateY(0);
}
.aksrt-author-hero__identity .aksrt-ip-geo-badge,
.uc-header__meta .aksrt-ip-geo-badge,
.article-avatar .aksrt-ip-geo-badge {
margin: 2px 0;
vertical-align: middle;
#respond.mobile-fixed .fixed-body {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, .35);
z-index: -1;
opacity: 0;
pointer-events: none;
transition: opacity .2s ease;
}
#respond.mobile-fixed.show .fixed-body {
opacity: 1;
pointer-events: auto;
}
#respond.mobile-fixed .comt-title {
display: none;
}
/* ---- zibll 移动端评论规则 ---- */
#comments .comt-title {
display: none;
}
#comments .comt-box {
width: 100%;
padding: 0;
}
#comments .list-inline {
padding: 15px 0;
margin: 0;
}
#comments .comment-signarea h3 {
font-size: 13px;
}
#comments .comt-tips-left .but svg,
#comments .comt-tips-left .but .fa {
margin: 0;
}
#comments .hide-sm {
display: none !important;
}
#comments .btn-input-expand {
width: 31px;
padding: 0;
height: 31px;
line-height: 31px;
}
#postcomments .comment-header {
font-size: 14px;
}
#postcomments .comt-meta {
font-size: 12px;
}
#postcomments .comment .list-inline > li {
padding: 0;
}
#postcomments .comment .list-inline > li + li {
padding-left: 10px;
}
#postcomments .avatar-img {
--this-size: 36px;
margin-right: 10px;
}
#postcomments .commentlist > .comment > .list-inline .comment-footer {
margin-left: 45px;
margin-top: -15px;
}
#postcomments .children {
margin-left: 0;
}
#postcomments .children .children {
margin-left: 13px;
}
#postcomments .children .list-inline {
padding: 15px;
}
#comments .pagenav a,
#comments .pagination ul > li {
display: none;
}
#comments .pagenav a.next,
#comments .pagenav a.prev {
display: inline-block;
}
#comments .pagenav .page-numbers {
font-size: 12px;
margin: 2px;
min-width: 28px;
}
#postcomments .post_ajax_loader .ml20 {
margin-left: 5px;
}
}
@media (min-width: 768px) {
.hide-pc {
display: none !important;
}
.aksrt-author-hero__identity {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem;
margin-top: 0.35rem;
}
File diff suppressed because it is too large Load Diff
-23
View File
@@ -56,29 +56,6 @@
font-size: 12px;
}
/* 评论空状态 */
.comments-empty {
text-align: center;
padding: 40px 20px;
}
.comments-empty__icon {
font-size: 48px;
color: var(--surface-3, #e8e8e8);
margin-bottom: 12px;
}
.comments-empty__text {
font-size: 14px;
color: var(--color-ink-3, #777);
margin-bottom: 4px;
}
.comments-empty__hint {
font-size: 12px;
color: var(--color-ink-4, #999);
}
/* 搜索空状态 */
.search-empty {
text-align: center;
+23 -25
View File
@@ -5,11 +5,11 @@
* Edit uses moveForm (#respond) via comment-move-form.js — not inline textarea.
* Submit owned by assets/js/core/comments.js (single submit contract).
*
* Markup (aksrt_comment_callback):
* li.aksrt-comment[data-comment-id]
* .comment-header .aksrt-comment__like[data-comment-action=like]
* Markup (aksrt_comment_callback, zibll DOM):
* li.comment#comment-{id}
* .comment-header .action-comment-like[data-comment-action=like] > count
* .comment-content#comment-content-{id}
* .comt-meta [data-comment-action=pin|approve|hold|spam|delete]
* .comt-meta [data-comment-action=pin|approve|hold|delete]
*
* @package AKSRT
*/
@@ -19,8 +19,8 @@
if (!window.aksrtData) return;
var COMMENT_SELECTOR = '.aksrt-comment, .comment';
var CONTENT_SELECTOR = '.aksrt-comment__text, .comment-content';
var COMMENT_SELECTOR = 'li.comment';
var CONTENT_SELECTOR = '.comment-content';
var REST_BASE = (aksrtData.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1';
var REST_NONCE = aksrtData.restNonce || '';
@@ -48,6 +48,10 @@
var commentEl = actionBtn.closest(COMMENT_SELECTOR);
if (!commentEl) return;
// 操作后收起 ⋮ 菜单
var openMenu = actionBtn.closest('span.dropdown.open');
if (openMenu) openMenu.classList.remove('open');
var commentId = getCommentId(commentEl);
if (!commentId) return;
@@ -75,14 +79,9 @@
var count = (result.data && result.data.count) || 0;
btn.setAttribute('data-active', active ? '1' : '0');
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
btn.classList.toggle('is-active', active);
var icon = btn.querySelector('i');
if (icon) { icon.classList.toggle('fa', active); icon.classList.toggle('fa', !active); }
var countEl = btn.querySelector('.aksrt-comment__like-count');
if (countEl) {
countEl.textContent = String(count);
if (count > 0) { countEl.removeAttribute('hidden'); } else { countEl.setAttribute('hidden', ''); }
}
btn.classList.toggle('actived', active);
var countEl = btn.querySelector('count');
if (countEl) countEl.textContent = String(count);
})
.catch(function () { notyf('网络错误', 'danger'); })
.finally(function () { btn.dataset.busy = ''; });
@@ -103,7 +102,7 @@
});
setTimeout(function () {
commentEl.remove();
var countEl = document.querySelector('.comments-count');
var countEl = document.querySelector('#comments .title-theme small');
if (countEl) {
var m = (countEl.textContent || '').match(/(\d+)/);
if (m) {
@@ -134,8 +133,7 @@
if (!result.success) { notyf((result.data && result.data.message) || result.message || '操作失败', 'danger'); return; }
var pinned = !!(result.data && result.data.pinned);
btn.setAttribute('data-pinned', pinned ? '1' : '0');
btn.innerHTML = '<i class="fa fas fa-thumbtack" aria-hidden="true"></i> ' + (pinned ? '取消置顶' : '置顶');
commentEl.classList.toggle('aksrt-comment--pinned', pinned);
btn.innerHTML = '<i class="fa fa-thumbtack mr10 fa-fw" aria-hidden="true"></i>' + (pinned ? '取消置顶' : '设置置顶');
syncPinnedBadge(commentEl, pinned);
notyf(result.message || (pinned ? '评论已置顶。' : '已取消置顶。'), 'success');
})
@@ -143,15 +141,15 @@
}
function syncPinnedBadge(commentEl, pinned) {
var header = commentEl.querySelector('.aksrt-comment__header, .comment-header');
if (!header) return;
var badge = header.querySelector('.aksrt-comment__badge--pinned');
var name = commentEl.querySelector('.comment-header name');
if (!name) return;
var badge = name.querySelector('badge.badge-topping');
if (pinned && !badge) {
badge = document.createElement('span');
badge.className = 'aksrt-comment__badge aksrt-comment__badge--pinned';
badge.innerHTML = '<i class="fa fas fa-thumbtack" aria-hidden="true"></i> 置顶';
var time = header.querySelector('.aksrt-comment__time');
header.insertBefore(badge, time || null);
badge = document.createElement('badge');
badge.className = 'badge-topping jb-blue mr6 flex0';
badge.title = '置顶评论';
badge.innerHTML = '<i class="fa fa-thumbtack" aria-hidden="true"></i>';
name.insertBefore(badge, name.firstChild);
} else if (!pinned && badge) {
badge.remove();
}
+4 -4
View File
@@ -49,7 +49,7 @@
}
if (!key) return;
insertAtCursor(findTextarea(sm), '[g=' + key + ']');
var wrap = sm.closest('.dropup, .input-expand-wrap');
var wrap = sm.closest('span.dropup');
if (wrap) wrap.classList.remove('open');
return;
}
@@ -66,7 +66,7 @@
}
insertAtCursor(findTextarea(codeBtn), '[code]\n' + code + '\n[/code]\n');
if (ta) ta.value = '';
var w = codeBtn.closest('.dropup, .input-expand-wrap');
var w = codeBtn.closest('span.dropup');
if (w) w.classList.remove('open');
return;
}
@@ -83,7 +83,7 @@
}
insertAtCursor(findTextarea(imgBtn), '[img=' + url + ']');
if (input) input.value = '';
var iw = imgBtn.closest('.dropup, .input-expand-wrap');
var iw = imgBtn.closest('span.dropup');
if (iw) iw.classList.remove('open');
return;
}
@@ -117,7 +117,7 @@
e.preventDefault();
var phrase = q.getAttribute('data-text') || q.textContent || '';
insertAtCursor(findTextarea(q), phrase);
var qw = q.closest('.dropup, .input-expand-wrap');
var qw = q.closest('span.dropup');
if (qw) qw.classList.remove('open');
}
});
+12 -13
View File
@@ -145,7 +145,9 @@
if (submit) submit.disabled = false;
});
} else {
setActionText('回复评论');
var nameEl = comm ? comm.querySelector('.comment-header name') : null;
var author = nameEl ? (nameEl.textContent || '').trim() : '';
setActionText(author ? ('回复 @' + author) : '回复评论');
ensureEditField(respond, 0);
if (textarea) {
textarea.focus();
@@ -195,25 +197,22 @@
}
});
// Dropup toggles (guest / expand).
// Dropup / dropdown toggles (guest info, expand toolbar, action menu).
document.addEventListener('click', function (e) {
var t = e.target && e.target.closest ? e.target.closest('[data-toggle-class="open"]') : null;
var t = e.target && e.target.closest ? e.target.closest('[data-toggle-class="open"], span.dropup > a.but, [data-toggle="dropdown"]') : null;
if (!t) return;
var targetSel = t.getAttribute('data-target');
if (!targetSel) return;
var wrap = t.closest('span.dropup, span.dropdown');
if (!wrap) return;
// 点击已在打开的下拉内部(表情/快捷项等)不切换
if (wrap.classList.contains('open') && t.parentElement !== wrap) return;
e.preventDefault();
var wrap = t.closest('.dropup, .input-expand-wrap, #comment-user-info') || t.parentElement;
var panel = targetSel.charAt(0) === '#' || targetSel.charAt(0) === '.'
? (wrap && wrap.querySelector(targetSel)) || document.querySelector(targetSel)
: null;
if (wrap) wrap.classList.toggle('open');
if (panel && panel !== wrap) panel.classList.toggle('open');
wrap.classList.toggle('open');
});
// Close dropups on outside click.
document.addEventListener('click', function (e) {
if (e.target.closest && e.target.closest('.dropup, .input-expand-wrap, [data-toggle-class="open"]')) return;
document.querySelectorAll('.dropup.open, .input-expand-wrap.open, #comment-user-info.open').forEach(function (n) {
if (e.target.closest && e.target.closest('span.dropup.open, span.dropdown.open')) return;
document.querySelectorAll('span.dropup.open, span.dropdown.open').forEach(function (n) {
n.classList.remove('open');
});
});
+70 -105
View File
@@ -97,13 +97,13 @@
}
function updateCommentCount(delta) {
var countEl = document.querySelector('.comments-count');
var countEl = document.querySelector('#comments .title-theme small');
if (!countEl) return;
var text = countEl.textContent || '';
var match = text.match(/(\d+)/);
if (!match) return;
var nextCount = Math.max(0, parseInt(match[1], 10) + delta);
countEl.textContent = '共 ' + nextCount + ' 条';
countEl.textContent = text.replace(match[1], String(nextCount));
}
function handleResponse(responseText, statusCode) {
try {
@@ -316,9 +316,9 @@
newComment.style.opacity = '0';
newComment.style.transition = 'max-height 0.4s ease, opacity 0.3s ease';
if (isReply && parentId) {
var parentComment = document.querySelector('#comment-' + parentId + ', [data-comment-id="' + parentId + '"]');
var parentComment = document.querySelector('#comment-' + parentId);
if (parentComment) {
var children = parentComment.querySelector('.children');
var children = parentComment.querySelector(':scope > .children');
if (!children) {
children = document.createElement('ol');
children.className = 'children';
@@ -354,8 +354,15 @@
}
function prependToCommentList(el) {
var list = document.querySelector('.commentlist, #comment-list > ol, #comment-list');
if (list) list.insertBefore(el, list.firstChild);
var list = document.querySelector('#comments .commentlist');
if (!list) return;
// 插到筛选栏之后、最新评论之前
var filter = list.querySelector('.comment-filter');
if (filter && filter.nextSibling) {
list.insertBefore(el, filter.nextSibling);
} else {
list.insertBefore(el, list.firstChild);
}
}
function restoreRespondPosition() {
@@ -471,66 +478,27 @@
function getCurrentAuthorOnly() {
var url = new URL(window.location.href);
if (url.searchParams.get("aksrt_author_only") === "1") return true;
var btn = document.querySelector("#comments .comment-author-filter");
var btn = document.querySelector("#comments .btn-only-author");
return !!(btn && btn.classList.contains("active") && btn.dataset.authorOnly === "0");
}
function applyFragmentPayload(commentsArea, data, urlState) {
if (!commentsArea || !data) return;
if (!commentsArea || !data || !data.html) return;
if (data.filter_html) {
var currentFilter = commentsArea.querySelector(".comment-filter");
var temp = document.createElement("div");
temp.innerHTML = data.filter_html;
var newFilter = temp.firstElementChild;
if (newFilter) {
if (currentFilter) currentFilter.replaceWith(newFilter);
else {
var listAnchor = commentsArea.querySelector("#postcomments, #comment-list");
if (listAnchor && listAnchor.parentNode) {
listAnchor.parentNode.insertBefore(newFilter, listAnchor);
}
}
}
}
// data.html 是完整的 <div id="postcomments">(含筛选、列表、分页)
var tempList = document.createElement("div");
tempList.innerHTML = data.html;
var newListWrap = tempList.querySelector("#postcomments");
if (!newListWrap) return;
var currentListWrap = commentsArea.querySelector("#postcomments");
var currentList = commentsArea.querySelector("#comment-list");
var currentEmpty = commentsArea.querySelector(".comments-empty");
var tempList = document.createElement("div");
tempList.innerHTML = data.html || "";
var newListWrap = tempList.querySelector("#postcomments");
var newEmpty = tempList.querySelector(".comments-empty");
if (newListWrap) {
if (currentListWrap) currentListWrap.replaceWith(newListWrap);
else if (currentList) currentList.replaceWith(newListWrap);
else commentsArea.appendChild(newListWrap);
if (currentEmpty) currentEmpty.remove();
} else if (newEmpty) {
if (currentListWrap) currentListWrap.remove();
if (currentList) currentList.remove();
if (currentEmpty) currentEmpty.replaceWith(newEmpty);
else commentsArea.appendChild(newEmpty);
if (currentListWrap) {
currentListWrap.replaceWith(newListWrap);
} else {
commentsArea.appendChild(newListWrap);
}
var currentPagination = commentsArea.querySelector(".aksrt-comment-pagination, .comment-navigation, .pagenav.ajax-pag");
if (data.pagination_html) {
var ptemp = document.createElement("div");
ptemp.innerHTML = data.pagination_html;
var newPag = ptemp.firstElementChild;
if (newPag) {
if (currentPagination) currentPagination.replaceWith(newPag);
else {
var list = commentsArea.querySelector("#postcomments, #comment-list");
if (list && list.parentNode) list.parentNode.insertBefore(newPag, list.nextSibling);
}
}
} else if (currentPagination) {
currentPagination.remove();
}
var currentCount = commentsArea.querySelector(".comments-count");
var currentCount = commentsArea.querySelector(".title-theme small");
if (currentCount && data.count_text) currentCount.textContent = data.count_text;
if (urlState && window.history && typeof window.history.replaceState === "function") {
@@ -562,20 +530,30 @@
}
/* ====== 评论列表动态刷新 ====== */
function showListLoading(commentsArea) {
var list = commentsArea.querySelector(".commentlist");
if (!list) return;
// 保留筛选栏,清空条目并显示 zibll 骨架
var filter = list.querySelector(".comment-filter");
var loader = list.querySelector(".post_ajax_loader");
var skeleton = loader ? loader.cloneNode(true) : null;
Array.prototype.slice.call(list.children).forEach(function (child) {
if (child !== filter && child !== loader) child.remove();
});
if (skeleton) {
skeleton.style.display = "block";
skeleton.removeAttribute("hidden");
list.appendChild(skeleton);
}
}
function reloadCommentList() {
var commentsArea = document.getElementById('comments');
var listContainer = document.getElementById('comment-list');
if (!commentsArea && !listContainer) {
if (!commentsArea) {
window.location.reload();
return;
}
if (listContainer) {
listContainer.innerHTML = '<li class="comment-loading-skeleton">' +
'<div class="aksrt-comment"><div class="aksrt-comment__body">' +
'<div style="height:60px;background:#f0f0f0;border-radius:8px;margin-bottom:10px;animation:pulse 1.5s ease-in-out infinite;"></div>' +
'<div style="height:20px;width:70%;background:#f0f0f0;border-radius:4px;animation:pulse 1.5s ease-in-out infinite;"></div>' +
'</div></div></li>';
}
showListLoading(commentsArea);
fetchCommentFragment({
order: getCurrentCommentOrder(),
@@ -583,7 +561,7 @@
page: 1
}).then(function (json) {
if (json && json.success && json.data) {
applyFragmentPayload(commentsArea || document, json.data, null);
applyFragmentPayload(commentsArea, json.data, null);
return;
}
throw new Error('fragment failed');
@@ -622,13 +600,7 @@
commentsArea.dataset.orderInit = "1";
function renderLoadingState() {
var listContainer = commentsArea.querySelector("#comment-list");
if (!listContainer) return;
listContainer.innerHTML = '<li class="comment-loading-skeleton">' +
'<div class="aksrt-comment"><div class="aksrt-comment__body">' +
'<div style="height:60px;background:#f0f0f0;border-radius:8px;margin-bottom:10px;animation:pulse 1.5s ease-in-out infinite;"></div>' +
'<div style="height:20px;width:70%;background:#f0f0f0;border-radius:4px;animation:pulse 1.5s ease-in-out infinite;"></div>' +
'</div></div></li>';
showListLoading(commentsArea);
}
function applyCommentOrder(order) {
if (order !== "newest" && order !== "oldest" && order !== "hottest") {
@@ -685,7 +657,7 @@
if (!target || typeof target.closest !== "function") return;
// 「只看作者」同时带 comment-orderby,需优先于排序按钮判断
var authorBtn = target.closest(".comment-author-filter");
var authorBtn = target.closest(".btn-only-author");
if (authorBtn && commentsArea.contains(authorBtn)) {
e.preventDefault();
applyAuthorFilter(authorBtn.getAttribute("data-author-only") || "0");
@@ -701,40 +673,33 @@
}
function initGuestFields() {
var root = document.getElementById("comments") || document;
root.querySelectorAll("[data-aksrt-guest-toggle]").forEach(function (btn) {
if (btn.dataset.aksrtGuestBound === "1") return;
btn.dataset.aksrtGuestBound = "1";
var wrap = btn.closest(".comment-form__guest");
var fields = wrap ? wrap.querySelector("[data-aksrt-guest-fields]") : null;
if (!fields) return;
btn.addEventListener("click", function (e) {
/* ====== 数字分页跳页(zibll pag-jump) ====== */
function jumpToCommentPage(page) {
page = parseInt(page, 10) || 1;
var url = new URL(window.location.href);
url.searchParams.set("cpage", String(page));
url.hash = "comments";
window.location.href = url.toString();
}
function initCommentPageJump() {
document.addEventListener("click", function (e) {
var jump = e.target.closest ? e.target.closest("#comments .pag-jump") : null;
if (!jump) return;
e.preventDefault();
var open = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", open ? "false" : "true");
if (open) {
fields.setAttribute("hidden", "");
fields.classList.remove("is-open");
} else {
fields.removeAttribute("hidden");
fields.classList.add("is-open");
var first = fields.querySelector("input");
if (first) first.focus();
}
var input = jump.querySelector(".jump-input");
if (input) jumpToCommentPage(input.value);
});
// Auto-open if browser restored values or validation fails later.
var form = btn.closest("form");
if (form) {
form.addEventListener("invalid", function () {
fields.removeAttribute("hidden");
fields.classList.add("is-open");
btn.setAttribute("aria-expanded", "true");
}, true);
}
document.addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;
var input = e.target;
if (!input || !input.classList || !input.classList.contains("jump-input")) return;
if (!input.closest("#comments .pagenav")) return;
e.preventDefault();
jumpToCommentPage(input.value);
});
}
window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); initGuestFields(); });
window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); initCommentPageJump(); });
window.AksrtFrontend.register("aksrt-comment-order", function (root) { initCommentOrder(root); });
})();
+4 -2
View File
@@ -42,8 +42,8 @@
target = target.previousElementSibling;
}
if (isComment) {
var commentList = document.getElementById("comment-list") ||
(container.parentElement && container.parentElement.querySelector("#comment-list, .commentlist, ol.commentlist"));
var commentList = container.closest("#comment-list, .commentlist, ol.commentlist") ||
document.getElementById("comment-list");
if (commentList) target = commentList;
}
if (!target && container.parentElement) {
@@ -167,6 +167,8 @@
while (newItems.length > 0) {
if (homepagePanel && container.parentNode === target) {
target.insertBefore(newItems[0], container);
} else if (container.parentNode === target) {
target.insertBefore(newItems[0], container);
} else {
target.appendChild(newItems[0]);
}
+92 -108
View File
@@ -321,7 +321,7 @@ if (!function_exists('aksrt_comments_render_list_fragment')) :
/**
* Fragment payload for order/author/page AJAX.
*
* @return array{html:string,filter_html:string,pagination_html:string,count_text:string,total:int,max_pages:int,page:int,order:string,author_only:bool,empty:bool}
* @return array{html:string,items_html:string,count_text:string,total:int,max_pages:int,page:int,order:string,author_only:bool,empty:bool}
*/
function aksrt_comments_render_list_fragment(int $post_id, array $args = []): array {
$result = aksrt_comments_query_for_post($post_id, $args);
@@ -351,25 +351,19 @@ if (!function_exists('aksrt_comments_render_list_fragment')) :
];
ob_start();
echo '<div id="postcomments"><ol class="commentlist list-unstyled">';
if ($items_html === '') {
if (function_exists('aksrt_comments_render_empty')) {
aksrt_comments_render_empty($ctx);
}
echo '<div class="pagenav hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
if (function_exists('aksrt_comments_render_filter') && !empty($ctx['corderby_enabled'])) {
aksrt_comments_render_filter($ctx);
}
$filter_html = (string) ob_get_clean();
ob_start();
if ($items_html === '' && function_exists('aksrt_comments_render_empty')) {
aksrt_comments_render_empty($ctx);
} else {
echo '<div id="postcomments"><ol class="commentlist list-unstyled" id="comment-list">';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rendered comment markup.
echo $items_html;
echo '</ol></div>';
}
$list_html = (string) ob_get_clean();
ob_start();
if (function_exists('aksrt_render_comment_pagination') && $max_pages > 1) {
// Temporarily override page counts via filter.
$pages_filter = static function () use ($max_pages) {
return $max_pages;
};
@@ -377,16 +371,16 @@ if (!function_exists('aksrt_comments_render_list_fragment')) :
aksrt_render_comment_pagination();
remove_filter('get_comment_pages_count', $pages_filter);
}
$pagination_html = (string) ob_get_clean();
}
echo '</ol></div>';
$list_html = (string) ob_get_clean();
wp_reset_postdata();
return [
'html' => $list_html,
'items_html' => $items_html,
'filter_html' => $filter_html,
'pagination_html' => $pagination_html,
'count_text' => sprintf(__('共 %d 条', 'aksrt'), $total),
'count_text' => sprintf(__('共%s条', 'aksrt'), (string) $total),
'total' => $total,
'max_pages' => $max_pages,
'page' => $page,
@@ -406,30 +400,32 @@ if (!function_exists('aksrt_comments_render_signarea')) :
*/
function aksrt_comments_render_signarea(array $ctx): void {
$oauth_providers = is_array($ctx['oauth_providers'] ?? null) ? $ctx['oauth_providers'] : [];
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink());
?>
<div class="comment-signarea text-center box-body radius8">
<i class="fa fas fa-lock" aria-hidden="true"></i>
<span><?php esc_html_e('登录后参与评论', 'aksrt'); ?></span>
<div class="comment-login-prompt__btns">
<a class="signin-loader but hollow" href="<?php echo esc_url(function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink())); ?>"><?php esc_html_e('登录', 'aksrt'); ?></a>
<?php if (get_option('users_can_register')) : ?>
<a class="signup-loader but b-theme" href="<?php echo esc_url(function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url()); ?>"><?php esc_html_e('注册', 'aksrt'); ?></a>
<h3 class="text-muted em12 theme-box muted-3-color"><?php esc_html_e('请登录后发表评论', 'aksrt'); ?></h3>
<p>
<a href="javascript:;" data-login-url="<?php echo esc_url($login_url); ?>" class="signin-loader but c-blue padding-lg"><i class="fa fa-fw fa-sign-in mr10" aria-hidden="true"></i><?php esc_html_e('登录', 'aksrt'); ?></a>
<?php if (get_option('users_can_register')) :
$register_url = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
?>
<a href="javascript:;" data-register-url="<?php echo esc_url($register_url); ?>" class="signup-loader ml10 but c-yellow padding-lg"><i class="fa fa-fw fa-user-plus mr10" aria-hidden="true"></i><?php esc_html_e('注册', 'aksrt'); ?></a>
<?php endif; ?>
</div>
</p>
<?php if (!empty($oauth_providers)) : ?>
<div class="comment-signarea__oauth">
<p class="social-login-wrap">
<?php foreach ($oauth_providers as $provider) :
$provider_name = (string) ($provider['name'] ?? '');
$label = (string) ($provider['label'] ?? $provider_name);
$url = (string) ($provider['url'] ?? '#');
$icon = $provider['icon'] ?? '';
?>
<a href="<?php echo esc_url($url); ?>" class="social-login-item but hollow <?php echo esc_attr($provider_name); ?>" data-provider="<?php echo esc_attr($provider_name); ?>" title="<?php echo esc_attr($label); ?>">
<a href="<?php echo esc_url($url); ?>" class="social-login-item but hollow ml10 <?php echo esc_attr($provider_name); ?>" data-provider="<?php echo esc_attr($provider_name); ?>" title="<?php echo esc_attr($label); ?>">
<?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<span><?php echo esc_html($label); ?></span>
</a>
<?php endforeach; ?>
</div>
</p>
<?php endif; ?>
</div>
<?php
@@ -445,7 +441,6 @@ if (!function_exists('aksrt_comments_render_form')) :
function aksrt_comments_render_form(array $ctx): void {
$is_logged_in = !empty($ctx['is_logged_in']);
$current_user = $ctx['current_user'] ?? null;
$avatar_size = max(40, (int) $ctx['avatar_size']);
$require_name_email = (bool) get_option('require_name_email');
$comment_author = isset($_COOKIE['comment_author_' . COOKIEHASH])
? sanitize_text_field(wp_unslash((string) $_COOKIE['comment_author_' . COOKIEHASH]))
@@ -472,59 +467,62 @@ if (!function_exists('aksrt_comments_render_form')) :
echo aksrt_get_comment_virtual_input_html($placeholder, '#respond'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<div id="respond" class="comment-respond mobile-fixed">
<div id="respond" class="mobile-fixed">
<div class="fixed-body"></div>
<form id="commentform" class="comment-form" method="post" action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>">
<form id="commentform" method="post" action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>">
<div class="flex ac">
<div class="comt-title text-center flex0 mr10">
<?php if ($is_logged_in && $current_user instanceof WP_User) : ?>
<div class="comt-avatar mb10">
<?php echo get_avatar($current_user->ID, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo get_avatar($current_user->ID, 60, '', '', ['class' => 'avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<p class="text-ellipsis muted-2-color"><?php echo esc_html($current_user->display_name); ?></p>
<?php else : ?>
<div class="comt-avatar mb10">
<?php echo get_avatar(0, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo get_avatar(0, 60, '', '', ['class' => 'avatar']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<p data-toggle-class="open" data-target="#comment-user-info" title="<?php esc_attr_e('填写用户信息', 'aksrt'); ?>">
<p data-toggle-class="open" data-target="#comment-user-info" data-toggle="tooltip" title="<?php esc_attr_e('填写用户信息', 'aksrt'); ?>">
<?php echo $comment_author !== '' ? esc_html($comment_author) : esc_html__('昵称', 'aksrt'); ?>
</p>
<?php endif; ?>
</div>
<div class="comt-box grow1">
<div class="action-text mb10 em09 muted-2-color" id="comment-reply-info" style="display:none;"></div>
<textarea placeholder="<?php echo esc_attr($placeholder); ?>" autoheight="true" maxheight="188" class="form-control grin comt-textarea" name="comment" id="comment" cols="100%" rows="4" tabindex="1" required></textarea>
<textarea placeholder="<?php echo esc_attr($placeholder); ?>" autoheight="true" maxheight="188" class="form-control grin" name="comment" id="comment" cols="100%" rows="4" tabindex="1"></textarea>
<?php if (function_exists('aksrt_is_captcha_enabled_for') && function_exists('aksrt_render_captcha') && aksrt_is_captcha_enabled_for('comment')) : ?>
<div class="comment-form__captcha" style="width:230px;margin-top:8px;"><?php aksrt_render_captcha('comment'); ?></div>
<div style="width: 230px; margin-top: 8px;"><?php aksrt_render_captcha('comment'); ?></div>
<?php endif; ?>
<div class="comt-ctrl relative">
<div class="comt-tips">
<?php comment_id_fields(); ?>
<?php wp_nonce_field('aksrt_comment_nonce', '_aksrt_comment_nonce'); ?>
<input type="hidden" name="comment_post_ID" value="<?php echo esc_attr((string) get_the_ID()); ?>">
<?php do_action('comment_form', get_the_ID()); ?>
</div>
<div class="comt-tips-right pull-right">
<a class="but c-red" id="cancel-comment-reply-link" href="javascript:;" style="display:none;"><?php esc_html_e('取消', 'aksrt'); ?></a>
<button class="but c-blue pw-1em input-expand-submit comment-send comment-form__submit" name="submit" id="submit" type="submit" tabindex="5"><?php echo esc_html((string) $ctx['submit_text']); ?></button>
<button class="but c-blue pw-1em input-expand-submit comment-send" name="submit" id="submit" type="submit" tabindex="5"><?php echo esc_html((string) $ctx['submit_text']); ?></button>
</div>
<div class="comt-tips-left">
<?php if (!$is_logged_in) :
$req_mark = $require_name_email ? '(' . __('必填', 'aksrt') . ')' : '';
?>
<span class="dropup relative" id="comment-user-info"<?php echo $require_name_email ? ' require_name_email="true"' : ''; ?>>
<a class="but mr6" data-toggle-class="open" data-target="#comment-user-info" href="javascript:;"><i class="fa fas fa-fw fa-user" aria-hidden="true"></i><span class="hide-sm"><?php esc_html_e('昵称', 'aksrt'); ?></span></a>
<a class="but mr6" data-toggle-class="open" data-target="#comment-user-info" href="javascript:;"><i class="fa fa-fw fa-user" aria-hidden="true"></i><span class="hide-sm"><?php esc_html_e('昵称', 'aksrt'); ?></span></a>
<div class="dropdown-menu box-body" style="width:250px;">
<div class="mb20">
<p><?php esc_html_e('请填写用户信息:', 'aksrt'); ?></p>
<ul>
<li class="line-form mb10">
<input type="text" name="author" class="line-form-input" tabindex="1" value="<?php echo esc_attr($comment_author); ?>" placeholder="" <?php echo $require_name_email ? 'required' : ''; ?> />
<input type="text" name="author" class="line-form-input" tabindex="1" value="<?php echo esc_attr($comment_author); ?>" placeholder="">
<div class="scale-placeholder"><?php echo esc_html__('昵称', 'aksrt') . esc_html($req_mark); ?></div>
<div class="abs-right muted-color"><i class="fa fa-fw fa-user" aria-hidden="true"></i></div>
<i class="line-form-line"></i>
</li>
<li class="line-form">
<input type="email" name="email" class="line-form-input" tabindex="2" value="<?php echo esc_attr($comment_email); ?>" placeholder="" <?php echo $require_name_email ? 'required' : ''; ?> />
<input type="text" name="email" class="line-form-input" tabindex="2" value="<?php echo esc_attr($comment_email); ?>" placeholder="">
<div class="scale-placeholder"><?php echo esc_html__('邮箱', 'aksrt') . esc_html($req_mark); ?></div>
<div class="abs-right muted-color"><i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i></div>
<i class="line-form-line"></i>
</li>
</ul>
</div>
@@ -532,14 +530,14 @@ if (!function_exists('aksrt_comments_render_form')) :
$oauth = is_array($ctx['oauth_providers'] ?? null) ? $ctx['oauth_providers'] : [];
if (!empty($oauth)) :
?>
<div class="comment-signarea__oauth flex ac" style="flex-wrap:wrap;gap:6px;">
<p class="social-login-wrap">
<?php foreach ($oauth as $provider) :
$label = (string) ($provider['label'] ?? $provider['name'] ?? '');
$url = (string) ($provider['url'] ?? '#');
?>
<a href="<?php echo esc_url($url); ?>" class="but hollow social-login-item"><?php echo esc_html($label); ?></a>
<a href="<?php echo esc_url($url); ?>" class="but hollow social-login-item ml10"><?php echo esc_html($label); ?></a>
<?php endforeach; ?>
</div>
</p>
<?php endif; ?>
</div>
</span>
@@ -571,8 +569,8 @@ if (!function_exists('aksrt_comments_render_filter')) :
$post_author_id = (int) $ctx['post_author_id'];
?>
<div class="comment-filter tab-nav-theme flex ac jsb" win-ajax-replace="comment-order-btn">
<ul class="list-inline comment-order-box comment-filter__tabs">
<li class="<?php echo $order === 'newest' ? 'active' : ''; ?>">
<ul class="list-inline comment-order-box" style="padding:0;">
<li class="mr6<?php echo $order === 'newest' ? ' active' : ''; ?>">
<a href="javascript:;" class="comment-orderby" data-order="newest" rel="nofollow"><?php esc_html_e('最新', 'aksrt'); ?></a>
</li>
<li class="<?php echo $order === 'hottest' ? 'active' : ''; ?>">
@@ -580,7 +578,7 @@ if (!function_exists('aksrt_comments_render_filter')) :
</li>
</ul>
<?php if ($post_author_id > 0) : ?>
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10 comment-author-filter<?php echo $author_only ? ' active' : ''; ?>" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10<?php echo $author_only ? ' active' : ''; ?>" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
<?php echo $author_only ? esc_html__('查看全部', 'aksrt') : esc_html__('只看作者', 'aksrt'); ?>
</a>
<?php endif; ?>
@@ -589,69 +587,34 @@ if (!function_exists('aksrt_comments_render_filter')) :
}
endif;
if (!function_exists('aksrt_comments_render_list')) :
if (!function_exists('aksrt_get_comment_ajax_loader')) :
/**
* Comment list + pagination.
* Hidden skeleton loader shown while AJAX pagination runs (zibll parity).
*
* @param array<string,mixed> $ctx Comments context.
* @return string
*/
function aksrt_comments_render_list(array $ctx): void {
?>
<div id="postcomments">
<ol class="commentlist list-unstyled" id="comment-list">
<?php
wp_list_comments([
'style' => 'ol',
'short_ping' => true,
'avatar_size' => (int) $ctx['avatar_size'],
'max_depth' => (int) $ctx['max_depth'],
'callback' => 'aksrt_comment_callback',
'reverse_top_level' => !empty($ctx['reverse_comments']),
'reverse_children' => !empty($ctx['reverse_comments']),
]);
?>
</ol>
</div>
<?php
if (function_exists('aksrt_render_comment_pagination')) {
aksrt_render_comment_pagination();
}
function aksrt_get_comment_ajax_loader(): string {
$item = '<ul class="list-inline flex"><div class="avatar-img placeholder radius"></div><li class="flex1"><div class="placeholder s1 mb6" style="width: 30%;"></div><div class="placeholder k2 mb10"></div><i class="placeholder s1 mb6"></i><i class="placeholder s1 mb6 ml10"></i></li></ul>';
return '<div style="display:none;" class="post_ajax_loader">' . $item . $item . $item . $item . '</div>';
}
endif;
if (!function_exists('aksrt_comments_render_empty')) :
/**
* Empty state.
* Empty state (zibll: zib_get_null('暂无评论内容', 40, 'null.svg', 'comment comment-null')).
*
* @param array<string,mixed> $ctx Comments context.
*/
function aksrt_comments_render_empty(array $ctx): void {
$closed = !comments_open();
$text = (string) ($ctx['empty_text'] ?? __('暂无评论内容', 'aksrt'));
if ($closed) {
$text = __('评论已关闭', 'aksrt');
} elseif (!empty($ctx['author_only'])) {
if (!empty($ctx['author_only'])) {
$text = __('暂无作者评论', 'aksrt');
}
$hint = $closed
? __('本文评论已关闭,仅可浏览历史留言', 'aksrt')
: (!empty($ctx['author_only'])
? __('切换到「查看全部」可浏览其他评论', 'aksrt')
: __('成为第一个评论者吧', 'aksrt'));
if (function_exists('aksrt_get_null')) {
echo aksrt_get_null($text, 40, 'null.svg', 'comment comment-null comments-empty', 220); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<p class="comments-empty__hint muted-3-color em09 text-center" style="margin-top:-20px;">' . esc_html($hint) . '</p>';
echo aksrt_get_null($text, 40, 'null.svg', 'comment comment-null', 280); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
?>
<div class="comments-empty post_ajax_loader">
<div class="comments-empty__icon">
<i class="fa fas fa-comment-dots"></i>
</div>
<p class="comments-empty__text"><?php echo esc_html($text); ?></p>
<p class="comments-empty__hint"><?php echo esc_html($hint); ?></p>
</div>
<?php
echo '<div class="text-center comment comment-null" style="padding:40px 0;"><p class="em09 muted-3-color separator">' . esc_html($text) . '</p></div>';
}
endif;
@@ -676,17 +639,15 @@ if (!function_exists('aksrt_comments_render')) :
set_query_var('aksrt_comment_order', (string) $ctx['order']);
set_query_var('aksrt_comment_settings', $ctx['settings']);
$avatar_size = max(45, (int) $ctx['avatar_size']);
$count = (int) $ctx['comment_count'];
$count_label = $count > 0
? sprintf(__('共%s条', 'aksrt'), (string) $count)
: __('抢沙发', 'aksrt');
?>
<div class="theme-box" id="comments" style="<?php echo esc_attr('--aksrt-comment-avatar:' . $avatar_size . 'px;--aksrt-comment-avatar-form:' . $avatar_size . 'px;--aksrt-comment-indent:86px;'); ?>">
<div class="theme-box" id="comments">
<div class="box-body notop">
<div class="title-theme">
<?php echo esc_html((string) $ctx['title_text']); ?>
<small class="comments-count"><?php echo esc_html($count_label); ?></small>
<div class="title-theme"><?php echo esc_html((string) $ctx['title_text']); ?>
<?php echo '<small>' . esc_html($count_label) . '</small>'; ?>
</div>
</div>
@@ -699,26 +660,49 @@ if (!function_exists('aksrt_comments_render')) :
<?php endif; ?>
<?php endif; ?>
<div id="postcomments">
<ol class="commentlist list-unstyled">
<?php
$show_filter = !empty($ctx['corderby_enabled']) && ($count > 0 || !empty($ctx['author_only']) || have_comments());
if ($show_filter) {
aksrt_comments_render_filter($ctx);
}
if (have_comments()) {
aksrt_comments_render_list_items($ctx);
} elseif (!post_password_required()) {
aksrt_comments_render_empty($ctx);
echo '<div class="pagenav hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
?>
<?php if (have_comments()) : ?>
<?php aksrt_comments_render_list($ctx); ?>
<?php elseif (!post_password_required()) : ?>
<?php aksrt_comments_render_empty($ctx); ?>
<div class="pagenav hide"><div class="next-page ajax-next"><a href="#"></a></div></div>
<?php endif; ?>
<?php if (!comments_open() && $count && post_type_supports(get_post_type(), 'comments')) : ?>
<p class="comments-closed separator text-muted em12"><?php esc_html_e('评论已关闭。', 'aksrt'); ?></p>
<?php endif; ?>
</ol>
</div>
</div>
</div>
<?php
}
endif;
if (!function_exists('aksrt_comments_render_list_items')) :
/**
* Render just the comment items + loader + pagination (inside ol.commentlist).
*
* @param array<string,mixed> $ctx Comments context.
*/
function aksrt_comments_render_list_items(array $ctx): void {
wp_list_comments([
'type' => 'comment',
'style' => 'ol',
'avatar_size' => (int) $ctx['avatar_size'],
'max_depth' => (int) $ctx['max_depth'],
'callback' => 'aksrt_comment_callback',
'reverse_top_level' => !empty($ctx['reverse_comments']),
'reverse_children' => !empty($ctx['reverse_comments']),
]);
echo aksrt_get_comment_ajax_loader(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (function_exists('aksrt_render_comment_pagination')) {
aksrt_render_comment_pagination();
}
}
endif;
+11
View File
@@ -114,6 +114,17 @@ if (!function_exists('aksrt_render_comment_content')) :
['<pre class="comment-code"><code>', '</code></pre>'],
(string) $rendered
);
// [link=url target=_blank]text[/link] / [link=url]text[/link]
$rendered = preg_replace(
'/\[link=(https?:\/\/[^\s\[\]]+) target=_blank\]([^\[]*)\[\/link\]/i',
'<a href="$1" target="_blank" rel="nofollow">$2</a>',
(string) $rendered
);
$rendered = preg_replace(
'/\[link=(https?:\/\/[^\s\[\]]+)\]([^\[]*)\[\/link\]/i',
'<a href="$1" rel="nofollow">$2</a>',
(string) $rendered
);
// [g=key]
$rendered = preg_replace_callback(
'/\[g=([^\]]+)\]/i',
+74 -54
View File
@@ -1,6 +1,9 @@
<?php
/**
* Comment form partials — virtual-input, expand toolbar, respond shell helpers.
* Comment form partials — virtual-input + expand toolbar (Zibll DOM contract).
*
* Mirrors zib_get_respond_mobile() and zib_get_input_expand_but()
* from zibll/inc/functions.
*
* @package AKSRT
*/
@@ -9,18 +12,19 @@ defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_comment_virtual_input_html')) :
/**
* Mobile virtual input bar that opens FixedInput #respond.
* Mobile virtual input bar (zibll: zib_get_respond_mobile).
*
* @param string $placeholder Placeholder text.
* @param string $target CSS selector for FixedInput target.
* @param string $target CSS selector for the FixedInput target.
* @return string
*/
function aksrt_get_comment_virtual_input_html(string $placeholder = '', string $target = '#respond'): string {
$user_id = get_current_user_id();
$avatar = get_avatar($user_id, 36, '', '', ['class' => 'avatar-img']);
$avatar = get_avatar($user_id, 36, '', '', ['class' => 'avatar']);
if ($placeholder === '') {
$placeholder = __('说点什么吧...', 'aksrt');
}
$html = '<div class="flex ac jsb virtual-input hide-pc" fixed-input="' . esc_attr($target) . '">';
$html = '<div class="flex ac jsb virtual-input" fixed-input="' . esc_attr($target) . '">';
$html .= '<div class="flex flex1 ac">';
$html .= $avatar;
$html .= '<div class="text-ellipsis simulation mr10">' . esc_html($placeholder) . '</div>';
@@ -33,46 +37,27 @@ endif;
if (!function_exists('aksrt_get_input_expand_but')) :
/**
* zibll-style expand toolbar button + dropdown.
* zibll-style expand toolbar button + dropdown (zibll: zib_get_input_expand_but).
*
* @param string $type smilie|code|image|quick|upload.
* @param mixed $arg1 Type-specific.
* @param array<string,mixed> $ctx Optional context (upload enabled, phrases).
* @param string $type smilie|code|image|quick|link.
* @param mixed $arg1 Type-specific arg.
* @param array<string,mixed> $ctx Optional context (upload, phrases).
* @return string
*/
function aksrt_get_input_expand_but(string $type = 'smilie', $arg1 = null, array $ctx = []): string {
$but = '';
$dropdown = '';
switch ($type) {
case 'smilie':
$map = function_exists('aksrt_comment_smilie_map') ? aksrt_comment_smilie_map() : [];
$icons = '';
foreach ($map as $key => $url) {
$icons .= '<a class="smilie-icon" href="javascript:;" data-smilie="' . esc_attr((string) $key) . '"><img' . aksrt_get_lazy_attr('lazy_comment', (string) $url, '', aksrt_get_lazy_thumb('sm')) . ' alt="[' . esc_attr((string) $key) . ']" width="28" height="28"></a>';
}
$but = '<a class="but btn-input-expand input-smilie mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-smilie-wrap" title="' . esc_attr__('表情', 'aksrt') . '"><i class="fa fas fa-fw fa-smile" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('表情', 'aksrt') . '</span></a>';
$drop = '<div class="dropdown-smilie-wrap relative dropup"><div class="dropdown-smilie scroll-y mini-scrollbar dropdown-menu">' . $icons . '</div></div>';
return '<span class="dropup relative input-expand-wrap input-expand-smilie">' . $but . $drop . '</span>';
case 'code':
$but = '<a class="but btn-input-expand input-code mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-code" title="' . esc_attr__('代码', 'aksrt') . '"><i class="fa fas fa-fw fa-code" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('代码', 'aksrt') . '</span></a>';
$drop = '<div class="dropdown-code dropdown-menu box-body" style="width:280px;">'
. '<p class="muted-2-color em09 mb6">' . esc_html__('插入代码块', 'aksrt') . '</p>'
. '<textarea class="form-control mb6" rows="4" placeholder="' . esc_attr__('粘贴代码…', 'aksrt') . '"></textarea>'
. '<button type="button" class="but c-blue pw-1em input-code-submit">' . esc_html__('插入', 'aksrt') . '</button>'
. '</div>';
return '<span class="dropup relative input-expand-wrap input-expand-code">' . $but . $drop . '</span>';
case 'image':
$upload = !empty($arg1) || !empty($ctx['upload']);
$but = '<a class="but btn-input-expand input-image mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-image" title="' . esc_attr__('图片', 'aksrt') . '"><i class="fa fas fa-fw fa-image" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('图片', 'aksrt') . '</span></a>';
$upload_btn = $upload
? '<button type="button" class="but hollow mr6 input-image-upload" data-toolbar-action="upload-image">' . esc_html__('上传', 'aksrt') . '</button>'
: '';
$drop = '<div class="dropdown-image dropdown-menu box-body" style="width:280px;">'
. '<p class="muted-2-color em09 mb6">' . esc_html__('插入图片地址', 'aksrt') . '</p>'
. '<input type="url" class="form-control mb6 input-image-url" placeholder="https://" />'
. $upload_btn
. '<button type="button" class="but c-blue pw-1em input-image-submit">' . esc_html__('插入', 'aksrt') . '</button>'
. '</div>';
return '<span class="dropup relative input-expand-wrap input-expand-image">' . $but . $drop . '</span>';
case 'link':
$but = '<a class="but btn-input-expand input-link mr6" href="javascript:;"><i class="fa fa-fw fa-link" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('链接', 'aksrt') . '</span></a>';
$dropdown = '<div class="dropdown-link">';
$dropdown .= '<p>' . esc_html__('请输入链接:', 'aksrt') . '</p>';
$dropdown .= '<p><textarea rows="2" tabindex="1" class="form-control input-textarea link-textarea" style="height:55px;" placeholder="https://..."></textarea></p>';
$dropdown .= '<p><input type="text" class="form-control link-text" placeholder="' . esc_attr__('链接文本', 'aksrt') . '"></p>';
$dropdown .= '<div class="text-right"><a type="submit" class="but c-blue pw-1em input-link-submit" href="javascript:;">' . esc_html__('确认', 'aksrt') . '</a></div>';
$dropdown .= '</div>';
break;
case 'quick':
$phrases = [];
@@ -81,30 +66,65 @@ if (!function_exists('aksrt_get_input_expand_but')) :
} elseif (!empty($ctx['quick_phrases']) && is_array($ctx['quick_phrases'])) {
$phrases = $ctx['quick_phrases'];
}
if (empty($phrases)) {
$phrases = [
__('不错,学习了', 'aksrt'),
__('感谢分享', 'aksrt'),
__('期待更新', 'aksrt'),
];
}
$items = '';
$sys_lists = '';
foreach ($phrases as $p) {
$items .= '<a class="quick-reply-item" href="javascript:;" data-text="' . esc_attr((string) $p) . '">' . esc_html((string) $p) . '</a>';
$sys_lists .= '<div class="quick-reply-item">' . esc_html((string) $p) . '</div>';
}
$but = '<a class="but btn-input-expand input-quick mr6" href="javascript:;" data-toggle-class="open" data-target=".dropdown-quick" title="' . esc_attr__('快捷', 'aksrt') . '"><i class="fa fas fa-fw fa-bolt" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('快捷', 'aksrt') . '</span></a>';
$drop = '<div class="dropdown-quick dropdown-menu box-body scroll-y mini-scrollbar" style="max-height:220px;min-width:160px;">' . $items . '</div>';
return '<span class="dropup relative input-expand-wrap input-expand-quick">' . $but . $drop . '</span>';
if ($sys_lists === '') {
$sys_lists = '<div class="text-center muted-2-color" style="margin: 60px 0;">' . esc_html__('系统暂无快捷回复', 'aksrt') . '</div>';
}
$but = '<a class="but btn-input-expand input-quick mr6" href="javascript:;"><i class="fa fa-fw fa-bolt" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('快捷回复', 'aksrt') . '</span></a>';
$dropdown = '<div class="relative dropdown-quick-often"><div class="quick-often-box scroll-y mini-scrollbar">' . $sys_lists . '</div></div>';
break;
case 'smilie':
$map = function_exists('aksrt_comment_smilie_map') ? aksrt_comment_smilie_map() : [];
$smilie_icon = '';
foreach ($map as $key => $url) {
$smilie_icon .= '<a class="smilie-icon" href="javascript:;" data-smilie="' . esc_attr((string) $key) . '"><img src="' . esc_url((string) $url) . '" alt="[' . esc_attr((string) $key) . ']" /></a>';
}
$but = '<a class="but btn-input-expand input-smilie mr6" href="javascript:;"><i class="fa fa-fw fa-smile-o" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('表情', 'aksrt') . '</span></a>';
$dropdown = '<div class="dropdown-smilie scroll-y mini-scrollbar">' . $smilie_icon . '</div>';
break;
case 'code':
$but = '<a class="but btn-input-expand input-code mr6" href="javascript:;"><i class="fa fa-fw fa-code" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('代码', 'aksrt') . '</span></a>';
$dropdown = '<div class="dropdown-code">';
$dropdown .= '<p>' . esc_html__('请输入代码:', 'aksrt') . '</p>';
$dropdown .= '<p><textarea rows="6" tabindex="1" class="form-control input-textarea" placeholder="' . esc_attr__('在此处粘贴或输入代码', 'aksrt') . '"></textarea></p>';
$dropdown .= '<div class="text-right"><a type="submit" class="but c-blue pw-1em input-code-submit" href="javascript:;">' . esc_html__('确认', 'aksrt') . '</a></div>';
$dropdown .= '</div>';
break;
case 'image':
$upload = !empty($arg1) || !empty($ctx['upload']);
$but = '<a class="but btn-input-expand input-image mr6" href="javascript:;"><i class="fa fa-fw fa-image" aria-hidden="true"></i><span class="hide-sm">' . esc_html__('图片', 'aksrt') . '</span></a>';
$dropdown = '<div class="dropdown-image">';
$dropdown .= '<p>' . esc_html__('请填写图片地址:', 'aksrt') . '</p>';
$dropdown .= '<p><textarea rows="2" tabindex="1" class="form-control input-textarea input-image-url" style="height:95px;" placeholder="http://..."></textarea></p>';
$dropdown .= '<div class="text-right">';
if ($upload) {
$dropdown .= '<a class="but c-yellow mr10 pw-1em input-image-upload" href="javascript:;">' . esc_html__('上传图片', 'aksrt') . '</a>';
}
$dropdown .= '<a type="submit" class="but c-blue pw-1em input-image-submit" href="javascript:;">' . esc_html__('确认', 'aksrt') . '</a>';
$dropdown .= '</div>';
$dropdown .= '</div>';
break;
default:
return '';
}
$con = $but . '<div class="dropdown-menu">' . $dropdown . '</div>';
return '<span class="dropup relative ' . esc_attr($type) . '">' . $con . '</span>';
}
endif;
if (!function_exists('aksrt_render_comment_expand_toolbar')) :
/**
* Render left-side expand tools based on settings.
* Render left-side expand tools based on settings (zibll order: smilie/code/image/link/quick).
*
* @param array<string,mixed> $ctx Comments context.
*/
+136 -94
View File
@@ -1,6 +1,9 @@
<?php
/**
* Comment list item renderer — zibll DOM contract.
* Comment list item renderer — Zibll DOM contract (1:1).
*
* Mirrors zib_comments_list / zib_get_comment_header / zib_get_comment_footer
* from zibll/inc/functions/zib-comments-list.php.
*
* @package AKSRT
*/
@@ -9,7 +12,11 @@ defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_comment_like_html')) :
/**
* Header like control (zibll places like in comment-header).
* Header like control (zibll: zib_get_comment_like).
*
* @param WP_Comment $comment Comment.
* @param bool $enabled Like enabled.
* @return string
*/
function aksrt_get_comment_like_html($comment, bool $enabled = true): string {
if (!$enabled) {
@@ -19,29 +26,59 @@ if (!function_exists('aksrt_get_comment_like_html')) :
if ($comment_id <= 0) {
return '';
}
$like_count = function_exists('aksrt_get_comment_likes') ? aksrt_get_comment_likes($comment_id) : 0;
$like_count = function_exists('aksrt_get_comment_likes') ? (int) aksrt_get_comment_likes($comment_id) : 0;
$is_liked = function_exists('aksrt_is_comment_liked') && aksrt_is_comment_liked($comment_id);
$classes = 'action action-comment-like muted-2-color flex0 ml10 aksrt-comment__like' . ($is_liked ? ' is-active' : '');
$count_attr = $like_count > 0 ? (string) $like_count : '';
$classes = 'action action-comment-like muted-2-color flex0 ml10' . ($is_liked ? ' actived' : '');
return sprintf(
'<a href="javascript:;" class="%1$s" data-comment-action="like" data-active="%2$s" data-pid="%3$d" aria-pressed="%4$s"><i class="%5$s fa-thumbs-up" aria-hidden="true"></i><count class="aksrt-comment__like-count"%6$s>%7$s</count></a>',
'<a href="javascript:;" data-comment-action="like" data-action="comment_like" class="%1$s" data-pid="%2$d" data-active="%3$s" aria-pressed="%4$s"><i class="fa fa-thumbs-up mr3" aria-hidden="true"></i><count>%5$s</count></a>',
esc_attr($classes),
$is_liked ? '1' : '0',
$comment_id,
$is_liked ? '1' : '0',
$is_liked ? 'true' : 'false',
$is_liked ? 'fa' : 'fa',
$like_count > 0 ? '' : ' hidden',
esc_html($count_attr)
esc_html((string) $like_count)
);
}
endif;
if (!function_exists('aksrt_get_comment_avatar_box')) :
/**
* Comment avatar box (zibll: zib_get_comment_avatar_box).
*
* @param WP_Comment $comment Comment.
* @param string $class Classes for the avatar wrapper.
* @param bool $link Wrap in a link when a target URL exists.
* @return string
*/
function aksrt_get_comment_avatar_box(WP_Comment $comment, string $class = 'avatar-img comt-avatar', bool $link = true): string {
$user_id = (int) $comment->user_id;
$size = 45;
$avatar = $user_id > 0
? get_avatar($user_id, $size, '', '', ['class' => 'avatar'])
: get_avatar($comment->comment_author_email, $size, '', '', ['class' => 'avatar']);
$html = '<span class="' . esc_attr($class) . '">' . $avatar . '</span>';
if (!$link) {
return $html;
}
if ($user_id > 0) {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
return '<a href="' . esc_url($url) . '">' . $html . '</a>';
}
if (!empty($comment->comment_author_url)) {
return '<a rel="nofollow" href="' . esc_url($comment->comment_author_url) . '">' . $html . '</a>';
}
return $html;
}
endif;
if (!function_exists('aksrt_get_comments_user_name_html')) :
/**
* Name row: topping + author link + identity + author/hot badges.
* Name row (zibll: zib_get_comments_user_name) — topping + author + badges.
*
* @param WP_Comment $comment Comment.
* @param array<string,mixed> $settings Display settings.
* @return string
*/
function aksrt_get_comments_user_name_html(WP_Comment $comment, array $settings = []): string {
$user_id = (int) $comment->user_id;
@@ -51,55 +88,65 @@ if (!function_exists('aksrt_get_comments_user_name_html')) :
$topping = '';
if ($is_pinned) {
$topping = '<badge class="badge-topping badge-topping jb-blue mr6 flex0 aksrt-comment__badge aksrt-comment__badge--pinned" title="' . esc_attr__('置顶评论', 'aksrt') . '"><i class="fa fas fa-thumbtack" aria-hidden="true"></i></badge>';
$topping = '<badge class="badge-topping jb-blue mr6 flex0" title="' . esc_attr__('置顶评论', 'aksrt') . '" data-toggle="tooltip"><i class="fa fa-thumbtack" aria-hidden="true"></i></badge>';
}
$user_name = '';
if ($user_id > 0) {
$user = get_userdata($user_id);
$display = $user instanceof WP_User ? $user->display_name : get_comment_author($comment);
if ($user instanceof WP_User) {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$name = '<a class="text-ellipsis font-bold aksrt-comment__author" href="' . esc_url($url) . '">' . esc_html($display) . '</a>';
$user_name = '<a class="text-ellipsis font-bold" href="' . esc_url($url) . '">' . esc_html($user->display_name) . '</a>';
if (function_exists('aksrt_render_user_identity_chips')) {
$name .= aksrt_render_user_identity_chips($user_id, 'comment');
$user_name .= aksrt_render_user_identity_chips($user_id, 'comment');
}
} else {
$name = '<b class="mr6 aksrt-comment__author">' . esc_html(get_comment_author($comment)) . '</b>';
}
} elseif (!empty($comment->comment_author_url)) {
$user_name = '<a class="text-ellipsis font-bold" rel="nofollow" href="' . esc_url($comment->comment_author_url) . '">' . esc_html($comment->comment_author) . '</a>';
}
$badges = '';
$user_name = $user_name !== '' ? $user_name : '<b class="mr6 text-ellipsis">' . esc_html($comment->comment_author) . '</b>';
$badge = '';
if ($show_author_tag && $is_post_author) {
$badges .= '<span class="badg c-green badg-sm flex0 ml3 aksrt-comment__badge">' . esc_html__('作者', 'aksrt') . '</span>';
}
if (function_exists('aksrt_is_blog_comment_hot') && aksrt_is_blog_comment_hot($comment)) {
$badges .= '<span class="badg c-yellow badg-sm flex0 ml3 aksrt-comment__badge aksrt-comment__badge--hot"><i class="fa fas fa-fire" aria-hidden="true"></i> ' . esc_html__('神评', 'aksrt') . '</span>';
$badge .= '<span class="badg c-green badg-sm flex0 ml3">' . esc_html__('作者', 'aksrt') . '</span>';
}
return '<name class="flex ac flex1">' . $topping . $name . $badges . '</name>';
return '<name class="flex ac flex1">' . $topping . $user_name . $badge . '</name>';
}
endif;
if (!function_exists('aksrt_get_comment_header_html')) :
/**
* Comment header (zibll: zib_get_comment_header).
*
* @param WP_Comment $comment Comment.
* @param array<string,mixed> $settings Settings.
* @return string
*/
function aksrt_get_comment_header_html(WP_Comment $comment, array $settings = []): string {
$avatar_size = max(24, (int) ($settings['avatar_size'] ?? 45));
$like_enabled = !isset($settings['like_enabled']) || !empty($settings['like_enabled']);
$avatar = get_avatar($comment, $avatar_size, '', '', ['class' => 'avatar-img comt-avatar']);
$avatar = aksrt_get_comment_avatar_box($comment, 'avatar-img comt-avatar');
$name = aksrt_get_comments_user_name_html($comment, $settings);
$like = aksrt_get_comment_like_html($comment, $like_enabled);
$inner = '<div class="author-box flex ac">' . $avatar . $name . $like . '</div>';
return '<div class="comment-header mb10 aksrt-comment__header">' . $inner . '</div>';
$html = '<div class="author-box flex ac">';
$html .= $avatar;
$html .= $name;
$html .= $like;
$html .= '</div>';
return '<div class="comment-header mb10">' . $html . '</div>';
}
endif;
if (!function_exists('aksrt_get_comments_action_dropdown_html')) :
/**
* More menu: pin / edit / approve / delete (REST-driven via data-comment-action).
* More-actions menu (zibll: zib_get_comments_action_list) — pin / edit / approve / delete.
*
* @param WP_Comment $comment Comment.
* @param int $depth Depth.
* @return string
*/
function aksrt_get_comments_action_dropdown_html(WP_Comment $comment, int $depth = 0): string {
$perms = function_exists('aksrt_comment_action_permissions')
@@ -107,95 +154,86 @@ if (!function_exists('aksrt_get_comments_action_dropdown_html')) :
: ['can_edit' => false, 'can_delete' => false, 'can_moderate' => false, 'can_pin' => false];
$is_pinned = function_exists('aksrt_is_comment_pinned') && aksrt_is_comment_pinned($comment);
$is_pending = '0' === (string) $comment->comment_approved;
$is_spam = 'spam' === (string) $comment->comment_approved;
$lists = '';
if (!empty($perms['can_pin']) && (int) $comment->comment_parent === 0) {
$label = $is_pinned ? __('取消置顶', 'aksrt') : __('设置置顶', 'aksrt');
$lists .= '<li><a href="javascript:;" class="comment-topping-link" data-comment-action="pin" data-pinned="' . ($is_pinned ? '1' : '0') . '"><i class="fa fas fa-thumbtack fa-fw mr10" aria-hidden="true"></i>' . esc_html($label) . '</a></li>';
$lists .= '<li><a class="comment-topping wp-ajax-submit" data-comment-action="pin" data-pinned="' . ($is_pinned ? '1' : '0') . '" href="javascript:;"><i class="fa fa-thumbtack mr10 fa-fw" aria-hidden="true"></i>' . esc_html($label) . '</a></li>';
}
if (!empty($perms['can_edit'])) {
$lists .= '<li><a href="javascript:;" class="comment-edit-link" data-comment-action="edit" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '"><i class="fa fas fa-edit fa-fw mr10" aria-hidden="true"></i>' . esc_html__('编辑', 'aksrt') . '</a></li>';
$lists .= '<li><a class="comment-edit-link" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa fa-edit mr10 fa-fw" aria-hidden="true"></i>' . esc_html__('编辑', 'aksrt') . '</a></li>';
}
if (!empty($perms['can_moderate'])) {
if ($is_pending) {
$lists .= '<li><a href="javascript:;" class="comment-approve-link approve" data-comment-action="approve"><i class="fa fas fa-check fa-fw mr10" aria-hidden="true"></i><text>' . esc_html__('批准', 'aksrt') . '</text></a></li>';
} elseif (!$is_spam) {
$lists .= '<li><a href="javascript:;" class="comment-approve-link unapprove" data-comment-action="hold"><i class="fa fas fa-eye-slash fa-fw mr10" aria-hidden="true"></i><text>' . esc_html__('驳回', 'aksrt') . '</text></a></li>';
}
$lists .= '<li><a href="javascript:;" class="comment-spam-link" data-comment-action="spam"><i class="fa fas fa-ban fa-fw mr10" aria-hidden="true"></i>' . esc_html__('垃圾', 'aksrt') . '</a></li>';
$text = $is_pending ? __('批准', 'aksrt') : __('驳回', 'aksrt');
$class = $is_pending ? 'approve' : 'unapprove';
$action = $is_pending ? 'approve' : 'hold';
$lists .= '<li><a class="comment-approve-link ' . $class . '" data-comment-action="' . $action . '" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa fa-check mr10 fa-fw" aria-hidden="true"></i><text>' . esc_html($text) . '</text></a></li>';
}
if (!empty($perms['can_delete'])) {
$lists .= '<li><a href="javascript:;" class="comment-trash-link c-red" data-comment-action="delete" data-commentid="' . (int) $comment->comment_ID . '"><i class="fa fas fa-trash-alt fa-fw mr10" aria-hidden="true"></i>' . esc_html__('删除', 'aksrt') . '</a></li>';
$lists .= '<li><a class="comment-trash-link c-red" data-comment-action="delete" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa fa-trash-o mr10 fa-fw" aria-hidden="true"></i>' . esc_html__('删除', 'aksrt') . '</a></li>';
}
if ($lists === '') {
return '';
}
return '<span class="dropdown drop-fixed-sm padding-6 comment-action-dropdown">'
. '<a href="javascript:;" class="muted-color padding-6" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="' . esc_attr__('更多操作', 'aksrt') . '"><i class="fa fas fa-ellipsis-h" aria-hidden="true"></i></a>'
. '<ul class="dropdown-menu">' . $lists . '</ul>'
. '</span>';
$icon_a = '<a href="javascript:;" class="muted-color padding-6" data-toggle="dropdown"><i class="fa fa-ellipsis-h" aria-hidden="true"></i></a>';
return '<span class="dropdown drop-fixed-sm padding-6">' . $icon_a . '<ul class="dropdown-menu">' . $lists . '</ul></span>';
}
endif;
if (!function_exists('aksrt_get_comment_footer_html')) :
/**
* comt-meta: time-ago · city · @parent · approve · reply · dropdown.
* comt-meta row (zibll: zib_get_comment_footer).
*
* @param WP_Comment $comment Comment.
* @param array $args wp_list_comments args.
* @param int $depth Depth.
* @param array<string,mixed> $settings Settings.
* @return string
*/
function aksrt_get_comment_footer_html(WP_Comment $comment, array $args, int $depth, array $settings = []): string {
$is_pending = '0' === (string) $comment->comment_approved;
// Time ago.
$absolute = date_i18n('Y-m-d H:i:s', strtotime((string) $comment->comment_date));
$badg_approve = '';
if ($is_pending) {
$badg_approve = '<span class="badg c-red badg-sm">' . esc_html__('待审核', 'aksrt') . '</span>';
}
$badg_approve = '<span class="badge-approve">' . $badg_approve . '</span>';
$absolute = date_i18n(__('Y年m月d日 H:i:s', 'aksrt'), strtotime((string) $comment->comment_date));
$ago = function_exists('aksrt_time_ago')
? aksrt_time_ago((string) $comment->comment_date)
: human_time_diff(strtotime((string) $comment->comment_date), current_time('timestamp')) . __('前', 'aksrt');
$time_html = '<span class="comt-author" title="' . esc_attr($absolute) . '">' . esc_html($ago) . '</span>';
// City.
$city_html = function_exists('aksrt_get_comment_city_badge_html')
? aksrt_get_comment_city_badge_html($comment)
: '';
// @ parent.
$parent_html = '';
if ((int) $comment->comment_parent > 0) {
$parent_author = get_comment_author((int) $comment->comment_parent);
$parent_html = '<span>@<a rel="nofollow" class="url" href="javascript:;" data-scroll-to="#comment-' . (int) $comment->comment_parent . '">' . esc_html($parent_author) . '</a></span>';
}
// Pending badge.
$approve_html = '<span class="badge-approve">';
if ($is_pending) {
$approve_html .= '<span class="badg c-red badg-sm">' . esc_html__('待审核', 'aksrt') . '</span>';
}
$approve_html .= '</span>';
// Reply.
$reply_html = '';
if (!$is_pending && $depth > 0) {
$max_depth = (int) ($args['max_depth'] ?? get_option('thread_comments_depth', 5));
$link = get_comment_reply_link(
array_merge(
$args,
[
'add_below' => 'div-comment',
'reply_text' => __('回复', 'aksrt'),
'login_text' => __('回复', 'aksrt'),
'depth' => $depth,
'max_depth' => $max_depth,
]
),
],
$comment
);
if ($link) {
// Ensure data-commentid for moveForm path.
// 去掉 WP 内联 onclick(addComment.moveForm),回复由 comment-move-form.js 事件委托处理。
$link = preg_replace('/ onclick="[^"]*"/i', '', $link);
if (strpos($link, 'data-commentid') === false) {
$link = str_replace(
'comment-reply-link',
@@ -208,15 +246,49 @@ if (!function_exists('aksrt_get_comment_footer_html')) :
}
$actions = aksrt_get_comments_action_dropdown_html($comment, $depth);
$info = $time_html . $city_html . $parent_html . $approve_html . $reply_html;
$info = $time_html . $city_html . $parent_html . $badg_approve . $reply_html;
return '<div class="comt-meta muted-2-color aksrt-comment__actions">' . $info . $actions . '</div>';
return '<div class="comt-meta muted-2-color">' . $info . $actions . '</div>';
}
endif;
if (!function_exists('aksrt_get_comments_list_html')) :
/**
* Single comment item HTML (zibll: zib_get_comments_list).
*
* @param WP_Comment $comment Comment.
* @param array $args Args.
* @param int $depth Depth.
* @param array $settings Settings.
* @return string
*/
function aksrt_get_comments_list_html(WP_Comment $comment, array $args = [], int $depth = 0, array $settings = []): string {
$comment_id = (int) $comment->comment_ID;
$con = function_exists('aksrt_render_comment_content')
? aksrt_render_comment_content((string) get_comment_text($comment))
: wp_kses_post((string) get_comment_text($comment));
$header = aksrt_get_comment_header_html($comment, $settings);
$footer = aksrt_get_comment_footer_html($comment, $args, $depth, $settings);
$html = '<li ' . comment_class('', $comment, null, false) . ' id="comment-' . esc_attr((string) $comment_id) . '">';
$html .= '<ul class="list-inline">';
$html .= '<li class="comt-main" id="div-comment-' . esc_attr((string) $comment_id) . '">';
$html .= $header;
$html .= '<div class="comment-footer">';
$html .= '<div class="mb10 comment-content" id="comment-content-' . esc_attr((string) $comment_id) . '">' . $con . '</div>';
$html .= $footer;
$html .= '</div>';
$html .= '</li>';
$html .= '</ul>';
return $html;
}
endif;
if (!function_exists('aksrt_comment_callback')) :
/**
* Canonical comment list item renderer (zibll structure).
* Canonical wp_list_comments callback (zibll: zib_comments_list).
*
* The closing </li> is emitted by the walker after children.
*
* @param WP_Comment $comment Comment.
* @param array $args Args.
@@ -230,37 +302,7 @@ if (!function_exists('aksrt_comment_callback')) :
$settings = \AKSRT\Settings\Settings::get_comment_display_settings();
}
$comment_id = (int) $comment->comment_ID;
$depth_class = 'aksrt-depth-' . min((int) ($settings['nested_depth'] ?? 3), (int) $depth);
$is_pinned = function_exists('aksrt_is_comment_pinned') && aksrt_is_comment_pinned($comment);
$is_pending = '0' === (string) $comment->comment_approved;
$li_classes = trim($depth_class . ($is_pinned ? ' aksrt-comment--pinned' : ''));
$header = aksrt_get_comment_header_html($comment, $settings);
$footer = aksrt_get_comment_footer_html($comment, $args, (int) $depth, $settings);
if ($is_pending) {
$body = '<p class="awaiting-moderation">' . esc_html__('您的评论正在等待审核。', 'aksrt') . '</p>';
} else {
$body = function_exists('aksrt_render_comment_content')
? aksrt_render_comment_content((string) get_comment_text($comment))
: wp_kses_post((string) get_comment_text($comment));
}
echo '<li ';
comment_class('aksrt-comment ' . $li_classes);
echo ' id="comment-' . esc_attr((string) $comment_id) . '" data-comment-id="' . esc_attr((string) $comment_id) . '" data-comment-status="' . esc_attr((string) $comment->comment_approved) . '">';
echo '<ul class="list-inline">';
echo '<li class="comt-main" id="div-comment-' . esc_attr((string) $comment_id) . '">';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- composed helpers escape.
echo $header;
echo '<div class="comment-footer">';
echo '<div class="mb10 comment-content aksrt-comment__text" id="comment-content-' . esc_attr((string) $comment_id) . '">' . $body . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $footer;
echo '</div>';
echo '</li>';
echo '</ul>';
// Closing </li> is emitted by Walker_Comment after children.
echo aksrt_get_comments_list_html($comment, is_array($args) ? $args : [], (int) $depth, $settings);
}
endif;
+71 -62
View File
@@ -491,75 +491,84 @@ endif;
if (!function_exists('aksrt_render_comment_pagination')) {
/**
* 渲染评论列表分页(根据后台配置选择模式)
* 渲染评论列表分页(zibll 标记:数字 pagenav / ajax_lists 加载更多)
*/
function aksrt_render_comment_pagination(): void {
$comment_pagination_mode = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_pagination_mode()
: 'numbered';
if ($comment_pagination_mode === 'numbered') {
$pagination_links = paginate_comments_links([
'echo' => false,
'prev_text' => __('上一页', 'aksrt'),
'next_text' => __('下一页', 'aksrt'),
'type' => 'plain',
]);
if (empty($pagination_links)) {
return;
}
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_display_settings()
: [];
$mode = (string) ($settings['pagination_mode'] ?? 'numbered');
global $wp_query;
$max_pages = get_comment_pages_count();
$current_page = max(1, get_query_var('cpage', 1));
$max_pages = (int) get_comment_pages_count();
if (empty($max_pages)) {
$max_pages = isset($wp_query->max_num_comment_pages) ? (int) $wp_query->max_num_comment_pages : 1;
}
$paged = (int) get_query_var('cpage', 0);
$paged = $paged > 0 ? $paged : 1;
if ($mode === 'ajax_lists') {
$next_page = $paged + 1;
if ($next_page > $max_pages || $max_pages <= 1) {
return;
}
$order = (string) get_query_var('aksrt_comment_order', 'newest');
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
$order = 'newest';
}
$author_only = isset($_GET['aksrt_author_only']) && (int) wp_unslash($_GET['aksrt_author_only']) === 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$ias = !empty($settings['paging_ajax_ias']);
$ias_max = (int) ($settings['paging_ajax_ias_max'] ?? 3);
$next = get_comments_pagenum_link($next_page, $max_pages);
$classes = 'theme-pagination ias-pagenav pagenav aksrt-comment-pagination';
$ias_attr = '';
if ($ias && ($paged <= $ias_max || !$ias_max)) {
$classes .= ' lazyload';
$ias_attr = ' lazyload-action="ias"';
}
printf(
'<div class="%1$s"%2$s data-mode="load_more" data-next-page="%3$d" data-max-pages="%4$d" data-post-id="%5$d" data-comment-order="%6$s" data-author-only="%7$s"><div class="order-ajax-next"><a href="%8$s" class="ias-btn" no-replace="true">%9$s</a></div></div>',
esc_attr($classes),
$ias_attr, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 固定字面量。
esc_attr((string) $next_page),
esc_attr((string) $max_pages),
esc_attr((string) get_the_ID()),
esc_attr($order),
$author_only ? '1' : '0',
esc_url($next),
esc_html__('加载更多', 'aksrt')
);
return;
}
// 数字分页(zibll:paginate_links + 跳页输入框)
$is_mobile = wp_is_mobile();
$args = [
'base' => add_query_arg('cpage', '%#%', get_permalink()),
'type' => 'array',
'mid_size' => $is_mobile ? 1 : 2,
'prev_text' => '<i class="fa fa-angle-left em12"></i><span class="hide-sm ml6">' . __('上一页', 'aksrt') . '</span>',
'next_text' => '<span class="hide-sm mr6">' . __('下一页', 'aksrt') . '</span><i class="fa fa-angle-right em12"></i>',
'total' => $max_pages,
'current' => $paged,
'add_fragment' => '#comments',
];
$array = paginate_links($args);
if (!$array) {
return;
}
echo '<div class="pagenav ajax-pag aksrt-pagination content-stream__footer-nav">';
echo wp_kses_post($pagination_links);
if ($max_pages > 8) {
echo '<span class="pag-jump">';
echo '<input type="number" min="1" max="' . esc_attr((string) $max_pages) . '" value="' . esc_attr((string) $current_page) . '" class="jump-input" data-max-pages="' . esc_attr((string) $max_pages) . '" />';
echo '<button type="button" class="ajax-next order-ajax-next" data-jump-comments="1">' . esc_html__('跳转', 'aksrt') . '</button>';
echo '</span>';
$array[] = '<a class="pag-jump page-numbers" href="javascript:;" no-scroll="true" data-jump-comments="1"><input autocomplete="off" max="' . esc_attr((string) $max_pages) . '" current="' . esc_attr((string) $paged) . '" type="' . ($is_mobile ? 'number' : 'text') . '" class="form-control jump-input" name="pag-go"><span class="hide-sm mr6 jump-text">' . __('跳转', 'aksrt') . '</span><i class="jump-icon fa fa-angle-double-right em12"></i></a>';
}
echo '<div class="pagenav">';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- paginate_links 输出与既定跳页标记。
echo implode('', $array);
echo '</div>';
return;
}
// load_more / infinite 模式
$post_id = get_the_ID();
$total = (int) get_comments_number($post_id);
$per_page = (int) get_option('comments_per_page', 50);
$max_pages = $per_page > 0 ? ceil($total / $per_page) : 1;
$current_page = max(1, get_query_var('cpage', 1));
$comment_order = (string) get_query_var('aksrt_comment_order', 'newest');
if (!in_array($comment_order, ['newest', 'oldest', 'hottest'], true)) {
$comment_order = 'newest';
}
$author_only = isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1;
if ($current_page >= $max_pages || $total <= $per_page) {
return;
}
?>
<div class="aksrt-pagination-ajax aksrt-comment-pagination ias-pagenav theme-pagination"
data-mode="<?php echo esc_attr($comment_pagination_mode); ?>"
data-next-page="<?php echo esc_attr((string) ($current_page + 1)); ?>"
data-max-pages="<?php echo esc_attr((string) ((int) $max_pages)); ?>"
data-post-id="<?php echo esc_attr((string) $post_id); ?>"
data-comment-order="<?php echo esc_attr($comment_order); ?>"
data-author-only="<?php echo $author_only ? '1' : '0'; ?>">
<?php if ($comment_pagination_mode === 'load_more') : ?>
<button type="button" class="aksrt-load-more-btn aksrt-load-more-btn--comment">
<span class="aksrt-load-more-btn__text"><?php esc_html_e('加载更多评论', 'aksrt'); ?></span>
</button>
<?php endif; ?>
<div class="aksrt-loading-indicator" aria-hidden="true">
<i class="fa fas fa-spinner fa-spin"></i>
<span><?php esc_html_e('加载中…', 'aksrt'); ?></span>
</div>
</div>
<?php
}
}
+2 -4
View File
@@ -476,10 +476,8 @@ trait ScriptsPart1c {
}
// Page navigation (TOC highlight, smooth scroll, doc search)
// 评论回复脚本
if (is_singular() && comments_open() && get_option('thread_comments')) {
wp_enqueue_script('comment-reply');
}
// 评论回复脚本:由 assets/js/core/comment-move-form.js 提供(zibll 同款机制),
// 不加载 WP comment-reply,避免两套 moveForm 冲突。
$runtime_routes = $this->resolve_frontend_routes();