/** * AKSRT Comment System — AJAX submit, captcha, ordering, author filter */ (function () { "use strict"; function initCommentSystem() { var form = document.querySelector("#commentform"); if (!form || form.dataset.aksrtCommentInit === "1") return; form.dataset.aksrtCommentInit = "1"; // 修复:同时匹配 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; } // 启动倒计时(用于频率限制) 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 .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 = text.replace(match[1], String(nextCount)); } // 执行实际的 REST 请求 — local insert instead of full-page re-fetch function performSubmit(captchaPayload) { setLoading(true); var formData = collectFormData(); var restRoot = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, '') : ''; var editId = form.querySelector('[name="edit_comment_ID"]'); var isEdit = editId && parseInt(editId.value, 10) > 0; var restBase = (restRoot || '') + '/wp-json/aksrt/v1/comment/' + (isEdit ? 'edit' : 'submit'); // Try REST endpoint for local-insert HTML response var restBody = {}; formData.forEach(function (v, k) { restBody[k] = v; }); Object.assign(restBody, captchaPayload || {}); if (isEdit) { restBody.comment_id = parseInt(editId.value, 10); restBody.content = restBody.comment || restBody.content || ''; } 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 (isEdit && json && json.success) { var cid = restBody.comment_id; var contentEl = document.querySelector('#comment-content-' + cid + ', [data-comment-id="' + cid + '"] .comment-content'); var rendered = (json.data && json.data.content) || json.content || ''; if (contentEl && rendered) contentEl.innerHTML = rendered; var textarea = form.querySelector('#comment'); if (textarea) textarea.value = ''; if (editId) editId.remove(); restoreRespondPosition(); setLoading(false); notyf(json.message || '评论已更新', 'success'); var respondEl = document.getElementById('respond'); if (respondEl && window.AksrtFixedInput) window.AksrtFixedInput(respondEl, 'hide'); return; } 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'; // 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(); // Hide mobile FixedInput after successful submit. var respondEl = document.getElementById('respond'); if (respondEl && window.AksrtFixedInput) { window.AksrtFixedInput(respondEl, 'hide'); } else if (respondEl) { respondEl.classList.remove('show'); } // If editing via moveForm, clear edit field. var editField = form.querySelector('[name="edit_comment_ID"]'); if (editField) editField.remove(); return; } if (json && json.code) { notyf(json.message || '评论提交失败', 'danger'); if (json.wait_time) startCountdown(json.wait_time); else setLoading(false); return; } notyf('评论已提交', 'success'); setLoading(false); setTimeout(function () { reloadCommentList(); }, 500); }) .catch(function (error) { console.error('[AKSRT Comment] 提交失败:', error); notyf((error && error.message) || '网络连接失败,请检查网络后重试', 'danger'); setLoading(false); }) .finally(function () { if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset('comment', form); if (!countdownTimer && isSubmitting) setLoading(false); }); } 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); if (parentComment) { var children = parentComment.querySelector(':scope > .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) { notyf('评论已提交,等待管理员审核', 'info'); } else { notyf('评论发布成功', 'success'); } // legacy 事件顺序:先 post_ajax.ed,再全局 auto_fun 重扫 newComment.dispatchEvent(new CustomEvent('post_ajax.ed', { bubbles: true })); if (typeof window.auto_fun === 'function') window.auto_fun(); updateCommentCount(1); } function prependToCommentList(el) { 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() { 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(); } } // 核心表单提交事件处理器 function handleSubmit(e) { // 如果已经在提交中或倒计时中,防止重复提交 if (isSubmitting || countdownTimer) { e.preventDefault(); e.stopImmediatePropagation(); return false; } // 阻止默认表单提交 e.preventDefault(); e.stopImmediatePropagation(); var captcha = window.AKSRTCaptcha; setLoading(true); (captcha ? captcha.run('comment', form) : Promise.resolve({})).then(function (captchaPayload) { if (captchaPayload === null) { notyf('人机验证未通过,请重试', 'warning'); setLoading(false); return; } performSubmit(captchaPayload); }).catch(function () { notyf('人机验证未通过,请重试', 'warning'); setLoading(false); if (captcha) captcha.reset('comment', form); }); 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 btn = document.querySelector("#comments .btn-only-author"); return !!(btn && btn.classList.contains("active") && btn.dataset.authorOnly === "0"); } /* ====== 全局无参数加载:评论状态仅保存在客户端,地址栏不携带 ? 参数 ====== */ var COMMENT_URL_PARAMS = ["cpage", "aksrt_comment_order", "aksrt_author_only"]; var commentUrlStateCleaned = false; function stripCommentUrlParams() { if (commentUrlStateCleaned || !window.history || typeof window.history.replaceState !== "function") return; commentUrlStateCleaned = true; var url = new URL(window.location.href); var changed = false; COMMENT_URL_PARAMS.forEach(function (key) { if (url.searchParams.has(key)) { url.searchParams.delete(key); changed = true; } }); if (changed) { // 服务端首屏已按参数渲染完毕,落地后地址栏回到无参干净链接。 window.history.replaceState(null, "", url.toString()); } } function getMaxCommentPages() { var pagenav = document.querySelector("#comments .pagenav[data-max-pages]"); if (pagenav) { var max = parseInt(pagenav.dataset.maxPages, 10); if (max > 0) return max; } var jumpInput = document.querySelector("#comments .jump-input[max]"); if (jumpInput) { var maxAttr = parseInt(jumpInput.getAttribute("max"), 10); if (maxAttr > 0) return maxAttr; } return 1; } function parseCommentPageFromHref(href) { if (!href || href === "#" || href.indexOf("javascript:") === 0) return 0; try { var url = new URL(href, window.location.href); var cpage = parseInt(url.searchParams.get("cpage"), 10); if (cpage > 0) return cpage; var match = url.pathname.match(/\/comment-page-([0-9]{1,})\/?$/); return match ? parseInt(match[1], 10) : 0; } catch (e) { return 0; } } function scrollCommentsIntoView() { var commentsArea = document.getElementById("comments"); if (!commentsArea || typeof commentsArea.scrollIntoView !== "function") return; var rect = commentsArea.getBoundingClientRect(); if (rect.top >= 0 && rect.top < window.innerHeight * 0.6) return; commentsArea.scrollIntoView({ behavior: "smooth", block: "start" }); } function loadCommentPage(page, opts) { opts = opts || {}; var commentsArea = document.getElementById("comments"); if (!commentsArea) { return Promise.reject(new Error("comments area missing")); } var maxPages = getMaxCommentPages(); page = parseInt(page, 10) || 1; page = Math.max(1, page); if (maxPages > 1) page = Math.min(page, maxPages); showListLoading(commentsArea); return fetchCommentFragment({ order: getCurrentCommentOrder(), authorOnly: getCurrentAuthorOnly(), page: page }).then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea, json.data); if (!opts.noScroll) scrollCommentsIntoView(); return true; } throw new Error("comment fragment failed"); }); } function applyFragmentPayload(commentsArea, data) { if (!commentsArea || !data || !data.html) return; // data.html 是完整的
(含筛选、列表、分页) var tempList = document.createElement("div"); tempList.innerHTML = data.html; var newListWrap = tempList.querySelector("#postcomments"); if (!newListWrap) return; var currentListWrap = commentsArea.querySelector("#postcomments"); if (currentListWrap) { currentListWrap.replaceWith(newListWrap); } else { commentsArea.appendChild(newListWrap); } var currentCount = commentsArea.querySelector(".title-theme small"); if (currentCount && data.count_text) currentCount.textContent = data.count_text; // legacy 事件顺序:先 post_ajax.ed,再全局 auto_fun 重扫 newListWrap.dispatchEvent(new CustomEvent("post_ajax.ed", { bubbles: true })); if (typeof window.auto_fun === "function") window.auto_fun(); } 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 showListLoading(commentsArea) { var list = commentsArea.querySelector(".commentlist"); if (!list) return; // 保留筛选栏,清空条目并显示 legacy 骨架 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() { loadCommentPage(1, { noScroll: true }).catch(function (error) { console.error('[AKSRT Comment] 刷新评论列表失败:', error); window.location.reload(); }); } /* ====== 评论排序(最新 / 最早) ====== */ /** * Locate #id within the given scope. * Document has getElementById; Element roots (AJAX panels) only have querySelector. */ function findInScope(scope, id) { scope = scope || document; if (!scope) return null; if (scope.nodeType === 9) { return scope.getElementById(id); } if (scope.nodeType === 1) { if (scope.id === id) return scope; return typeof scope.querySelector === "function" ? scope.querySelector("#" + id) : null; } return null; } function initCommentOrder(root) { var scope = root || document; var commentsArea = findInScope(scope, "comments"); if (!commentsArea || commentsArea.dataset.orderInit === "1") return; // Event delegation survives filter HTML replacement after AJAX refresh. commentsArea.dataset.orderInit = "1"; function renderLoadingState() { showListLoading(commentsArea); } function applyCommentOrder(order) { if (order !== "newest" && order !== "oldest" && order !== "hottest") { order = "newest"; } renderLoadingState(); var authorOnly = getCurrentAuthorOnly(); fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) .then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea, json.data); return; } throw new Error("order fragment failed"); }) .catch(function (error) { console.error("[AKSRT Comment] 排序刷新失败:", error); // 兜底整页刷新,地址栏保持无参。 window.location.reload(); }); } function applyAuthorFilter(authorOnlyFlag) { renderLoadingState(); var authorOnly = authorOnlyFlag === "1" || authorOnlyFlag === 1 || authorOnlyFlag === true; var order = getCurrentCommentOrder(); fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) .then(function (json) { if (json && json.success && json.data) { applyFragmentPayload(commentsArea, json.data); return; } throw new Error("author fragment failed"); }) .catch(function (error) { console.error("[AKSRT Comment] Author filter failed:", error); // 兜底整页刷新,地址栏保持无参。 window.location.reload(); }); } commentsArea.addEventListener("click", function (e) { var target = e.target; if (!target || typeof target.closest !== "function") return; // 「只看作者」同时带 comment-orderby,需优先于排序按钮判断 var authorBtn = target.closest(".btn-only-author"); if (authorBtn && commentsArea.contains(authorBtn)) { e.preventDefault(); applyAuthorFilter(authorBtn.getAttribute("data-author-only") || "0"); return; } var orderBtn = target.closest(".comment-orderby[data-order]"); if (orderBtn && commentsArea.contains(orderBtn)) { e.preventDefault(); applyCommentOrder(orderBtn.getAttribute("data-order") || "newest"); return; } // 数字分页:本地 DOM 刷新,地址栏不携带参数(href 仅作无 JS 兜底)。 var pageLink = target.closest(".pagenav a.page-numbers"); if (pageLink && commentsArea.contains(pageLink) && !pageLink.classList.contains("pag-jump")) { var page = parseCommentPageFromHref(pageLink.getAttribute("href")); if (page > 0) { e.preventDefault(); loadCommentPage(page).catch(function (error) { console.error("[AKSRT Comment] 分页刷新失败:", error); // AJAX 不可用时退回原始链接(pretty 链接本身无 ? 参数)。 window.location.href = pageLink.href; }); } } }); } /* ====== 数字分页跳页(legacy pag-jump) ====== */ var commentPageJumpBound = false; function jumpToCommentPage(page) { loadCommentPage(page).catch(function (error) { console.error("[AKSRT Comment] 跳页失败:", error); window.location.reload(); }); } function initCommentPageJump() { if (commentPageJumpBound) return; commentPageJumpBound = true; document.addEventListener("click", function (e) { var jump = e.target.closest ? e.target.closest("#comments .pag-jump") : null; if (!jump) return; e.preventDefault(); var input = jump.querySelector(".jump-input"); if (input) jumpToCommentPage(input.value); }); 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); }); } function init() { stripCommentUrlParams(); initCommentSystem(); initCommentPageJump(); initCommentOrder(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } // AJAX 注入新内容后由 auto_fun 重扫(form/commentsArea 均带 data 标记,重复调用幂等)。 if (window.jQuery) { window.jQuery(document).on("auto_fun", init); } // 供 pagination.js 等模块复用:评论页码加载 / 列表刷新(局部 DOM,无 ? 参数)。 window.AksrtComments = { loadPage: function (page, opts) { return loadCommentPage(page, opts); }, reload: function () { reloadCommentList(); } }; })();