diff --git a/assets/js/comment-ajax.js b/assets/js/comment-ajax.js
index ab3a1360..7a4a8ac4 100644
--- a/assets/js/comment-ajax.js
+++ b/assets/js/comment-ajax.js
@@ -28,7 +28,7 @@
if (!window.aksrtData) return;
var COMMENT_SELECTOR = '.aksrt-comment, .comment';
- var CONTENT_SELECTOR = '.aksrt-comment__text';
+ var CONTENT_SELECTOR = '.aksrt-comment__text, .comment-content';
var REST_BASE = (aksrtData.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1';
var REST_NONCE = aksrtData.restNonce || '';
@@ -174,7 +174,17 @@
commentEl.style.maxHeight = '0';
commentEl.style.overflow = 'hidden';
});
- setTimeout(function () { commentEl.remove(); }, 300);
+ 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');
}
@@ -206,7 +216,7 @@
}
function syncPinnedBadge(commentEl, pinned) {
- var header = commentEl.querySelector('.aksrt-comment__header');
+ var header = commentEl.querySelector('.aksrt-comment__header, .comment-header');
if (!header) return;
var badge = header.querySelector('.aksrt-comment__badge--pinned');
if (pinned && !badge) {
diff --git a/assets/js/core/comments.js b/assets/js/core/comments.js
index aaa3810b..1670db93 100644
--- a/assets/js/core/comments.js
+++ b/assets/js/core/comments.js
@@ -205,7 +205,8 @@
setLoading(true);
var formData = collectFormData();
- var restBase = '/wp-json/aksrt/v1/comment/submit';
+ 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 = {};
@@ -407,57 +408,150 @@
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 (!listContainer) {
- // 降级:整页刷新
+ if (!commentsArea && !listContainer) {
window.location.reload();
return;
}
+ if (listContainer) {
+ listContainer.innerHTML = '
';
+ }
- 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();
- });
+ 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();
+ });
}
/* ====== 评论排序(最新 / 最早) ====== */
@@ -486,63 +580,21 @@
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");
- var fetchUrl = new URL(url.toString());
- fetchUrl.searchParams.set("_cache_bust", Date.now().toString());
+ if (authorOnly) url.searchParams.set("aksrt_author_only", "1");
+ else url.searchParams.delete("aksrt_author_only");
- 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");
+ fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 })
+ .then(function (json) {
+ if (json && json.success && json.data) {
+ applyFragmentPayload(commentsArea, json.data, url.toString());
+ return;
}
-
- 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');
+ throw new Error("order fragment failed");
})
.catch(function (error) {
console.error("[AKSRT Comment] 排序刷新失败:", error);
@@ -550,99 +602,23 @@
});
}
- 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) {
+ function applyAuthorFilter(authorOnlyFlag) {
renderLoadingState();
-
+ var authorOnly = authorOnlyFlag === "1" || authorOnlyFlag === 1 || authorOnlyFlag === true;
+ var order = getCurrentCommentOrder();
var url = new URL(window.location.href);
- if (authorOnly === "1") {
- url.searchParams.set("aksrt_author_only", "1");
- } else {
- url.searchParams.delete("aksrt_author_only");
- }
+ if (authorOnly) 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");
- }
+ if (order) url.searchParams.set("aksrt_comment_order", order);
- 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);
- }
+ fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 })
+ .then(function (json) {
+ if (json && json.success && json.data) {
+ applyFragmentPayload(commentsArea, json.data, url.toString());
+ return;
}
-
- 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');
+ throw new Error("author fragment failed");
})
.catch(function (error) {
console.error("[AKSRT Comment] Author filter failed:", error);
diff --git a/assets/js/core/pagination.js b/assets/js/core/pagination.js
index 431c090a..8b77df71 100644
--- a/assets/js/core/pagination.js
+++ b/assets/js/core/pagination.js
@@ -29,8 +29,13 @@
if (target && target.classList && target.classList.contains("content-stream__footer")) {
target = target.previousElementSibling;
}
+ if (isComment) {
+ var commentList = document.getElementById("comment-list") ||
+ (container.parentElement && container.parentElement.querySelector("#comment-list, .commentlist, ol.commentlist"));
+ if (commentList) target = commentList;
+ }
if (!target && container.parentElement) {
- target = container.parentElement.querySelector(".post-grid, .comment-list, .content-stream__list");
+ target = container.parentElement.querySelector(".post-grid, .comment-list, .commentlist, .content-stream__list");
}
function showSkeleton() {
@@ -79,6 +84,8 @@
if (isComment && postId) {
body.set("post_id", postId);
body.set("comment_order", commentOrder);
+ var authorOnly = container.dataset.authorOnly || "0";
+ if (authorOnly === "1") body.set("author_only", "1");
}
fetch(aksrtData.ajaxUrl, {
@@ -98,6 +105,9 @@
while (newItems.length > 0) {
target.appendChild(newItems[0]);
}
+ if (isComment && window.AksrtFrontend) {
+ AksrtFrontend.rehydrate(target, "ajax");
+ }
nextPage++;
container.dataset.nextPage = String(nextPage);
diff --git a/inc/classes/class-aksrt-admin.php b/inc/classes/class-aksrt-admin.php
index 25dcf8cb..2549d3c5 100644
--- a/inc/classes/class-aksrt-admin.php
+++ b/inc/classes/class-aksrt-admin.php
@@ -268,41 +268,28 @@ class AKSRT_Admin {
wp_send_json_error(['message' => __('无效的文章 ID', 'aksrt')]);
}
- $settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_comment_display_settings() : [];
- $per_page = max(1, (int) get_option('comments_per_page', 10));
- $comment_query = new WP_Comment_Query([
- 'post_id' => $post_id,
- 'status' => 'approve',
- 'number' => $per_page,
- 'offset' => ($page - 1) * $per_page,
- 'orderby' => 'comment_date_gmt',
- 'order' => !empty($settings['order']) && strtoupper((string) $settings['order']) === 'ASC' ? 'ASC' : 'DESC',
- ]);
- $comments = $comment_query->comments;
+ $order = sanitize_key((string) ($_POST['comment_order'] ?? $_POST['order'] ?? 'newest'));
+ $author_only = !empty($_POST['author_only']) || (isset($_POST['aksrt_author_only']) && (int) $_POST['aksrt_author_only'] === 1);
- ob_start();
- if (!empty($comments) && function_exists('aksrt_comment_callback')) {
- set_query_var('aksrt_comment_settings', $settings);
- wp_list_comments([
- 'callback' => 'aksrt_comment_callback',
- 'style' => 'ul',
- 'avatar_size' => max(24, (int) ($settings['avatar_size'] ?? 48)),
- 'max_depth' => max(1, (int) ($settings['nested_depth'] ?? 3)),
- ], $comments);
+ if (!function_exists('aksrt_comments_query_for_post') || !function_exists('aksrt_comments_render_items_html')) {
+ wp_send_json_error(['message' => __('评论加载不可用', 'aksrt')]);
}
- $html = ob_get_clean();
- $total = get_comments([
- 'post_id' => $post_id,
- 'status' => 'approve',
- 'count' => true,
+ $result = aksrt_comments_query_for_post($post_id, [
+ 'page' => $page,
+ 'order' => $order,
+ 'author_only' => $author_only,
]);
- $max_pages = (int) ceil(((int) $total) / $per_page);
+
+ $html = aksrt_comments_render_items_html($result['comments'], $result['settings'], $result['order']);
wp_send_json_success([
'html' => $html,
- 'has_more' => $page < $max_pages,
+ 'has_more' => $page < (int) $result['max_pages'],
'page' => $page,
+ 'order' => $result['order'],
+ 'total' => (int) $result['total'],
+ 'max_pages'=> (int) $result['max_pages'],
]);
}
}
diff --git a/inc/classes/class-aksrt-comment-actions.php b/inc/classes/class-aksrt-comment-actions.php
index 10c43f45..4f874eb5 100644
--- a/inc/classes/class-aksrt-comment-actions.php
+++ b/inc/classes/class-aksrt-comment-actions.php
@@ -27,6 +27,24 @@ class AKSRT_Comment_Actions {
}
public function register_routes(): void {
+ register_rest_route('aksrt/v1', '/comment/list', [
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => [$this, 'list_comments'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'post_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
+ 'page' => ['required' => false, 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
+ 'order' => ['required' => false, 'type' => 'string', 'default' => 'newest', 'sanitize_callback' => 'sanitize_key'],
+ 'author_only' => ['required' => false, 'type' => 'boolean', 'default' => false],
+ ],
+ ]);
+
+ register_rest_route('aksrt/v1', '/comment/submit', [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'submit_comment'],
+ 'permission_callback' => '__return_true',
+ ]);
+
register_rest_route('aksrt/v1', '/comment/edit', [
'methods' => 'POST',
'callback' => [$this, 'edit_comment'],
@@ -127,6 +145,172 @@ class AKSRT_Comment_Actions {
return $this->permissions((int) $request->get_param('comment_id'))['can_pin'];
}
+
+ /**
+ * Fragment list for order / author / page without full HTML scrape.
+ */
+ public function list_comments(\WP_REST_Request $request): \WP_REST_Response {
+ $post_id = (int) $request->get_param('post_id');
+ $post = get_post($post_id);
+ if (!$post) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'post_not_found',
+ 'message' => __('文章不存在。', 'aksrt'),
+ ], 404);
+ }
+
+ if (!function_exists('aksrt_comments_render_list_fragment')) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'fragment_unavailable',
+ 'message' => __('评论片段渲染不可用。', 'aksrt'),
+ ], 500);
+ }
+
+ $order = sanitize_key((string) ($request->get_param('order') ?: 'newest'));
+ $author_only = (bool) $request->get_param('author_only');
+ $page = max(1, (int) ($request->get_param('page') ?: 1));
+
+ $fragment = aksrt_comments_render_list_fragment($post_id, [
+ 'order' => $order,
+ 'author_only' => $author_only,
+ 'page' => $page,
+ ]);
+
+ return new \WP_REST_Response([
+ 'success' => true,
+ 'data' => $fragment,
+ ]);
+ }
+
+ /**
+ * AJAX submit with HTML item for local insert.
+ * Runs through native WP comment pipeline (preprocess_comment / moderation).
+ */
+ public function submit_comment(\WP_REST_Request $request): \WP_REST_Response {
+ $params = $request->get_json_params();
+ if (!is_array($params) || empty($params)) {
+ $params = $request->get_params();
+ }
+ if (!is_array($params)) {
+ $params = [];
+ }
+
+ $post_id = absint($params['comment_post_ID'] ?? $params['post_id'] ?? 0);
+ $content = isset($params['comment']) ? (string) $params['comment'] : '';
+ $parent = absint($params['comment_parent'] ?? 0);
+
+ if ($post_id <= 0 || trim(wp_strip_all_tags($content)) === '') {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'invalid_payload',
+ 'message' => __('请输入评论内容。', 'aksrt'),
+ ], 400);
+ }
+
+ $post = get_post($post_id);
+ if (!$post || !comments_open($post_id)) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'comments_closed',
+ 'message' => __('评论已关闭。', 'aksrt'),
+ ], 403);
+ }
+
+ $nonce = (string) ($params['_aksrt_comment_nonce'] ?? $params['_wpnonce'] ?? '');
+ if ($nonce !== '' && !wp_verify_nonce($nonce, 'aksrt_comment_nonce') && !wp_verify_nonce($nonce, 'wp_rest')) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'invalid_nonce',
+ 'message' => __('安全校验失败,请刷新后重试。', 'aksrt'),
+ ], 403);
+ }
+
+ $user = wp_get_current_user();
+ $commentdata = [
+ 'comment_post_ID' => $post_id,
+ 'comment_content' => $content,
+ 'comment_parent' => $parent,
+ 'comment_type' => '',
+ 'comment_author_IP' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash((string) $_SERVER['REMOTE_ADDR'])) : '',
+ 'comment_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash((string) $_SERVER['HTTP_USER_AGENT'])) : '',
+ 'comment_date' => current_time('mysql'),
+ 'comment_date_gmt' => current_time('mysql', true),
+ ];
+
+ if ($user && $user->ID > 0) {
+ $commentdata['user_id'] = (int) $user->ID;
+ $commentdata['comment_author'] = $user->display_name ?: $user->user_login;
+ $commentdata['comment_author_email'] = $user->user_email;
+ $commentdata['comment_author_url'] = $user->user_url;
+ } else {
+ $commentdata['user_id'] = 0;
+ $commentdata['comment_author'] = sanitize_text_field((string) ($params['author'] ?? ''));
+ $commentdata['comment_author_email'] = sanitize_email((string) ($params['email'] ?? ''));
+ $commentdata['comment_author_url'] = esc_url_raw((string) ($params['url'] ?? ''));
+ if ($commentdata['comment_author'] === '' || $commentdata['comment_author_email'] === '') {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'guest_fields_required',
+ 'message' => __('请填写昵称和邮箱。', 'aksrt'),
+ ], 400);
+ }
+ }
+
+ foreach (['author', 'email', 'url', 'comment', 'comment_post_ID', 'comment_parent', '_aksrt_comment_nonce'] as $k) {
+ if (isset($params[$k])) {
+ $_POST[$k] = $params[$k];
+ }
+ }
+ foreach ($params as $k => $v) {
+ if (is_string($k) && (str_contains($k, 'captcha') || str_starts_with($k, 'g-recaptcha') || str_starts_with($k, 'cf-turnstile') || str_starts_with($k, 'h-captcha'))) {
+ $_POST[$k] = $v;
+ }
+ }
+
+ $comment_id = wp_new_comment(wp_slash($commentdata), true);
+ if (is_wp_error($comment_id)) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => $comment_id->get_error_code() ?: 'submit_failed',
+ 'message' => $comment_id->get_error_message() ?: __('评论提交失败。', 'aksrt'),
+ ], 400);
+ }
+
+ $comment = get_comment((int) $comment_id);
+ if (!$comment) {
+ return new \WP_REST_Response([
+ 'success' => false,
+ 'code' => 'submit_failed',
+ 'message' => __('评论提交失败。', 'aksrt'),
+ ], 500);
+ }
+
+ $settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_comment_display_settings() : [];
+ $html = function_exists('aksrt_comments_render_single_html')
+ ? aksrt_comments_render_single_html($comment, $settings)
+ : '';
+
+ $needs_moderation = '0' === (string) $comment->comment_approved;
+ $wait_time = 0;
+
+ return new \WP_REST_Response([
+ 'success' => true,
+ 'code' => 'submitted',
+ 'message' => $needs_moderation ? __('评论已提交,等待管理员审核', 'aksrt') : __('评论发布成功', 'aksrt'),
+ 'html' => $html,
+ 'is_reply'=> $parent > 0,
+ 'parent_id' => $parent,
+ 'needs_moderation' => $needs_moderation,
+ 'wait_time' => $wait_time,
+ 'data' => [
+ 'comment_id' => (int) $comment_id,
+ 'approved' => (string) $comment->comment_approved,
+ ],
+ ]);
+ }
+
/**
* Edit comment content
*/
@@ -168,12 +352,16 @@ class AKSRT_Comment_Actions {
], 500);
}
+ $rendered = function_exists('aksrt_render_comment_content')
+ ? aksrt_render_comment_content($clean_content)
+ : wpautop($clean_content);
+
return new \WP_REST_Response([
'success' => true,
'code' => 'updated',
'message' => __('评论已更新。', 'aksrt'),
'data' => [
- 'content' => $clean_content,
+ 'content' => $rendered,
],
]);
}
diff --git a/inc/helpers/aksrt-comments.php b/inc/helpers/aksrt-comments.php
index ccbe1dd0..de3de7eb 100644
--- a/inc/helpers/aksrt-comments.php
+++ b/inc/helpers/aksrt-comments.php
@@ -142,6 +142,259 @@ if (!function_exists('aksrt_comments_apply_hottest_order')) :
}
endif;
+
+if (!function_exists('aksrt_comments_normalize_order')) :
+ /**
+ * Normalize comment order key.
+ */
+ function aksrt_comments_normalize_order(string $order = ''): string {
+ $order = sanitize_key($order);
+ if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
+ $settings = class_exists('AKSRT_Settings')
+ ? AKSRT_Settings::get_comment_display_settings()
+ : [];
+ $order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
+ }
+ if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
+ $order = 'newest';
+ }
+ return $order;
+ }
+endif;
+
+if (!function_exists('aksrt_comments_query_for_post')) :
+ /**
+ * Shared comment query for first paint / fragment / load-more.
+ *
+ * @return array{comments:array,total:int,max_pages:int,page:int,order:string,author_only:bool,settings:array}
+ */
+ function aksrt_comments_query_for_post(int $post_id, array $args = []): array {
+ $settings = class_exists('AKSRT_Settings')
+ ? AKSRT_Settings::get_comment_display_settings()
+ : [];
+ $order = aksrt_comments_normalize_order((string) ($args['order'] ?? ''));
+ $author_only = !empty($args['author_only']);
+ $page = max(1, (int) ($args['page'] ?? 1));
+ $per_page = max(1, (int) ($args['per_page'] ?? get_option('comments_per_page', 10)));
+ $include_unapproved_for_user = (int) ($args['include_unapproved_user'] ?? 0);
+
+ $query_args = [
+ 'post_id' => $post_id,
+ 'status' => 'approve',
+ 'number' => $per_page,
+ 'offset' => ($page - 1) * $per_page,
+ 'orderby' => 'comment_date_gmt',
+ 'order' => $order === 'oldest' ? 'ASC' : 'DESC',
+ 'hierarchical' => 'threaded',
+ ];
+
+ if ($author_only) {
+ $author_id = (int) get_post_field('post_author', $post_id);
+ if ($author_id > 0) {
+ $query_args['user_id'] = $author_id;
+ $query_args['hierarchical'] = false;
+ }
+ }
+
+ if ($include_unapproved_for_user > 0) {
+ // WP_Comment_Query does not support mixed status easily; keep approve only for list fragment.
+ }
+
+ // Hottest uses meta_value_num when meta exists; fall back to date.
+ $hottest_filter = null;
+ if ($order === 'hottest') {
+ $query_args['orderby'] = 'meta_value_num';
+ $query_args['meta_key'] = 'aksrt_comment_likes';
+ $query_args['order'] = 'DESC';
+ // Ensure comments without like meta still appear.
+ $query_args['meta_query'] = [
+ 'relation' => 'OR',
+ [
+ 'key' => 'aksrt_comment_likes',
+ 'compare' => 'EXISTS',
+ ],
+ [
+ 'key' => 'aksrt_comment_likes',
+ 'compare' => 'NOT EXISTS',
+ ],
+ ];
+ // Prefer clauses filter for stable hottest ordering.
+ $hottest_filter = static function ($clauses) {
+ global $wpdb;
+ if (strpos($clauses['join'], 'aksrt_cm_hot') === false) {
+ $clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm_hot ON ({$wpdb->comments}.comment_ID = aksrt_cm_hot.comment_id AND aksrt_cm_hot.meta_key = 'aksrt_comment_likes')";
+ }
+ $clauses['orderby'] = 'CAST(COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC, ' . $wpdb->comments . '.comment_date_gmt DESC';
+ return $clauses;
+ };
+ add_filter('comments_clauses', $hottest_filter, 20);
+ // Prefer date orderby with clauses override.
+ $query_args['orderby'] = 'comment_date_gmt';
+ unset($query_args['meta_key'], $query_args['meta_query']);
+ }
+
+ $comment_query = new WP_Comment_Query($query_args);
+ $comments = is_array($comment_query->comments) ? $comment_query->comments : [];
+
+ if ($hottest_filter) {
+ remove_filter('comments_clauses', $hottest_filter, 20);
+ }
+
+ // Pin reorder for page 1 when not author-only.
+ if ($page === 1 && !$author_only && function_exists('aksrt_reorder_pinned_comments')) {
+ $comments = aksrt_reorder_pinned_comments($comments, $post_id);
+ }
+
+ $count_args = [
+ 'post_id' => $post_id,
+ 'status' => 'approve',
+ 'count' => true,
+ ];
+ if ($author_only) {
+ $author_id = (int) get_post_field('post_author', $post_id);
+ if ($author_id > 0) {
+ $count_args['user_id'] = $author_id;
+ }
+ }
+ $total = (int) get_comments($count_args);
+ $max_pages = (int) max(1, (int) ceil($total / $per_page));
+
+ return [
+ 'comments' => $comments,
+ 'total' => $total,
+ 'max_pages' => $max_pages,
+ 'page' => $page,
+ 'order' => $order,
+ 'author_only' => $author_only,
+ 'settings' => $settings,
+ 'per_page' => $per_page,
+ ];
+ }
+endif;
+
+if (!function_exists('aksrt_comments_render_items_html')) :
+ /**
+ * Render comment list items HTML via canonical callback.
+ *
+ * @param array $comments
+ * @param array $settings
+ * @param string $order
+ */
+ function aksrt_comments_render_items_html(array $comments, array $settings = [], string $order = 'newest'): string {
+ if (empty($comments) || !function_exists('aksrt_comment_callback')) {
+ return '';
+ }
+ set_query_var('aksrt_comment_settings', $settings);
+ set_query_var('aksrt_comment_order', aksrt_comments_normalize_order($order));
+
+ $avatar = max(24, (int) ($settings['avatar_size'] ?? 48));
+ $depth = max(1, (int) ($settings['nested_depth'] ?? 3));
+ $reverse = $order === 'newest';
+
+ ob_start();
+ wp_list_comments([
+ 'callback' => 'aksrt_comment_callback',
+ 'style' => 'ol',
+ 'avatar_size' => $avatar,
+ 'max_depth' => $depth,
+ 'reverse_top_level' => $reverse,
+ 'reverse_children' => $reverse,
+ ], $comments);
+ return (string) ob_get_clean();
+ }
+endif;
+
+if (!function_exists('aksrt_comments_render_single_html')) :
+ /**
+ * Render one comment item HTML.
+ */
+ function aksrt_comments_render_single_html(\WP_Comment $comment, array $settings = []): string {
+ $html = aksrt_comments_render_items_html([$comment], $settings, 'newest');
+ return $html;
+ }
+endif;
+
+if (!function_exists('aksrt_comments_render_list_fragment')) :
+ /**
+ * Fragment payload for order/author/page AJAX.
+ *
+ * @return array{html:string,filter_html:string,pagination_html:string,count_text:string,total:int,max_pages:int,page:int,order:string,author_only:bool,empty:bool}
+ */
+ function aksrt_comments_render_list_fragment(int $post_id, array $args = []): array {
+ $result = aksrt_comments_query_for_post($post_id, $args);
+ $settings = $result['settings'];
+ $order = $result['order'];
+ $author_only = !empty($result['author_only']);
+ $page = (int) $result['page'];
+ $max_pages = (int) $result['max_pages'];
+ $total = (int) $result['total'];
+
+ // Fake globals needed by pagination helpers when not on singular loop.
+ $GLOBALS['post'] = get_post($post_id);
+ setup_postdata($GLOBALS['post']);
+ set_query_var('cpage', $page);
+ set_query_var('aksrt_comment_order', $order);
+ set_query_var('aksrt_comment_settings', $settings);
+
+ $items_html = aksrt_comments_render_items_html($result['comments'], $settings, $order);
+
+ $ctx = [
+ 'order' => $order,
+ 'author_only' => $author_only,
+ 'post_author_id' => (int) get_post_field('post_author', $post_id),
+ 'corderby_enabled' => !empty($settings['corderby']),
+ 'comment_count' => $total,
+ 'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
+ ];
+
+ ob_start();
+ if (function_exists('aksrt_comments_render_filter') && !empty($ctx['corderby_enabled'])) {
+ aksrt_comments_render_filter($ctx);
+ }
+ $filter_html = (string) ob_get_clean();
+
+ ob_start();
+ if ($items_html === '' && function_exists('aksrt_comments_render_empty')) {
+ aksrt_comments_render_empty($ctx);
+ } else {
+ echo '';
+ }
+ $list_html = (string) ob_get_clean();
+
+ ob_start();
+ if (function_exists('aksrt_render_comment_pagination') && $max_pages > 1) {
+ // Temporarily override page counts via filter.
+ $pages_filter = static function () use ($max_pages) {
+ return $max_pages;
+ };
+ add_filter('get_comment_pages_count', $pages_filter);
+ aksrt_render_comment_pagination();
+ remove_filter('get_comment_pages_count', $pages_filter);
+ }
+ $pagination_html = (string) ob_get_clean();
+
+ wp_reset_postdata();
+
+ return [
+ 'html' => $list_html,
+ 'items_html' => $items_html,
+ 'filter_html' => $filter_html,
+ 'pagination_html' => $pagination_html,
+ 'count_text' => sprintf(__('共 %d 条', 'aksrt'), $total),
+ 'total' => $total,
+ 'max_pages' => $max_pages,
+ 'page' => $page,
+ 'order' => $order,
+ 'author_only' => $author_only,
+ 'empty' => $items_html === '',
+ ];
+ }
+endif;
+
+
if (!function_exists('aksrt_comments_render_signarea')) :
/**
* Login-required gate.
diff --git a/inc/helpers/aksrt-frontend-tags.php b/inc/helpers/aksrt-frontend-tags.php
index 8e3f03eb..d3e780a3 100644
--- a/inc/helpers/aksrt-frontend-tags.php
+++ b/inc/helpers/aksrt-frontend-tags.php
@@ -1063,7 +1063,7 @@ if (!function_exists('aksrt_comment_callback')) :
id="comment-" data-comment-id="" data-comment-status="comment_approved); ?>">