diff --git a/assets/css/build/public.css b/assets/css/build/public.css
index 50256f65..3aadba99 100644
--- a/assets/css/build/public.css
+++ b/assets/css/build/public.css
@@ -2504,6 +2504,11 @@ main.container,
--bs-gutter-y: 0;
}
+/* Header multifunc icon cards must align with the main content edges:
+ * the global .container is padding-free at >=992px (dual-column float
+ * math), so the card container gets no extra gutter either. The
+ * <=991.98px media rule below restores 15px for every .container. */
+
/* Header multifunc icon cards: pin typography baseline so cards render
* identically on all pages. .px12-sm is only defined globally in
* legacy-utilities.css, which is @import'd by the single-post aggregator;
diff --git a/assets/js/report.js b/assets/js/report.js
index 4bb3d6d9..8030d6d2 100644
--- a/assets/js/report.js
+++ b/assets/js/report.js
@@ -1,7 +1,9 @@
/**
- * AKSRT Report — 前台举报提交
+ * AKSRT Report — 前台统一举报弹窗
*
- * 监听 [data-aksrt-report] 按钮点击,弹出举报表单并提交。
+ * 监听 [data-aksrt-report] 点击,弹出对齐参考主题格式的举报弹窗:
+ * 红色警示头 + 举报原因徽章(含“其它原因”)+ 违规链接 + 详细描述 + 取消/确认提交。
+ * 提交走 REST /aksrt/v1/report/submit(user/comment/post 三类目标统一入口)。
*
* @package AKSRT
*/
@@ -14,130 +16,218 @@
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
return String(raw).replace(/\/$/, '');
})();
- var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
+ var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
+
+ var TARGET_TITLES = {
+ user: '举报用户',
+ post: '举报文章',
+ comment: '举报评论',
+ };
function getReportCategories() {
- // Default categories; can be overridden by server-rendered config
- if (window.aksrtReportCategories && Array.isArray(window.aksrtReportCategories)) {
- return window.aksrtReportCategories;
+ // 预置原因:后台“用户&互动 → 禁封/举报”配置(aksrtData.reportCategories)。
+ var list = window.aksrtData && window.aksrtData.reportCategories;
+ if (Array.isArray(list)) {
+ var items = list
+ .map(function (cat) {
+ return {
+ slug: String((cat && (cat.slug || cat.value)) || ''),
+ label: String((cat && (cat.label || cat.name)) || ''),
+ };
+ })
+ .filter(function (cat) { return cat.slug !== '' && cat.label !== ''; });
+ if (items.length) return items;
}
return [
- { value: 'spam', label: '垃圾信息' },
- { value: 'abuse', label: '人身攻击' },
- { value: 'copyright', label: '版权投诉' },
- { value: 'other', label: '其他' },
+ { slug: 'spam', label: '垃圾信息' },
+ { slug: 'abuse', label: '人身攻击' },
+ { slug: 'copyright', label: '版权投诉' },
];
}
- function createReportModal(data) {
- // Remove existing modal if any
- var existing = document.getElementById('aksrt-report-modal');
- if (existing) existing.remove();
+ function escapeHtml(value) {
+ return String(value == null ? '' : value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+ function buildReportModalHtml(data) {
var categories = getReportCategories();
+ var title = TARGET_TITLES[data.target_type] || '举报不良信息';
- var overlay = document.createElement('div');
- overlay.id = 'aksrt-report-modal';
- overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;';
+ var html = '';
- var modal = document.createElement('div');
- modal.style.cssText = 'background:#fff;border-radius:8px;padding:24px;max-width:420px;width:90%;box-shadow:0 4px 20px rgba(0,0,0,0.15);';
+ // 弹窗头:参考主题红色警示样式。
+ html += '
';
+ html += '' + escapeHtml(title) + '';
+ html += '';
+ html += '
';
- var title = document.createElement('h3');
- title.textContent = '提交举报';
- title.style.cssText = 'margin:0 0 16px;font-size:16px;font-weight:600;';
- modal.appendChild(title);
+ html += '';
- var submitBtn = document.createElement('button');
- submitBtn.type = 'button';
- submitBtn.textContent = '提交举报';
- submitBtn.style.cssText = 'padding:8px 16px;border:none;border-radius:4px;background:#e74c3c;color:#fff;cursor:pointer;';
- submitBtn.onclick = function () {
- var reason = reasonInput.value.trim();
- if (!reason) {
- reasonInput.style.borderColor = '#e74c3c';
- reasonInput.focus();
+ return html;
+ }
+
+ function selectedReason(shell) {
+ var active = shell.querySelector('[data-report-reason].is-active');
+ return active ? active.getAttribute('data-report-reason') : '';
+ }
+
+ function submitReport(session, data, shell) {
+ var form = shell.querySelector('.aksrt-report-form');
+ if (!form) return;
+
+ var submitBtn = form.querySelector('[data-aksrt-report-submit]');
+ var descInput = form.querySelector('[name="report_desc"]');
+ var otherInput = form.querySelector('[name="reason_other"]');
+ var urlInput = form.querySelector('[name="report_url"]');
+
+ var slug = selectedReason(shell);
+ var reason = (descInput && descInput.value || '').trim();
+ var other = (otherInput && otherInput.value || '').trim();
+
+ if (!slug && !reason) {
+ notyf('请选择或输入举报原因', 'danger');
+ return;
+ }
+
+ // “其它原因”时以自定义文本为举报原因。
+ if (slug === 'other') {
+ slug = '';
+ reason = other || reason;
+ }
+ if (!reason) {
+ // 徽章已选但没有描述时,将徽章文案作为举报原因(参考主题同款语义)。
+ var activeBtn = shell.querySelector('[data-report-reason].is-active');
+ reason = activeBtn ? (activeBtn.textContent || '').trim() : '';
+ }
+ var link = (urlInput && urlInput.value || '').trim();
+ if (link) {
+ reason += (reason ? '\n' : '') + '违规链接:' + link;
+ }
+
+ if (!reason) {
+ notyf('请选择或输入举报原因', 'danger');
+ return;
+ }
+
+ submitBtn.disabled = true;
+ notyf('正在提交举报…', 'load', 3000, 'aksrt-report-submit');
+
+ window.fetch(API_ROOT + '/report/submit', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': NONCE,
+ },
+ body: JSON.stringify({
+ target_type: data.target_type,
+ target_id: data.target_id,
+ category: slug,
+ reason: reason,
+ }),
+ })
+ .then(function (res) { return res.json(); })
+ .then(function (result) {
+ if (result && result.success) {
+ window.AksrtModalRuntime.close('submit');
+ notyf(result.message || '举报已提交,我们会尽快处理', 'success');
+ } else {
+ submitBtn.disabled = false;
+ notyf((result && result.message) || '举报提交失败', 'danger');
+ }
+ })
+ .catch(function () {
+ submitBtn.disabled = false;
+ notyf('网络错误,请重试', 'danger');
+ })
+ .then(function () {
+ var toast = document.getElementById('aksrt-report-submit');
+ if (toast) toast.remove();
+ });
+ }
+
+ function openReportModal(data) {
+ if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') {
+ notyf('举报组件加载失败,请刷新页面后重试', 'danger');
+ return;
+ }
+
+ var shell = window.AksrtModalRuntime.open({
+ html: buildReportModalHtml(data),
+ className: 'aksrt-report-modal modal-mini',
+ kind: 'report',
+ mobileBottom: true,
+ });
+
+ shell.addEventListener('click', function (event) {
+ var chip = event.target.closest('[data-report-reason]');
+ if (chip) {
+ event.preventDefault();
+ var wasActive = chip.classList.contains('is-active');
+ shell.querySelectorAll('[data-report-reason].is-active').forEach(function (el) {
+ el.classList.remove('is-active');
+ });
+ if (!wasActive) chip.classList.add('is-active');
+ var otherBox = shell.querySelector('.aksrt-report-other');
+ if (otherBox) otherBox.hidden = chip.getAttribute('data-report-reason') !== 'other' || wasActive;
return;
}
- submitBtn.disabled = true;
- submitBtn.textContent = '提交中…';
-
- fetch(API_ROOT + '/report/submit', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-WP-Nonce': NONCE,
- },
- body: JSON.stringify({
- target_type: data.target_type,
- target_id: data.target_id,
- category: catSelect.value,
- reason: reason,
- }),
- })
- .then(function (res) { return res.json(); })
- .then(function (result) {
- overlay.remove();
- if (result.success) {
- notyf(result.message || '举报已提交');
- } else {
- notyf(result.message || '提交失败', 'danger');
- }
- })
- .catch(function () {
- submitBtn.disabled = false;
- submitBtn.textContent = '提交举报';
- notyf('网络错误,请重试', 'danger');
- });
- };
- btnRow.appendChild(submitBtn);
- modal.appendChild(btnRow);
-
- overlay.appendChild(modal);
- overlay.addEventListener('click', function (e) {
- if (e.target === overlay) overlay.remove();
+ var submitBtn = event.target.closest('[data-aksrt-report-submit]');
+ if (submitBtn) {
+ event.preventDefault();
+ submitReport({ target_type: data.target_type, target_id: data.target_id }, data, shell);
+ }
});
-
- document.body.appendChild(overlay);
- reasonInput.focus();
}
function initReportButtons() {
@@ -151,7 +241,7 @@
try {
var data = JSON.parse(btn.getAttribute('data-aksrt-report'));
if (data && data.target_type && data.target_id) {
- createReportModal(data);
+ openReportModal(data);
}
} catch {
// invalid data attribute
diff --git a/inc/helpers/aksrt-single-components.php b/inc/helpers/aksrt-single-components.php
index ffa74da5..27ef0954 100644
--- a/inc/helpers/aksrt-single-components.php
+++ b/inc/helpers/aksrt-single-components.php
@@ -137,7 +137,12 @@ if (!function_exists('aksrt_get_single_meta_box')) :
}
$more .= '';
if (is_user_logged_in()) {
- $more .= '';
+ $report_payload = wp_json_encode([
+ 'target_type' => 'post',
+ 'target_id' => (int) $post_id,
+ 'target_name' => get_the_title($post_id),
+ ]);
+ $more .= '';
} else {
$more .= '' . esc_html__('登录后举报', 'aksrt') . '';
}
diff --git a/inc/helpers/comments/item.php b/inc/helpers/comments/item.php
index f9497456..53382cc8 100644
--- a/inc/helpers/comments/item.php
+++ b/inc/helpers/comments/item.php
@@ -147,6 +147,8 @@ if (!function_exists('aksrt_get_comment_report_html')) :
/**
* 评论举报入口 — 登录用户可举报他人评论(前台走 report.js 的 data-aksrt-report 契约)。
*
+ * 举报位于“回复”右侧三点下拉菜单内(参考主题同位置)。
+ *
* @param WP_Comment $comment Comment.
* @return string
*/
@@ -169,7 +171,14 @@ if (!function_exists('aksrt_get_comment_report_html')) :
return '';
}
- return '';
+ $report_payload = wp_json_encode([
+ 'target_type' => 'comment',
+ 'target_id' => (int) $comment->comment_ID,
+ 'target_name' => get_comment_author($comment),
+ 'target_avatar' => get_avatar_url((int) $comment->user_id > 0 ? (int) $comment->user_id : 0, ['size' => 40]) ?: '',
+ ]);
+
+ return '';
}
endif;
diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php
index 5ac8321e..b1061e83 100644
--- a/src/Core/ScriptsPart1b.php
+++ b/src/Core/ScriptsPart1b.php
@@ -277,9 +277,9 @@ trait ScriptsPart1b {
// 评论区样式由上方 is_singular 统一入口按 needs_comments 加载。
}
- // 用户中心 / 公开主页:路由专属 CSS
- if ($is_profile_page) {
- // 举报脚本(用户主页和个人中心均需要)
+ // 举报脚本:举报入口渲染于文章页 meta 菜单、评论区与用户主页/个人中心,
+ // 因此 singular 页面与 profile 路由都需要加载(文章页举报按钮此前无脚本支撑)。
+ if (is_singular() || $is_profile_page) {
$report_js_path = get_template_directory() . '/assets/js/report.js';
if (file_exists($report_js_path)) {
wp_enqueue_script(
@@ -290,7 +290,10 @@ trait ScriptsPart1b {
true
);
}
+ }
+ // 用户中心 / 公开主页:路由专属 CSS
+ if ($is_profile_page) {
// 关注脚本已在全局栈注册(ScriptsPart1c),data-aksrt-follow 按钮
// 也渲染于文章页作者栏与各处用户卡片,不能仅限本路由加载。
diff --git a/src/Core/ScriptsPart1c.php b/src/Core/ScriptsPart1c.php
index 47478f2d..737c1758 100644
--- a/src/Core/ScriptsPart1c.php
+++ b/src/Core/ScriptsPart1c.php
@@ -487,6 +487,14 @@ trait ScriptsPart1c {
'restUrl' => esc_url_raw(rest_url('aksrt/v1')),
// Alias used by modal-auth / follow / report (same value as restUrl).
'apiUrl' => esc_url_raw(rest_url('aksrt/v1')),
+ // 举报弹窗预置原因(report.js 徽章选择数据源)。
+ 'reportCategories' => (function () {
+ if (!class_exists('\AKSRT\Settings\Settings')) {
+ return [];
+ }
+ $report = \AKSRT\Settings\Settings::get_user_identity_settings()['report'] ?? [];
+ return is_array($report['categories'] ?? null) ? array_values($report['categories']) : [];
+ })(),
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s']) : true,
'stickyPages' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s'] : ['home', 'cat'],
'badgeShow' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['list_badge_show']) : true,
diff --git a/src/Settings/UserRuntime.php b/src/Settings/UserRuntime.php
index d2f893a4..aef2d9c4 100644
--- a/src/Settings/UserRuntime.php
+++ b/src/Settings/UserRuntime.php
@@ -173,7 +173,8 @@ class UserRuntime {
}, $medal['items_by_slug'])),
],
'report' => [
- 'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? false),
+ // 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
+ 'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
'public_notice' => !isset($settings['user_report_public_notice']) || !empty($settings['user_report_public_notice']),
'ban_notice_text' => $ban_notice_text !== '' ? $ban_notice_text : __('当前账号处于限制状态,部分互动能力已关闭。', 'aksrt'),
@@ -252,7 +253,8 @@ class UserRuntime {
public static function report_settings(): array {
$settings = Settings::get_all();
return [
- 'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? false),
+ // 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
+ 'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
'public_notice' => !isset($settings['user_report_public_notice']) || !empty($settings['user_report_public_notice']),
'ban_notice_text' => sanitize_text_field((string) ($settings['user_report_ban_notice_text'] ?? '')),
diff --git a/templates/parts/user/author-hero.php b/templates/parts/user/author-hero.php
index 725fa803..7e92045d 100644
--- a/templates/parts/user/author-hero.php
+++ b/templates/parts/user/author-hero.php
@@ -80,7 +80,16 @@ $unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0
diff --git a/tests/widget-system/report-ban-contract.test.cjs b/tests/widget-system/report-ban-contract.test.cjs
index 4ff47f95..101402d4 100644
--- a/tests/widget-system/report-ban-contract.test.cjs
+++ b/tests/widget-system/report-ban-contract.test.cjs
@@ -170,10 +170,16 @@ test('native ban page mirrors the reference theme layout', () => {
})
test('comments expose a report entry for other users when reporting is enabled', () => {
- assert.match(commentItem, /data-aksrt-report=\\?'\{"target_type":"comment","target_id":/)
+ // 举报位于“回复”右侧三点下拉菜单内(参考主题同位置),载荷走 wp_json_encode。
+ assert.match(commentItem, /data-aksrt-report="/)
assert.match(commentItem, /function aksrt_get_comment_report_html/)
+ assert.match(commentItem, /comment-report-link/)
assert.match(commentItem, /report'\]\['enabled'\]/)
assert.match(commentItem, /\$author_id === \$reporter_id/)
+ // 下拉菜单(置顶/编辑/批准/删除 + 举报)包含举报项。
+ assert.match(commentItem, /function aksrt_get_comments_action_dropdown_html[\s\S]*?\$lists \.= aksrt_get_comment_report_html/)
+ // 举报不作为 comt-meta 独立按钮直接渲染。
+ assert.doesNotMatch(commentItem, /\$report\s*=\s*aksrt_get_comment_report_html\(\$comment\)/)
})
test('uninstall cleans up every ban/report meta key written by the theme', () => {