/** * 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(' ' + 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() 输出格式:

错误信息

var match = html.match(/]*>([^<]+)<\/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( ' 请等待 ' + 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(/]*>([^<]*(?:错误|失败|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 = '
  • ' + '
    ' + '
    ' + '
    ' + '
  • '; // 获取当前页面 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 = '
  • ' + '
    ' + '
    ' + '
    ' + '
  • '; } 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)); } var currentList = commentsArea.querySelector("#comment-list"); var newList = newComments.querySelector("#comment-list"); if (currentList && newList) { currentList.replaceWith(newList.cloneNode(true)); } 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); }); })();