/** * AKSRT Comment Actions client * * Single client for per-comment actions via REST aksrt/v1: * like / edit / delete / moderate (approve|hold|spam) / pin * * Comment SUBMISSION is owned by main.js initCommentSystem() (the native * wp-comments-post.php flow, which runs captcha verification, rate limiting and * the moderation pipeline, then re-renders the list through the canonical * server callback). This file deliberately does NOT bind the submit form, so * there is exactly one comment-submit contract. * * Markup contract (rendered by aksrt_comment_callback, identical on first * paint, load-more and post-submit reload): * li.aksrt-comment[data-comment-id] * .aksrt-comment__text (content) * .aksrt-comment__actions * [data-comment-action="like|edit|delete|approve|hold|spam|pin"] * * Depends on: aksrtData.restNonce (wp_rest) + aksrtData.siteUrl. * * @package AKSRT */ (function () { 'use strict'; if (!window.aksrtData) return; var COMMENT_SELECTOR = '.aksrt-comment, .comment'; var CONTENT_SELECTOR = '.aksrt-comment__text, .comment-content'; var REST_BASE = (aksrtData.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1'; var REST_NONCE = aksrtData.restNonce || ''; function restPost(endpoint, body) { return fetch(REST_BASE + endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body), credentials: 'same-origin' }).then(function (r) { return r.json(); }); } function notify(message, type) { if (typeof window.AksrtToast === 'function') { window.AksrtToast(message, type || 'info'); return; } if (typeof window.aksrtToast === 'function') { window.aksrtToast(message, type || 'info'); return; } if (typeof window.showToast === 'function') { window.showToast(message, type || 'info'); return; } if (type === 'error') { window.alert(message); } } function getCommentId(el) { return parseInt(el.getAttribute('data-comment-id') || (el.id || '').replace('comment-', ''), 10) || 0; } // ── Comment actions (event delegation) ─────────────────────────── document.addEventListener('click', function (e) { var actionBtn = e.target.closest('[data-comment-action]'); if (!actionBtn) return; var action = actionBtn.getAttribute('data-comment-action'); var commentEl = actionBtn.closest(COMMENT_SELECTOR); if (!commentEl) return; var commentId = getCommentId(commentEl); if (!commentId) return; switch (action) { case 'like': handleLike(actionBtn, commentId); break; case 'edit': handleEdit(commentEl); break; case 'save-edit': saveEdit(commentEl, commentId, actionBtn); break; case 'cancel-edit': cancelEdit(commentEl); break; case 'delete': handleDelete(commentEl, commentId); break; case 'pin': handlePin(commentEl, actionBtn, commentId); break; case 'approve': case 'hold': case 'spam': handleModerate(commentEl, commentId, action); break; } }); function handleLike(btn, commentId) { if (btn.dataset.busy === '1') return; btn.dataset.busy = '1'; var nextActive = btn.getAttribute('data-active') !== '1'; restPost('/comment/like', { comment_id: commentId, active: nextActive }) .then(function (result) { if (!result.success) { notify((result.data && result.data.message) || result.message || '操作失败', 'error'); return; } var active = !!(result.data && result.data.active); 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('fas', active); icon.classList.toggle('far', !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', ''); } } }) .catch(function () { notify('网络错误', 'error'); }) .finally(function () { btn.dataset.busy = ''; }); } function handleEdit(commentEl) { var contentEl = commentEl.querySelector(CONTENT_SELECTOR); if (!contentEl || commentEl.querySelector('.aksrt-comment-edit-form')) return; var temp = document.createElement('div'); temp.innerHTML = contentEl.innerHTML; var originalText = (temp.textContent || temp.innerText || '').trim(); var editForm = document.createElement('div'); editForm.className = 'aksrt-comment-edit-form'; editForm.innerHTML = '' + '
' + '' + '' + '
'; editForm.querySelector('textarea').value = originalText; contentEl.style.display = 'none'; contentEl.parentNode.insertBefore(editForm, contentEl.nextSibling); editForm.querySelector('textarea').focus(); } function cancelEdit(commentEl) { var editForm = commentEl.querySelector('.aksrt-comment-edit-form'); if (!editForm) return; var contentEl = commentEl.querySelector(CONTENT_SELECTOR); if (contentEl) contentEl.style.display = ''; editForm.remove(); } function saveEdit(commentEl, commentId, saveBtn) { var editForm = commentEl.querySelector('.aksrt-comment-edit-form'); if (!editForm) return; var textarea = editForm.querySelector('.aksrt-comment-edit-textarea'); var newContent = textarea ? textarea.value.trim() : ''; if (!newContent) return; if (saveBtn) { saveBtn.disabled = true; saveBtn.textContent = '保存中...'; } restPost('/comment/edit', { comment_id: commentId, content: newContent }) .then(function (result) { if (result.success) { var contentEl = commentEl.querySelector(CONTENT_SELECTOR); if (contentEl) { contentEl.innerHTML = result.data.content; contentEl.style.display = ''; } editForm.remove(); } else { notify((result.data && result.data.message) || result.message || '编辑失败', 'error'); if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = '保存'; } } }) .catch(function () { notify('网络错误', 'error'); if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = '保存'; } }); } function handleDelete(commentEl, commentId) { var msg = (window.aksrt && aksrt.__) ? aksrt.__('comment_confirm_delete', '确定要删除这条评论吗?') : '确定要删除这条评论吗?'; var doDelete = function () { restPost('/comment/delete', { comment_id: commentId }) .then(function (result) { if (result.success) { commentEl.style.transition = 'opacity 0.3s, max-height 0.3s'; commentEl.style.maxHeight = commentEl.scrollHeight + 'px'; requestAnimationFrame(function () { commentEl.style.opacity = '0'; commentEl.style.maxHeight = '0'; commentEl.style.overflow = 'hidden'; }); setTimeout(function () { commentEl.remove(); var countEl = document.querySelector('.comments-count'); if (countEl) { var m = (countEl.textContent || '').match(/(\d+)/); if (m) { var n = Math.max(0, parseInt(m[1], 10) - 1); countEl.textContent = (countEl.textContent || '').replace(m[1], String(n)); } } }, 300); } else { notify((result.data && result.data.message) || result.message || '删除失败', 'error'); } }) .catch(function () { notify('网络错误', 'error'); }); }; if (window.aksrt && aksrt.confirm) { aksrt.confirm(msg).then(function (ok) { if (ok) doDelete(); }); } else if (window.confirm(msg)) { doDelete(); } } function handlePin(commentEl, btn, commentId) { var nextPinned = btn.getAttribute('data-pinned') !== '1'; restPost('/comment/pin', { comment_id: commentId, pinned: nextPinned }) .then(function (result) { if (!result.success) { notify((result.data && result.data.message) || result.message || '操作失败', 'error'); return; } var pinned = !!(result.data && result.data.pinned); btn.setAttribute('data-pinned', pinned ? '1' : '0'); btn.innerHTML = ' ' + (pinned ? '取消置顶' : '置顶'); commentEl.classList.toggle('aksrt-comment--pinned', pinned); syncPinnedBadge(commentEl, pinned); notify(result.message || (pinned ? '评论已置顶。' : '已取消置顶。'), 'success'); }) .catch(function () { notify('网络错误', 'error'); }); } function syncPinnedBadge(commentEl, pinned) { var header = commentEl.querySelector('.aksrt-comment__header, .comment-header'); if (!header) return; var badge = header.querySelector('.aksrt-comment__badge--pinned'); if (pinned && !badge) { badge = document.createElement('span'); badge.className = 'aksrt-comment__badge aksrt-comment__badge--pinned'; badge.innerHTML = ' 置顶'; var time = header.querySelector('.aksrt-comment__time'); header.insertBefore(badge, time || null); } else if (!pinned && badge) { badge.remove(); } } function handleModerate(commentEl, commentId, action) { restPost('/comment/moderate', { comment_id: commentId, status: action }) .then(function (result) { if (result.success) { commentEl.setAttribute('data-comment-status', result.data.status === 'approved' ? '1' : (result.data.status === 'spam' ? 'spam' : '0')); commentEl.style.opacity = '0.6'; setTimeout(function () { commentEl.style.opacity = '1'; }, 400); if (action === 'approve') { var note = commentEl.querySelector('.awaiting-moderation'); if (note) note.remove(); } notify(result.message || '操作成功。', 'success'); } else { notify((result.data && result.data.message) || result.message || '操作失败', 'error'); } }) .catch(function () { notify('网络错误', 'error'); }); } })();