Files
wordpress-theme-aksrt/assets/js/core/comments.js
T
kate522 b01a89bee2 feat(frontend): wire comment sort/author filter like Zibll
Apply comments_template_query_args for newest/hottest/author-only,
keep filter bar visible under empty author filters, and restyle order
tabs/buttons with Zibll-compatible classes.
2026-07-11 21:07:22 +08:00

657 lines
24 KiB
JavaScript

/**
* AKSRT Comment System — AJAX submit, captcha, ordering, author filter
*/
(function () {
"use strict";
function showToast(msg, type) {
if (window.aksrt && aksrt.notyf) {
aksrt.notyf(msg, { type: type || "info", duration: 2200 });
} else {
console.log("[AKSRT Toast]", type, msg);
}
}
function initCommentSystem() {
var form = document.querySelector("#commentform");
if (!form) return;
// 修复:同时匹配 button 和 input 元素
var submitBtn = form.querySelector('[type="submit"]');
if (!submitBtn) return;
var isInputSubmit = submitBtn.tagName === 'INPUT';
// 保存原始按钮内容(用于恢复)
var originalBtnHTML = isInputSubmit ? submitBtn.value : submitBtn.innerHTML;
function setSubmitBtnContent(html, textFallback) {
if (isInputSubmit) {
submitBtn.value = textFallback;
} else {
submitBtn.innerHTML = html;
}
}
// 状态管理
var isSubmitting = false;
var countdownTimer = null;
// 显示/隐藏加载状态
function setLoading(loading) {
if (loading) {
isSubmitting = true;
submitBtn.classList.add("is-loading");
submitBtn.disabled = true;
var loadingText = (window.aksrtData && aksrtData.commentSubmittingText) || '发送中...';
setSubmitBtnContent('<i class="fas fa-circle-notch fa-spin"></i> ' + loadingText, loadingText);
} else {
isSubmitting = false;
submitBtn.classList.remove("is-loading");
submitBtn.classList.remove('countdown-active');
submitBtn.disabled = false;
setSubmitBtnContent(originalBtnHTML, originalBtnHTML);
}
}
// 收集表单数据(包含验证码字段)
function collectFormData() {
var formData = new FormData(form);
// 确保包含必要的隐藏字段
var postId = document.querySelector('input[name="comment_post_ID"]');
if (postId && !formData.get('comment_post_ID')) {
formData.append('comment_post_ID', postId.value);
}
return formData;
}
// 从 wp_die() HTML 中提取错误信息
function extractErrorMessage(html) {
// WordPress wp_die() 输出格式: <p>错误信息</p>
var match = html.match(/<p[^>]*>([^<]+)<\/p>/i);
return match ? match[1].trim() : '未知错误';
}
// 从错误消息中提取等待时间(秒)
function extractWaitTime(msg) {
var match = msg.match(/(\d+)\s*秒/);
return match ? parseInt(match[1]) : 0;
}
// 启动倒计时(用于频率限制)
function startCountdown(seconds) {
if (countdownTimer) clearInterval(countdownTimer);
submitBtn.classList.add('countdown-active');
submitBtn.disabled = true;
var remaining = seconds;
countdownTimer = setInterval(function () {
setSubmitBtnContent(
'<i class="fas fa-clock"></i> 请等待 ' + remaining + 's',
'请等待 ' + remaining + 's'
);
if (--remaining <= 0) {
clearInterval(countdownTimer);
countdownTimer = null;
submitBtn.classList.remove('countdown-active', 'is-loading');
submitBtn.disabled = false;
setSubmitBtnContent(originalBtnHTML, originalBtnHTML);
isSubmitting = false;
}
}, 1000);
}
function updateCommentCount(delta) {
var countEl = document.querySelector('.comments-count');
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 + ' 条';
}
function handleResponse(responseText, statusCode) {
try {
// 情况1: 成功提交 - 包含评论ID或成功标记
if (statusCode === 200 || statusCode === 201) {
if (responseText.includes('comment-submitted') ||
responseText.indexOf('id="comment-') !== -1 ||
responseText.indexOf('awaiting-moderation') !== -1) {
// 区分是否需要审核
if (responseText.indexOf('审核') !== -1 ||
responseText.indexOf('moderation') !== -1 ||
responseText.includes('awaiting-moderation')) {
showToast('评论已提交,等待管理员审核', 'info');
} else {
showToast('评论发布成功', 'success');
}
// 清空文本框
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
// 重置回复状态
var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none';
// 刷新评论列表(延迟执行,确保后端处理完成)
setTimeout(function () {
reloadCommentList();
}, 500);
setLoading(false);
return true;
}
}
// 情况2: HTTP 429 频率限制
if (statusCode === 429) {
var errorMsg = extractErrorMessage(responseText);
var waitSeconds = extractWaitTime(errorMsg);
showToast(errorMsg, 'warning');
if (waitSeconds > 0) {
startCountdown(waitSeconds);
} else {
setLoading(false); // 无具体等待时间时恢复按钮
}
return false;
}
// 情况3: HTTP 403 权限不足(地区/IP/关键词等)
if (statusCode === 403) {
var forbiddenMsg = extractErrorMessage(responseText);
showToast(forbiddenMsg, 'error');
setLoading(false);
return false;
}
// 情况4: 包含WordPress错误信息
var errorMatch = responseText.match(/<p[^>]*>([^<]*(?:错误|失败|error|Error)[^<]*)<\/p>/i);
if (errorMatch) {
showToast(errorMatch[1].trim(), 'error');
setLoading(false);
return false;
}
// 默认情况:可能是成功或其他
showToast('评论已提交', 'success');
var textarea2 = form.querySelector('#comment');
if (textarea2) textarea2.value = '';
// 局部刷新评论列表(不再整页 reload)
setTimeout(function () {
reloadCommentList();
}, 500);
setLoading(false);
return true;
} catch (e) {
console.error('[AKSRT Comment] 解析响应失败:', e);
showToast('响应解析异常,请重试', 'error');
setLoading(false);
return false;
}
}
// 执行实际的 AJAX 请求 — local insert instead of full-page re-fetch
function performSubmit() {
setLoading(true);
var formData = collectFormData();
var restBase = '/wp-json/aksrt/v1/comment/submit';
// Try REST endpoint for local-insert HTML response
var restBody = {};
formData.forEach(function (v, k) { restBody[k] = v; });
fetch(restBase, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || ''
},
credentials: 'same-origin',
body: JSON.stringify(restBody)
})
.then(function (r) { return r.json(); })
.then(function (json) {
if (json && json.success && json.html) {
// Local insert: prepend new comment HTML
localInsertComment(json.html, json.is_reply, json.parent_id, json.needs_moderation);
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none';
// Reset captcha
var captchaImg = form.querySelector('.imagecaptcha img');
if (captchaImg) captchaImg.click();
// Wait time countdown
if (json.wait_time && json.wait_time > 0) {
startCountdown(json.wait_time);
} else {
setLoading(false);
}
// Move respond back to original position
restoreRespondPosition();
return;
}
if (json && json.code) {
showToast(json.message || '评论提交失败', 'error');
if (json.wait_time) startCountdown(json.wait_time);
else setLoading(false);
return;
}
showToast('评论已提交', 'success');
setLoading(false);
setTimeout(function () { reloadCommentList(); }, 500);
})
.catch(function () {
// Fallback: submit to wp-comments-post.php directly
performLegacySubmit();
});
}
function localInsertComment(html, isReply, parentId, needsModeration) {
var temp = document.createElement('div');
temp.innerHTML = html;
var newComment = temp.firstElementChild;
if (!newComment) { reloadCommentList(); return; }
newComment.style.overflow = 'hidden';
newComment.style.maxHeight = '0';
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 + '"]');
if (parentComment) {
var children = parentComment.querySelector('.children');
if (!children) {
children = document.createElement('ol');
children.className = 'children';
parentComment.appendChild(children);
}
children.insertBefore(newComment, children.firstChild);
} else {
prependToCommentList(newComment);
}
} else {
prependToCommentList(newComment);
}
// slideDown animation
requestAnimationFrame(function () {
newComment.style.maxHeight = newComment.scrollHeight + 100 + 'px';
newComment.style.opacity = '1';
setTimeout(function () {
newComment.style.maxHeight = '';
newComment.style.overflow = '';
newComment.style.transition = '';
}, 500);
});
if (needsModeration) {
showToast('评论已提交,等待管理员审核', 'info');
} else {
showToast('评论发布成功', 'success');
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(newComment, 'ajax');
updateCommentCount(1);
}
function prependToCommentList(el) {
var list = document.querySelector('.commentlist, #comment-list > ol, #comment-list');
if (list) list.insertBefore(el, list.firstChild);
}
function restoreRespondPosition() {
var respond = document.getElementById('respond');
var placeholder = document.getElementById('wp-temp-form-div');
if (respond && placeholder && placeholder.parentNode) {
placeholder.parentNode.insertBefore(respond, placeholder);
placeholder.remove();
}
}
// Legacy fallback: POST to wp-comments-post.php
function performLegacySubmit() {
var formData = collectFormData();
var actionUrl = form.getAttribute('action') || '/wp-comments-post.php';
fetch(actionUrl, {
method: 'POST',
body: formData,
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function (response) {
return response.text().then(function (text) {
return { status: response.status, text: text, ok: response.ok };
});
})
.then(function (result) {
handleResponse(result.text, result.status);
})
.catch(function (error) {
console.error('[AKSRT Comment] 提交失败:', error);
if (error.message === 'Failed to fetch' || error.name === 'TypeError') {
showToast('网络连接失败,请检查网络后重试', 'error');
} else {
showToast(error.message || '评论提交失败,请重试', 'error');
}
setLoading(false);
})
.finally(function () {
// 兜底:避免异常分支漏掉状态恢复导致按钮卡在"发送中"
if (!countdownTimer && isSubmitting) {
setLoading(false);
}
});
}
// 核心表单提交事件处理器
function handleSubmit(e) {
// 如果已经在提交中或倒计时中,防止重复提交
if (isSubmitting || countdownTimer) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
// 阻止默认表单提交
e.preventDefault();
e.stopImmediatePropagation();
// 检查验证码状态(如果有验证码组件)
var captchaScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (captchaScene && typeof captchaScene.verify === 'function') {
// 有验证码:先触发验证流程
setLoading(true);
captchaScene.verify(function (verified) {
if (verified !== false) {
// 验证通过,执行 AJAX 提交
performSubmit();
} else {
// 验证失败
showToast('人机验证未通过,请重试', 'warning');
setLoading(false);
}
});
} else {
// 无验证码或已经验证过:直接提交
performSubmit();
}
return false;
}
// 绑定表单提交事件(使用捕获阶段,高优先级)
form.addEventListener('submit', handleSubmit, true);
}
/* ====== 评论列表动态刷新 ====== */
function reloadCommentList() {
var listContainer = document.getElementById('comment-list');
if (!listContainer) {
// 降级:整页刷新
window.location.reload();
return;
}
var postIdInput = document.querySelector('input[name="comment_post_ID"]');
if (!postIdInput) 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>';
// 获取当前页面 URL(带时间戳避免缓存)
var currentUrl = window.location.href.split('#')[0];
var bustUrl = currentUrl + (currentUrl.indexOf('?') === -1 ? '?' : '&') + '_cache_bust=' + Date.now();
fetch(bustUrl, {
method: 'GET',
credentials: 'same-origin'
})
.then(function (response) { return response.text(); })
.then(function (html) {
// 从新页面提取评论列表
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var newList = doc.getElementById('comment-list');
if (newList && newList.innerHTML.trim()) {
listContainer.innerHTML = newList.innerHTML;
// 更新评论计数
var countEl = document.querySelector('.comments-count');
var newCountEl = doc.querySelector('.comments-count');
if (countEl && newCountEl) {
countEl.textContent = newCountEl.textContent;
}
} else {
listContainer.innerHTML = '';
}
})
.catch(function (error) {
console.error('[AKSRT Comment] 刷新评论列表失败:', error);
// 失败时降级为整页刷新
window.location.reload();
});
}
/* ====== 评论排序(最新 / 最早) ====== */
function initCommentOrder(root) {
var scope = root || document;
var commentsArea = scope.getElementById("comments");
if (!commentsArea) return;
if (commentsArea.dataset.orderInit === "1") return;
commentsArea.dataset.orderInit = "1";
function getPaginationEl(root) {
if (!root) return null;
return root.querySelector(".aksrt-comment-pagination, .comment-navigation");
}
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>';
}
function applyCommentOrder(order) {
if (order !== "newest" && order !== "oldest" && order !== "hottest") {
order = "newest";
}
renderLoadingState();
var url = new URL(window.location.href);
url.searchParams.set("aksrt_comment_order", order);
url.searchParams.delete("cpage");
var fetchUrl = new URL(url.toString());
fetchUrl.searchParams.set("_cache_bust", Date.now().toString());
fetch(fetchUrl.toString(), {
method: "GET",
credentials: "same-origin"
})
.then(function (response) { return response.text(); })
.then(function (html) {
var parser = new DOMParser();
var doc = parser.parseFromString(html, "text/html");
var newComments = doc.getElementById("comments");
if (!newComments) {
throw new Error("new comments area not found");
}
var currentFilter = commentsArea.querySelector(".comment-filter");
var newFilter = newComments.querySelector(".comment-filter");
if (currentFilter && newFilter) {
currentFilter.replaceWith(newFilter.cloneNode(true));
}
var currentList = commentsArea.querySelector("#comment-list");
var newList = newComments.querySelector("#comment-list");
if (currentList && newList) {
currentList.replaceWith(newList.cloneNode(true));
}
var currentPagination = getPaginationEl(commentsArea);
var newPagination = getPaginationEl(newComments);
if (currentPagination && newPagination) {
currentPagination.replaceWith(newPagination.cloneNode(true));
} else if (currentPagination && !newPagination) {
currentPagination.remove();
} else if (!currentPagination && newPagination) {
var list = commentsArea.querySelector("#comment-list");
if (list && list.parentNode) {
list.parentNode.insertBefore(newPagination.cloneNode(true), list.nextSibling);
}
}
var currentCount = commentsArea.querySelector(".comments-count");
var newCount = newComments.querySelector(".comments-count");
if (currentCount && newCount) {
currentCount.textContent = newCount.textContent;
}
if (window.history && typeof window.history.replaceState === "function") {
window.history.replaceState(null, "", url.toString());
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(commentsArea, 'ajax');
})
.catch(function (error) {
console.error("[AKSRT Comment] 排序刷新失败:", error);
window.location.href = url.toString();
});
}
commentsArea.addEventListener("click", function (e) {
var link = e.target.closest(".comment-orderby");
if (link && commentsArea.contains(link)) {
e.preventDefault();
var order = (link.dataset && link.dataset.order) ? link.dataset.order : "newest";
applyCommentOrder(order);
return;
}
var authorLink = e.target.closest(".comment-author-filter");
if (authorLink && commentsArea.contains(authorLink)) {
e.preventDefault();
var authorOnly = (authorLink.dataset && authorLink.dataset.authorOnly) ? authorLink.dataset.authorOnly : "1";
applyAuthorFilter(authorOnly);
}
});
function applyAuthorFilter(authorOnly) {
renderLoadingState();
var url = new URL(window.location.href);
if (authorOnly === "1") {
url.searchParams.set("aksrt_author_only", "1");
} else {
url.searchParams.delete("aksrt_author_only");
}
url.searchParams.delete("cpage");
var fetchUrl = new URL(url.toString());
fetchUrl.searchParams.set("_cache_bust", Date.now().toString());
fetch(fetchUrl.toString(), {
method: "GET",
credentials: "same-origin"
})
.then(function (response) { return response.text(); })
.then(function (html) {
var parser = new DOMParser();
var doc = parser.parseFromString(html, "text/html");
var newComments = doc.getElementById("comments");
if (!newComments) {
throw new Error("new comments area not found");
}
var currentFilter = commentsArea.querySelector(".comment-filter");
var newFilter = newComments.querySelector(".comment-filter");
if (currentFilter && newFilter) {
currentFilter.replaceWith(newFilter.cloneNode(true));
} else if (!currentFilter && newFilter) {
var listAnchor = commentsArea.querySelector("#postcomments, #comment-list");
if (listAnchor && listAnchor.parentNode) {
listAnchor.parentNode.insertBefore(newFilter.cloneNode(true), listAnchor);
}
}
var currentListWrap = commentsArea.querySelector("#postcomments");
var newListWrap = newComments.querySelector("#postcomments");
var currentList = commentsArea.querySelector("#comment-list");
var newList = newComments.querySelector("#comment-list");
if (currentListWrap && newListWrap) {
currentListWrap.replaceWith(newListWrap.cloneNode(true));
} else if (currentList && newList) {
currentList.replaceWith(newList.cloneNode(true));
}
var currentEmpty = commentsArea.querySelector(".comments-empty");
var newEmpty = newComments.querySelector(".comments-empty");
if (currentEmpty && newEmpty) {
currentEmpty.replaceWith(newEmpty.cloneNode(true));
} else if (currentEmpty && !newEmpty) {
currentEmpty.remove();
} else if (!currentEmpty && newEmpty) {
var insertAt = commentsArea.querySelector("#postcomments") || commentsArea;
insertAt.appendChild(newEmpty.cloneNode(true));
}
var currentPagination = getPaginationEl(commentsArea);
var newPagination = getPaginationEl(newComments);
if (currentPagination && newPagination) {
currentPagination.replaceWith(newPagination.cloneNode(true));
} else if (currentPagination && !newPagination) {
currentPagination.remove();
}
var currentCount = commentsArea.querySelector(".comments-count");
var newCount = newComments.querySelector(".comments-count");
if (currentCount && newCount) {
currentCount.textContent = newCount.textContent;
}
if (window.history && typeof window.history.replaceState === "function") {
window.history.replaceState(null, "", url.toString());
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(commentsArea, 'ajax');
})
.catch(function (error) {
console.error("[AKSRT Comment] Author filter failed:", error);
window.location.href = url.toString();
});
}
}
window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); });
window.AksrtFrontend.register("aksrt-comment-order", function (root) { initCommentOrder(root); });
})();