From 0e8c9d12c7e85e7d00ecbeb3ffc24e176ce2d273 Mon Sep 17 00:00:00 2001
From: unknown
Date: Tue, 21 Jul 2026 14:36:00 +0800
Subject: [PATCH 1/4] feat(widgets): plate moderator apply modal + JS submit
Replace bare apply link with modal form posting to forum moderator
REST; empty/admin states and modal CSS.
---
assets/css/frontend/widget-batch5.css | 35 ++++++
assets/js/widget-set.js | 85 ++++++++++++++
.../Widgets/WidgetForumPlateModerator.php | 111 ++++++++++++++----
3 files changed, 207 insertions(+), 24 deletions(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index b5a663a4..eb500440 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -2565,3 +2565,38 @@ a.main-color > .icon-cover-card:hover,
z-index: 2;
}
.avatar-img.link-img img.aksrt-lazy { object-fit: cover; }
+
+
+/* batch49-51 residual */
+.aksrt-mod-apply-modal[hidden] { display: none !important; }
+.aksrt-mod-apply-modal {
+ position: fixed; inset: 0; z-index: 10050;
+ display: flex; align-items: center; justify-content: center;
+}
+.aksrt-mod-apply-modal__backdrop {
+ position: absolute; inset: 0; background: rgba(15, 23, 42, .45);
+}
+.aksrt-mod-apply-modal__dialog {
+ position: relative; z-index: 1; width: min(420px, calc(100vw - 32px));
+ max-height: min(80vh, 560px); overflow: auto;
+ padding: 16px; border-radius: 12px; background: var(--surface-1, #fff);
+ box-shadow: 0 18px 50px rgba(0,0,0,.2);
+}
+.aksrt-mod-apply-modal__dialog textarea {
+ width: 100%; min-height: 96px; border: 1px solid var(--color-line, #e5e7eb);
+ border-radius: 8px; padding: 8px 10px; resize: vertical;
+}
+.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-error { color: #ef4444; }
+.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-success { color: #10b981; }
+.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-loading { color: #64748b; }
+body.aksrt-mod-apply-open { overflow: hidden; }
+.aksrt-product-widget__item--list { gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--color-line, rgba(0,0,0,.06)); }
+.aksrt-product-widget__item--list:last-child { border-bottom: 0; }
+.aksrt-product-widget__thumb.list {
+ flex: 0 0 80px; width: 80px; height: 80px; border-radius: 10px; overflow: hidden;
+ background: var(--surface-2, #f3f4f6); display: block;
+}
+.aksrt-product-widget__thumb.list img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.text-ellipsis-2 {
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
+}
diff --git a/assets/js/widget-set.js b/assets/js/widget-set.js
index 4bbdad30..aea86eeb 100644
--- a/assets/js/widget-set.js
+++ b/assets/js/widget-set.js
@@ -42,6 +42,9 @@
tab_count: tabCount,
style: style,
paginate: paginate,
+ topics: tab.getAttribute('data-topics') || '',
+ limit_day: tab.getAttribute('data-limit-day') || '0',
+ pay_type: tab.getAttribute('data-pay-type') || '',
});
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString();
panel.classList.add('is-ajax-loading');
@@ -379,6 +382,87 @@
});
}
+
+ function initModeratorApplyModal() {
+ document.addEventListener('click', function (e) {
+ var openBtn = e.target.closest('[data-aksrt-mod-apply-open]');
+ if (openBtn) {
+ e.preventDefault();
+ var id = openBtn.getAttribute('data-aksrt-mod-apply-open');
+ var modal = id ? document.getElementById(id) : null;
+ if (modal) {
+ modal.hidden = false;
+ document.body.classList.add('aksrt-mod-apply-open');
+ }
+ return;
+ }
+ var closeBtn = e.target.closest('[data-aksrt-mod-apply-close]');
+ if (closeBtn) {
+ var box = closeBtn.closest('.aksrt-mod-apply-modal');
+ if (box) {
+ box.hidden = true;
+ document.body.classList.remove('aksrt-mod-apply-open');
+ }
+ }
+ });
+
+ document.querySelectorAll('.js-aksrt-moderator-apply-form').forEach(function (form) {
+ if (form.dataset.aksrtModApplyBound === '1') return;
+ form.dataset.aksrtModApplyBound = '1';
+ form.addEventListener('submit', function (event) {
+ event.preventDefault();
+ var boardId = parseInt(form.getAttribute('data-board-id') || '0', 10);
+ var reasonField = form.querySelector('[name="reason"]');
+ var reason = reasonField ? reasonField.value.trim() : '';
+ var feedback = form.querySelector('.js-aksrt-action-feedback');
+ var submitButton = form.querySelector('button[type="submit"]');
+ if (!boardId || !reason) {
+ if (feedback) {
+ feedback.textContent = '请填写完整的申请理由。';
+ feedback.className = 'js-aksrt-action-feedback mb10 is-error';
+ }
+ return;
+ }
+ if (submitButton) submitButton.disabled = true;
+ if (feedback) {
+ feedback.textContent = '正在提交申请…';
+ feedback.className = 'js-aksrt-action-feedback mb10 is-loading';
+ }
+ var siteUrl = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/+$/, '') : '';
+ var url = siteUrl + '/wp-json/aksrt/v1/forum/moderator/apply';
+ fetch(url, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ 'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || '',
+ },
+ body: JSON.stringify({ board_id: boardId, reason: reason }),
+ })
+ .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
+ .then(function (res) {
+ if (!res.ok || (res.j && res.j.success === false)) {
+ throw new Error((res.j && res.j.message) || '申请提交失败');
+ }
+ var msg = (res.j && (res.j.message || (res.j.data && res.j.data.message))) || '申请已提交';
+ if (feedback) {
+ feedback.textContent = msg;
+ feedback.className = 'js-aksrt-action-feedback mb10 is-success';
+ }
+ if (reasonField) reasonField.disabled = true;
+ })
+ .catch(function (err) {
+ if (feedback) {
+ feedback.textContent = (err && err.message) || '申请提交失败。';
+ feedback.className = 'js-aksrt-action-feedback mb10 is-error';
+ }
+ if (submitButton) submitButton.disabled = false;
+ });
+ });
+ });
+ }
+
function initFloatingQRCodes() {
var wrap = document.querySelector('.float-right');
if (!wrap) return;
@@ -424,6 +508,7 @@
initProductTabs();
initMainPostsLazy();
initFeatureIntro();
+ initModeratorApplyModal();
initFloatingQRCodes();
}
diff --git a/src/AKSRT/Widgets/WidgetForumPlateModerator.php b/src/AKSRT/Widgets/WidgetForumPlateModerator.php
index ff9bfb27..444fe7c9 100644
--- a/src/AKSRT/Widgets/WidgetForumPlateModerator.php
+++ b/src/AKSRT/Widgets/WidgetForumPlateModerator.php
@@ -22,6 +22,70 @@ class WidgetForumPlateModerator extends \WP_Widget {
);
}
+ /**
+ * Build apply entry HTML (modal form).
+ */
+ public static function render_apply_html(int $plate_id, bool $show_apply = true): string {
+ if (!$show_apply || $plate_id <= 0) {
+ return '';
+ }
+ $forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
+ $cuid = get_current_user_id();
+ $show_always = true;
+ if (function_exists('aksrt_get_setting')) {
+ $show_always = (bool) aksrt_get_setting('bbs_show_apply_moderator', true);
+ }
+ $can_apply = $cuid > 0 && $forum && method_exists($forum, 'forum_user_can')
+ ? $forum->forum_user_can($cuid, 'moderator_apply')
+ : ($cuid > 0);
+ if (!$show_always && !$can_apply) {
+ return '';
+ }
+ $is_mod = false;
+ if ($cuid > 0 && $forum && method_exists($forum, 'is_board_moderator')) {
+ $is_mod = $forum->is_board_moderator($cuid, $plate_id);
+ }
+ if ($is_mod || ($cuid > 0 && user_can($cuid, 'manage_options'))) {
+ return '';
+ }
+ if ($cuid <= 0) {
+ $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink($plate_id)) : wp_login_url(get_permalink($plate_id));
+ return '' . esc_html__('登录后申请版主', 'aksrt') . '';
+ }
+ $pending = false;
+ if ($forum && method_exists($forum, 'get_board_moderator_applications')) {
+ foreach ((array) $forum->get_board_moderator_applications($plate_id) as $app) {
+ if ((int) ($app['user_id'] ?? 0) === $cuid && (string) ($app['status'] ?? '') === 'pending') {
+ $pending = true;
+ break;
+ }
+ }
+ }
+ if ($pending) {
+ return '' . esc_html__('申请审核中', 'aksrt') . '';
+ }
+ $uid = 'aksrt-mod-apply-' . $plate_id . '-' . wp_rand(1000, 9999);
+ $desc = function_exists('aksrt_get_setting') ? (string) aksrt_get_setting('bbs_apply_moderator_desc', '') : '';
+ if ($desc === '') {
+ $desc = __('欢迎有热情的用户申请成为版主,协助管理社区秩序。', 'aksrt');
+ }
+ obs_start();
+ echo '';
+ echo '';
+ return (string) ob_get_clean();
+ }
+
/**
* @return array{html:string}
*/
@@ -62,10 +126,6 @@ class WidgetForumPlateModerator extends \WP_Widget {
}
$people[] = ['id' => $mid, 'role' => 'moderator'];
}
- if (!$people) {
- return ['html' => ''];
- }
-
$title_text = (string) ($instance['title_text'] ?? __('版主', 'aksrt'));
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
@@ -73,31 +133,30 @@ class WidgetForumPlateModerator extends \WP_Widget {
$style = 'list';
}
- $show_apply = !isset($instance['apply_btn']) || !empty($instance['apply_btn']);
- $apply_html = '';
- if ($show_apply && is_user_logged_in()) {
- $forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
- $cuid = get_current_user_id();
- $can_apply = $forum && method_exists($forum, 'forum_user_can')
- ? $forum->forum_user_can($cuid, 'moderator_apply')
- : true;
- $is_mod = $forum && method_exists($forum, 'is_board_moderator')
- ? $forum->is_board_moderator($cuid, $id)
- : in_array($cuid, array_merge([$author_id], $mods, $super), true);
- if ($can_apply && !$is_mod) {
- $apply_url = add_query_arg(['aksrt_apply_moderator' => 1, 'plate' => $id], get_permalink($id));
- $apply_html = '' . esc_html__('申请版主', 'aksrt') . '';
+ $show_apply = !isset($instance['apply_btn']) || !empty($instance['apply_btn']);
+ $apply_html = self::render_apply_html($id, $show_apply);
+
+ if (!$people && $apply_html === '') {
+ if (current_user_can('manage_options')) {
+ return [
+ 'html' => function_exists('aksrt_get_widget_null')
+ ? aksrt_get_widget_null(__('当前版块暂无版主', 'aksrt'))
+ : '' . esc_html__('当前版块暂无版主', 'aksrt') . '
',
+ ];
}
+ return ['html' => ''];
}
- ob_start();
- echo '';
+ return ['html' => (string) ob_get_clean()];
}
public function widget($args, $instance): void {
From 9f485cbe451a51a7f0a54fcaf4fadba0a768f384 Mon Sep 17 00:00:00 2001
From: unknown
Date: Tue, 21 Jul 2026 14:36:01 +0800
Subject: [PATCH 2/4] feat(widgets): per-tab topics/limit_day/pay_type filters
TabPosts each tab can filter topics taxonomy, date window and pay type;
REST and widget-set.js pass the extra query args.
---
src/AKSRT/Core/WidgetAjax.php | 5 ++++-
src/AKSRT/Widgets/TabPostsWidget.php | 25 ++++++++++++++++++++++++-
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/AKSRT/Core/WidgetAjax.php b/src/AKSRT/Core/WidgetAjax.php
index 1a3cdbf8..aa293aee 100644
--- a/src/AKSRT/Core/WidgetAjax.php
+++ b/src/AKSRT/Core/WidgetAjax.php
@@ -93,7 +93,10 @@ class WidgetAjax {
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'count' => max(0, min(20, (int) ($request->get_param('tab_count') ?? 0))),
'enabled' => true,
- ];
+
+ 'topics' => sanitize_text_field((string) ($request->get_param('topics') ?? '')),
+ 'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
+ 'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),];
$opts = [
'style' => $style,
'paginate' => $paginate,
diff --git a/src/AKSRT/Widgets/TabPostsWidget.php b/src/AKSRT/Widgets/TabPostsWidget.php
index 1e9dcdb9..fc42a830 100644
--- a/src/AKSRT/Widgets/TabPostsWidget.php
+++ b/src/AKSRT/Widgets/TabPostsWidget.php
@@ -107,6 +107,9 @@ class TabPostsWidget extends \WP_Widget {
'count' => max(1, min(30, $count)),
'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')),
'cat' => absint($tab['category_id'] ?? 0),
+ 'topics' => sanitize_text_field((string) ($tab['topics'] ?? '')),
+ 'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
+ 'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
'style' => $style,
'show_thumb' => !isset($opts['show_thumb']) ? 1 : (int) $opts['show_thumb'],
'show_meta' => !isset($opts['show_meta']) ? 1 : (int) $opts['show_meta'],
@@ -213,6 +216,9 @@ class TabPostsWidget extends \WP_Widget {
data-orderby=""
data-cat=""
data-tab-count=""
+ data-topics=""
+ data-limit-day=""
+ data-pay-type=""
aria-selected=""
>
@@ -295,7 +301,7 @@ class TabPostsWidget extends \WP_Widget {
$max_tabs = 8;
$tabs = array_slice($tabs, 0, $max_tabs);
while (count($tabs) < 3) {
- $tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false];
+ $tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'topics' => '', 'limit_day' => 0, 'pay_type' => '', 'enabled' => false];
}
foreach ($tabs as $i => $tab) :
?>
@@ -323,6 +329,23 @@ class TabPostsWidget extends \WP_Widget {
" value="">
+
+
+ " value="">
+
+
+
+ " value="">
+
+
+
+
+
Date: Tue, 21 Jul 2026 14:36:01 +0800
Subject: [PATCH 3/4] feat(widgets): product list vs mini DOM + admin empty
polish
Differentiate product list/mini row sizes; custom HTML and posts-navs
admin empty states via aksrt_get_widget_null.
---
src/AKSRT/Widgets/CustomHtmlGapWidget.php | 4 +++-
src/AKSRT/Widgets/WidgetPostsNavs.php | 7 +++++++
src/AKSRT/Widgets/WidgetProductList.php | 17 ++++++++++-------
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/AKSRT/Widgets/CustomHtmlGapWidget.php b/src/AKSRT/Widgets/CustomHtmlGapWidget.php
index 154493d9..55ab4486 100644
--- a/src/AKSRT/Widgets/CustomHtmlGapWidget.php
+++ b/src/AKSRT/Widgets/CustomHtmlGapWidget.php
@@ -38,7 +38,9 @@ class CustomHtmlGapWidget extends \WP_Widget {
if (trim(wp_strip_all_tags($content)) === '' && trim($content) === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo '' . esc_html__('自定义 HTML:', 'aksrt') . '' . esc_html__('请填写内容或切换为间距模式', 'aksrt') . '
';
+ echo function_exists('aksrt_get_widget_null')
+ ? aksrt_get_widget_null(__('请填写 HTML 内容或切换为间距模式', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ : '' . esc_html__('自定义 HTML:', 'aksrt') . '' . esc_html__('请填写内容或切换为间距模式', 'aksrt') . '
';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
diff --git a/src/AKSRT/Widgets/WidgetPostsNavs.php b/src/AKSRT/Widgets/WidgetPostsNavs.php
index 3567c6e5..0c0dfc4c 100644
--- a/src/AKSRT/Widgets/WidgetPostsNavs.php
+++ b/src/AKSRT/Widgets/WidgetPostsNavs.php
@@ -27,6 +27,13 @@ class WidgetPostsNavs extends \WP_Widget {
public function widget($args, $instance): void {
if (!is_singular(['post', 'page', 'aksrt_forum_topic', 'aksrt_product'])) {
+ if (current_user_can('manage_options')) {
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo function_exists('aksrt_get_widget_null')
+ ? aksrt_get_widget_null(__('目录树仅在文章/帖子/商品详情页显示', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ : '' . esc_html__('目录树仅在详情页显示', 'aksrt') . '
';
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
return;
}
diff --git a/src/AKSRT/Widgets/WidgetProductList.php b/src/AKSRT/Widgets/WidgetProductList.php
index b455b40e..3fbe06a4 100644
--- a/src/AKSRT/Widgets/WidgetProductList.php
+++ b/src/AKSRT/Widgets/WidgetProductList.php
@@ -94,7 +94,8 @@ class WidgetProductList extends \WP_Widget {
return '';
}
$oneline = $style === 'oneline';
- $mini = $style === 'mini' || $style === 'list';
+ $mini = $style === 'mini';
+ $list = $style === 'list';
ob_start();
while ($products->have_posts()) {
$products->the_post();
@@ -108,16 +109,18 @@ class WidgetProductList extends \WP_Widget {
if ($oneline) {
echo '