/** * 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 restRoot = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, '') : ''; var restBase = (restRoot || '') + '/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 getCommentsRestBase() { var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : ""; return root + "/wp-json/aksrt/v1"; } function getCurrentCommentPostId() { var input = document.querySelector('input[name="comment_post_ID"]'); if (input && input.value) return parseInt(input.value, 10) || 0; var comments = document.getElementById("comments"); if (comments && comments.dataset.postId) return parseInt(comments.dataset.postId, 10) || 0; return 0; } function getCurrentCommentOrder() { var active = document.querySelector("#comments .comment-order-box li.active .comment-orderby"); if (active && active.dataset && active.dataset.order) return active.dataset.order; var url = new URL(window.location.href); return url.searchParams.get("aksrt_comment_order") || "newest"; } 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"); return !!(btn && btn.classList.contains("active") && btn.dataset.authorOnly === "0"); } function applyFragmentPayload(commentsArea, data, urlState) { if (!commentsArea || !data) 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); } } } } 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); } 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"); if (currentCount && data.count_text) currentCount.textContent = data.count_text; if (urlState && window.history && typeof window.history.replaceState === "function") { window.history.replaceState(null, "", urlState); } if (window.AksrtFrontend) AksrtFrontend.rehydrate(commentsArea, "ajax"); } function fetchCommentFragment(opts) { opts = opts || {}; var postId = opts.postId || getCurrentCommentPostId(); if (!postId) return Promise.reject(new Error("missing post id")); var order = opts.order || getCurrentCommentOrder() || "newest"; var authorOnly = !!opts.authorOnly; var page = opts.page || 1; var qs = new URLSearchParams(); qs.set("post_id", String(postId)); qs.set("order", order); qs.set("page", String(page)); if (authorOnly) qs.set("author_only", "1"); var url = getCommentsRestBase() + "/comment/list?" + qs.toString(); return fetch(url, { method: "GET", credentials: "same-origin", headers: { "X-WP-Nonce": (window.aksrtData && aksrtData.restNonce) || "" } }).then(function (r) { return r.json(); }); } /* ====== 评论列表动态刷新 ====== */ function reloadCommentList() { var commentsArea = document.getElementById('comments'); var listContainer = document.getElementById('comment-list'); if (!commentsArea && !listContainer) { window.location.reload(); return; } if (listContainer) { listContainer.innerHTML = '
  • ' + '
    ' + '
    ' + '
    ' + '
  • '; } fetchCommentFragment({ order: getCurrentCommentOrder(), authorOnly: getCurrentAuthorOnly(), page: 1 }).then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea || document, json.data, null); return; } throw new Error('fragment failed'); }).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 authorOnly = getCurrentAuthorOnly(); var url = new URL(window.location.href); url.searchParams.set("aksrt_comment_order", order); url.searchParams.delete("cpage"); if (authorOnly) url.searchParams.set("aksrt_author_only", "1"); else url.searchParams.delete("aksrt_author_only"); fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) .then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea, json.data, url.toString()); return; } throw new Error("order fragment failed"); }) .catch(function (error) { console.error("[AKSRT Comment] 排序刷新失败:", error); window.location.href = url.toString(); }); } function applyAuthorFilter(authorOnlyFlag) { renderLoadingState(); var authorOnly = authorOnlyFlag === "1" || authorOnlyFlag === 1 || authorOnlyFlag === true; var order = getCurrentCommentOrder(); var url = new URL(window.location.href); if (authorOnly) url.searchParams.set("aksrt_author_only", "1"); else url.searchParams.delete("aksrt_author_only"); url.searchParams.delete("cpage"); if (order) url.searchParams.set("aksrt_comment_order", order); fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) .then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea, json.data, url.toString()); return; } throw new Error("author fragment failed"); }) .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); }); })();