From a3585b0f9690e76f98eadc22430f2026ff9e84d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Jul 2026 22:11:52 +0800 Subject: [PATCH 1/6] feat(user-center): task list go-to actions for free points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add per-task CTAs (去签到 / 去完成 / 去看看) on the free points tasks list, with sign-in proxying to the asset-card button and bbs actions gated on forum availability. --- assets/css/routes/user-center/credits.css | 20 ++++ assets/js/credits.js | 23 ++++ .../credits-points-tasks.php | 111 +++++++++++++++++- 3 files changed, 150 insertions(+), 4 deletions(-) diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 07ddc9f7..216a39ce 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -965,4 +965,24 @@ body.uc-modal-open { cursor: pointer; padding: 0; } +.uc-points-tasks__meta { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} +.uc-points-tasks__action { + display: inline-flex; + align-items: center; + text-decoration: none; + color: var(--muted-color, #8b9198); + background: none; + border: 0; + cursor: pointer; + padding: 0; + line-height: 1.4; +} +.uc-points-tasks__action:hover { + color: var(--focus-color, #f04494); +} .focus-color { color: var(--focus-color, #f59e0b); } diff --git a/assets/js/credits.js b/assets/js/credits.js index efbe0433..dd9afcec 100644 --- a/assets/js/credits.js +++ b/assets/js/credits.js @@ -180,6 +180,28 @@ refresh(); } + // Task-list "去签到" → scroll + click the real asset-card sign-in button. + function initSigninGoto() { + document.querySelectorAll('[data-aksrt-signin-goto]').forEach(function (el) { + el.addEventListener('click', function (e) { + e.preventDefault(); + var root = document.querySelector('[data-aksrt-signin]'); + var btn = root ? root.querySelector('[data-signin-btn]') : null; + if (!btn) return; + try { + if (root && typeof root.scrollIntoView === 'function') { + root.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } catch (_) {} + if (!btn.disabled) { + btn.click(); + } else { + btn.focus(); + } + }); + }); + } + // ===== 邀请 ===== function initInvite() { var root = document.querySelector('[data-aksrt-invite]'); @@ -944,6 +966,7 @@ function bootAssets() { initSignin(); + initSigninGoto(); initInvite(); initVipShop(); initVipPointsExchange(); diff --git a/inc/helpers/user-center-renderers/credits-points-tasks.php b/inc/helpers/user-center-renderers/credits-points-tasks.php index e76d9de5..63789319 100644 --- a/inc/helpers/user-center-renderers/credits-points-tasks.php +++ b/inc/helpers/user-center-renderers/credits-points-tasks.php @@ -11,6 +11,94 @@ if (!class_exists('AKSRT_Points_Free') || !AKSRT_Points_Free::enabled()) { return; } +/** + * Resolve a "go complete" action for a free-points task row. + * + * @return array{type:string,label:string,href?:string} + */ +$aksrt_points_task_action = static function (string $key): array { + $home = home_url('/'); + $submit = ''; + if (function_exists('aksrt_get_page_url_by_template')) { + $submit = (string) aksrt_get_page_url_by_template('templates/pages/page-submit.php'); + } + + $forum_on = false; + $forum_home = ''; + $forum_compose = ''; + if (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_forum_settings')) { + $f = AKSRT_Settings::get_forum_settings(); + $forum_on = !empty($f['enabled']); + } + if ($forum_on) { + if (function_exists('aksrt_get_forum_home_url')) { + $forum_home = (string) aksrt_get_forum_home_url(); + } + if (function_exists('aksrt_get_forum_compose_url')) { + $forum_compose = (string) aksrt_get_forum_compose_url(); + } + } + + switch ($key) { + case 'sign_in': + return [ + 'type' => 'signin', + 'label' => __('去签到', 'aksrt'), + ]; + case 'post_new': + return [ + 'type' => 'link', + 'label' => __('去完成', 'aksrt'), + 'href' => $submit !== '' ? $submit : $home, + ]; + case 'comment_new': + return [ + 'type' => 'link', + 'label' => __('去完成', 'aksrt'), + 'href' => $home, + ]; + case 'login': + case 'post_like': + case 'post_favorite': + case 'followed': + case 'comment_like': + return [ + 'type' => 'link', + 'label' => __('去看看', 'aksrt'), + 'href' => $home, + ]; + case 'bbs_posts_new': + case 'bbs_reply': + if (!$forum_on) { + return ['type' => 'none', 'label' => '']; + } + $href = $forum_compose !== '' ? $forum_compose : $forum_home; + if ($href === '') { + return ['type' => 'none', 'label' => '']; + } + return [ + 'type' => 'link', + 'label' => __('去完成', 'aksrt'), + 'href' => $href, + ]; + case 'bbs_essence': + case 'bbs_adopt': + case 'bbs_posts_hot': + case 'bbs_comment_hot': + case 'bbs_score_extra': + if (!$forum_on || $forum_home === '') { + return ['type' => 'none', 'label' => '']; + } + return [ + 'type' => 'link', + 'label' => __('去看看', 'aksrt'), + 'href' => $forum_home, + ]; + default: + return ['type' => 'none', 'label' => '']; + } +}; + $user_id = (int) ($user_id ?? get_current_user_id()); $today = AKSRT_Points_Free::today_total($user_id); $day_max = (int) AKSRT_Points_Free::settings()['day_max']; @@ -76,15 +164,30 @@ $pane = isset($points_tasks_pane) ? sanitize_key((string) $points_tasks_pane) -
+ +
- - + - +
+
+ + +
+ + + + + + + +
From 1d75170f8e924c424d752344a12bafbcad263f1d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Jul 2026 22:17:21 +0800 Subject: [PATCH 2/6] feat(user): badge icon urls and wear medal chrome Extend badge dictionary with optional icon_url, prefer aksrt_wear_badge when assigned, and render medal images in identity chips. --- .../admin/src/schema/sections/users/badge.js | 9 ++++- assets/css/frontend/comments.css | 17 +++++++++ assets/css/frontend/single-post/cover.css | 8 +++++ .../schema/class-aksrt-admin-schema-users.php | 8 ++++- inc/classes/class-aksrt-user-home.php | 31 ++++++++++++---- .../class-aksrt-settings-user-runtime.php | 12 ++++--- inc/helpers/aksrt-identity.php | 35 +++++++++++++++---- 7 files changed, 99 insertions(+), 21 deletions(-) diff --git a/assets/admin/src/schema/sections/users/badge.js b/assets/admin/src/schema/sections/users/badge.js index 48fc7c91..768e55f6 100644 --- a/assets/admin/src/schema/sections/users/badge.js +++ b/assets/admin/src/schema/sections/users/badge.js @@ -25,7 +25,14 @@ export const badgeSchema = { title: '徽章字典', icon: 'fas fa-bookmark', fields: [ - { key: 'user_badge_dictionary', label: '徽章列表', type: 'textarea', default: 'founder|创始成员|warning\nstaff|站务团队|info', help: '每行一个徽章:slug|显示文案|色调(neutral/info/success/warning/brand)', dependsOn: { key: 'user_badge_enabled', value: true } }, + { + key: 'user_badge_dictionary', + label: '徽章列表', + type: 'textarea', + default: 'founder|创始成员|warning\nstaff|站务团队|info', + help: '每行一个徽章:slug|显示文案|色调(neutral/info/success/warning/brand)|图标URL(可选)。有 icon_url 时前台显示勋章图;用户 meta aksrt_wear_badge 指定当前佩戴一枚。', + dependsOn: { key: 'user_badge_enabled', value: true }, + }, ], }, ], diff --git a/assets/css/frontend/comments.css b/assets/css/frontend/comments.css index 783dfbd6..72806fc6 100644 --- a/assets/css/frontend/comments.css +++ b/assets/css/frontend/comments.css @@ -688,6 +688,23 @@ body.dark-theme .comment-login-prompt__inner { font-size: 11px; padding: 0 6px; } +.medal-icon.img-icon, +.aksrt-identity-medal { + display: inline-block; + width: 1.35em; + height: 1.35em; + object-fit: contain; + vertical-align: middle; + margin-left: 4px; +} +.aksrt-identity-medal--comment { + width: 1.15em; + height: 1.15em; +} +.aksrt-identity-medal.is-wear { + outline: 1px solid rgba(245, 158, 11, 0.45); + border-radius: 3px; +} .aksrt-comment__badge--hot { color: #ea580c; background: rgba(234, 88, 12, 0.1); diff --git a/assets/css/frontend/single-post/cover.css b/assets/css/frontend/single-post/cover.css index eb863168..3af39fea 100644 --- a/assets/css/frontend/single-post/cover.css +++ b/assets/css/frontend/single-post/cover.css @@ -285,6 +285,14 @@ line-height: 1.2; vertical-align: middle; } +.article-avatar .medal-icon.img-icon, +.article-avatar .aksrt-identity-medal { + width: 1.4em; + height: 1.4em; + object-fit: contain; + vertical-align: middle; + margin-left: 4px; +} .article-avatar .display-name { margin-right: 2px; } diff --git a/inc/classes/admin/schema/class-aksrt-admin-schema-users.php b/inc/classes/admin/schema/class-aksrt-admin-schema-users.php index 6edd2ed8..f06e99a6 100644 --- a/inc/classes/admin/schema/class-aksrt-admin-schema-users.php +++ b/inc/classes/admin/schema/class-aksrt-admin-schema-users.php @@ -648,7 +648,13 @@ class AKSRT_Admin_Schema_Users { ['key' => 'user_badge_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8], ]], ['key' => 'dictionary', 'title' => '徽章字典', 'fields' => [ - ['key' => 'user_badge_dictionary', 'label' => '徽章列表', 'type' => 'textarea', 'default' => "founder|创始成员|warning\nstaff|站务团队|info"], + [ + 'key' => 'user_badge_dictionary', + 'label' => '徽章列表', + 'type' => 'textarea', + 'default' => "founder|创始成员|warning\nstaff|站务团队|info", + 'help' => '每行:slug|显示文案|色调(neutral/info/success/warning/brand)|图标URL(可选)。配置 icon_url 后前台显示勋章图;用户 meta aksrt_wear_badge 指定当前佩戴。', + ], ]], ], ], diff --git a/inc/classes/class-aksrt-user-home.php b/inc/classes/class-aksrt-user-home.php index e70c5438..8b5d1c76 100644 --- a/inc/classes/class-aksrt-user-home.php +++ b/inc/classes/class-aksrt-user-home.php @@ -130,7 +130,7 @@ class AKSRT_User_Home { /** * 获取公开展示的身份信号(认证 / 等级 / 徽章)。 * - * @return array + * @return array */ public function get_profile_signals(int $user_id): array { if ($user_id <= 0 || !class_exists('AKSRT_Settings')) { @@ -142,6 +142,7 @@ class AKSRT_User_Home { if (!empty($settings['verify']['enabled']) && !empty($settings['verify']['public_display']) && $this->is_user_verified($user_id, $settings['verify'])) { $signals[] = [ + 'type' => 'verify', 'label' => (string) $settings['verify']['badge_text'], 'icon' => 'check-circle', 'tone' => 'success', @@ -152,6 +153,7 @@ class AKSRT_User_Home { $level = $this->get_user_level($user_id, $settings['level']); if ($level > 0) { $signals[] = [ + 'type' => 'level', 'label' => (string) $settings['level']['label_prefix'] . $level, 'icon' => 'layer-group', 'tone' => 'info', @@ -171,21 +173,36 @@ class AKSRT_User_Home { if (!is_array($assigned)) { $assigned = preg_split('/[\s,]+/', (string) $assigned, -1, PREG_SPLIT_NO_EMPTY); } + $assigned = array_values(array_filter(array_map(static function ($slug) { + return sanitize_key((string) $slug); + }, (array) $assigned))); + + // Prefer worn badge first (Zibll wear-medal). + $wear = sanitize_key((string) get_user_meta($user_id, 'aksrt_wear_badge', true)); + if ($wear !== '' && in_array($wear, $assigned, true) && isset($badge_map[$wear])) { + $assigned = array_values(array_unique(array_merge([$wear], $assigned))); + } $limit = max(1, (int) ($settings['badge']['limit'] ?? 3)); - foreach ((array) $assigned as $slug) { - $slug = sanitize_key((string) $slug); + $badge_count = 0; + foreach ($assigned as $slug) { if ($slug === '' || !isset($badge_map[$slug])) { continue; } + $icon_url = (string) ($badge_map[$slug]['icon_url'] ?? ''); $signals[] = [ - 'label' => (string) $badge_map[$slug]['label'], - 'icon' => 'award', - 'tone' => (string) ($badge_map[$slug]['tone'] ?? 'neutral'), + 'type' => 'badge', + 'slug' => $slug, + 'label' => (string) $badge_map[$slug]['label'], + 'icon' => 'award', + 'tone' => (string) ($badge_map[$slug]['tone'] ?? 'neutral'), + 'icon_url' => $icon_url, + 'wear' => ($wear !== '' && $slug === $wear), ]; + $badge_count++; - if (count($signals) >= $limit + 2) { + if ($badge_count >= $limit) { break; } } diff --git a/inc/classes/settings/class-aksrt-settings-user-runtime.php b/inc/classes/settings/class-aksrt-settings-user-runtime.php index bb92f9a8..78bd1a27 100644 --- a/inc/classes/settings/class-aksrt-settings-user-runtime.php +++ b/inc/classes/settings/class-aksrt-settings-user-runtime.php @@ -120,7 +120,7 @@ class AKSRT_Settings_User_Runtime { * @return array{ * verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool}, * level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array}, - * badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array}, + * badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array}, * report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array} * } */ @@ -238,7 +238,7 @@ class AKSRT_Settings_User_Runtime { } /** - * @return array + * @return array */ public static function parse_badge_dictionary(string $raw): array { $lines = preg_split('/[\r\n]+/', $raw, -1, PREG_SPLIT_NO_EMPTY); @@ -254,6 +254,7 @@ class AKSRT_Settings_User_Runtime { $slug = sanitize_key((string) $parts[0]); $label = sanitize_text_field((string) $parts[1]); $tone = isset($parts[2]) ? sanitize_key((string) $parts[2]) : 'neutral'; + $icon_url = isset($parts[3]) ? esc_url_raw((string) $parts[3]) : ''; if ($slug === '' || $label === '') { continue; @@ -264,9 +265,10 @@ class AKSRT_Settings_User_Runtime { } $items[] = [ - 'slug' => $slug, - 'label' => $label, - 'tone' => $tone, + 'slug' => $slug, + 'label' => $label, + 'tone' => $tone, + 'icon_url' => $icon_url, ]; } diff --git a/inc/helpers/aksrt-identity.php b/inc/helpers/aksrt-identity.php index 3c9ac91c..8ff34f17 100644 --- a/inc/helpers/aksrt-identity.php +++ b/inc/helpers/aksrt-identity.php @@ -9,7 +9,7 @@ defined('ABSPATH') || exit; if (!function_exists('aksrt_get_user_identity_signals')) { /** - * @return list + * @return list */ function aksrt_get_user_identity_signals(int $user_id): array { if ($user_id <= 0) { @@ -27,6 +27,8 @@ if (!function_exists('aksrt_render_user_identity_chips')) { /** * Render compact identity chips (verify / level / badges). * + * Badge signals with icon_url render as Zibll-like medal images. + * * @param int $user_id * @param string $context author|comment|card */ @@ -42,8 +44,9 @@ if (!function_exists('aksrt_render_user_identity_chips')) { } } $signals = aksrt_get_user_identity_signals($user_id); - $limit = $context === 'comment' ? 3 : 6; - $n = 0; + // Comments stay compact; author/card can show more. + $limit = $context === 'comment' ? 3 : 6; + $n = 0; foreach ($signals as $signal) { if ($n >= $limit) { break; @@ -52,14 +55,32 @@ if (!function_exists('aksrt_render_user_identity_chips')) { if ($label === '') { continue; } - $tone = sanitize_html_class((string) ($signal['tone'] ?? 'info')); - $icon = sanitize_html_class((string) ($signal['icon'] ?? 'check')); + $tone = sanitize_html_class((string) ($signal['tone'] ?? 'info')); + $icon = sanitize_html_class((string) ($signal['icon'] ?? 'check')); + $icon_url = esc_url((string) ($signal['icon_url'] ?? '')); + $title = (string) ($signal['title'] ?? $label); + $is_wear = !empty($signal['wear']); + $type = sanitize_html_class((string) ($signal['type'] ?? '')); + + // Image medal when dictionary provides icon_url. + if ($icon_url !== '') { + $classes = 'medal-icon img-icon ml3 aksrt-identity-medal aksrt-identity-medal--' . $context; + if ($is_wear) { + $classes .= ' is-wear'; + } + if ($type !== '') { + $classes .= ' aksrt-identity-medal--' . $type; + } + $html .= '' . esc_attr($label) . ''; + $n++; + continue; + } + // Font Awesome: map short names to fa-* if needed. if ($icon !== '' && !str_contains($icon, 'fa-')) { $icon = 'fa-' . $icon; } - $title = (string) ($signal['title'] ?? $label); - $html .= ''; + $html .= ''; if ($icon !== '') { $html .= ' '; } From 357d710b1b2c68bceb58a784ca50c21f3f60ee92 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Jul 2026 22:18:49 +0800 Subject: [PATCH 3/6] fix(comments): denser zibll-like chrome and guest fields Tighten comment form density, collapse guest name/email behind a toggle, and refine empty-state copy for closed/author-only views. --- assets/css/frontend/comments.css | 69 ++++++++++++++++++++++++++++++-- assets/js/core/comments.js | 37 ++++++++++++++++- inc/helpers/aksrt-comments.php | 28 ++++++++++--- 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/assets/css/frontend/comments.css b/assets/css/frontend/comments.css index 72806fc6..37527bc7 100644 --- a/assets/css/frontend/comments.css +++ b/assets/css/frontend/comments.css @@ -87,7 +87,7 @@ .comment-form__inner { display: flex; - gap: 12px; + gap: 10px; align-items: flex-start; } @@ -169,11 +169,74 @@ align-items: center; gap: 8px; margin-left: auto; + flex-wrap: wrap; + justify-content: flex-end; +} + +.comment-form__guest { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 6px; +} + +.comment-form__guest-toggle { + background: none; + border: 0; + padding: 0; + cursor: pointer; + color: var(--muted-color, #8b9198); + white-space: nowrap; +} + +.comment-form__guest-toggle:hover, +.comment-form__guest-toggle[aria-expanded="true"] { + color: var(--focus-color, #f04494); +} + +.comment-form__guest-fields { + display: flex; + gap: 6px; + align-items: center; + max-height: 0; + overflow: hidden; + opacity: 0; + transition: max-height 0.2s ease, opacity 0.15s ease; +} + +.comment-form__guest-fields.is-open, +.comment-form__guest-fields:not([hidden]) { + max-height: 48px; + opacity: 1; } .comment-form__guest-field { - width: 120px; - height: 32px; + width: 110px; + height: 30px; + font-size: 12px; + padding: 0 8px; +} + +.comt-box .comt-textarea, +.comt-box #comment { + min-height: 72px; + padding: 10px 12px; + border-radius: 8px; + font-size: 13px; + line-height: 1.5; + margin-bottom: 6px; +} + +.comt-ctrl { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.comment-form__submit { + height: 30px; + padding: 0 12px; font-size: 13px; } diff --git a/assets/js/core/comments.js b/assets/js/core/comments.js index 940150d3..79987458 100644 --- a/assets/js/core/comments.js +++ b/assets/js/core/comments.js @@ -675,6 +675,41 @@ }); } - window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); }); + + function initGuestFields() { + var root = document.getElementById("comments") || document; + root.querySelectorAll("[data-aksrt-guest-toggle]").forEach(function (btn) { + if (btn.dataset.aksrtGuestBound === "1") return; + btn.dataset.aksrtGuestBound = "1"; + var wrap = btn.closest(".comment-form__guest"); + var fields = wrap ? wrap.querySelector("[data-aksrt-guest-fields]") : null; + if (!fields) return; + btn.addEventListener("click", function (e) { + e.preventDefault(); + var open = btn.getAttribute("aria-expanded") === "true"; + btn.setAttribute("aria-expanded", open ? "false" : "true"); + if (open) { + fields.setAttribute("hidden", ""); + fields.classList.remove("is-open"); + } else { + fields.removeAttribute("hidden"); + fields.classList.add("is-open"); + var first = fields.querySelector("input"); + if (first) first.focus(); + } + }); + // Auto-open if browser restored values or validation fails later. + var form = btn.closest("form"); + if (form) { + form.addEventListener("invalid", function () { + fields.removeAttribute("hidden"); + fields.classList.add("is-open"); + btn.setAttribute("aria-expanded", "true"); + }, true); + } + }); + } + + window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); initGuestFields(); }); window.AksrtFrontend.register("aksrt-comment-order", function (root) { initCommentOrder(root); }); })(); diff --git a/inc/helpers/aksrt-comments.php b/inc/helpers/aksrt-comments.php index cb94a64c..2b176ed3 100644 --- a/inc/helpers/aksrt-comments.php +++ b/inc/helpers/aksrt-comments.php @@ -463,7 +463,7 @@ if (!function_exists('aksrt_comments_render_form')) :
- +
- - +
+ + +

-

+

Date: Thu, 16 Jul 2026 22:31:18 +0800 Subject: [PATCH 4/6] fix(share): mobile share chrome and tabbar poster entry Use a bottom sheet share panel on small screens and add a one-tap poster button to the single-post footer tabbar extend. --- .../frontend/single-post/author-actions.css | 53 +++++++++++++++++++ assets/js/post-share.js | 8 ++- inc/helpers/aksrt-template-tags.php | 17 ++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/assets/css/frontend/single-post/author-actions.css b/assets/css/frontend/single-post/author-actions.css index 50f919fc..96fbb8e8 100644 --- a/assets/css/frontend/single-post/author-actions.css +++ b/assets/css/frontend/single-post/author-actions.css @@ -335,6 +335,59 @@ html.dark .share-item { .theme-dark .share-item[data-platform="copy"]:hover { background: rgba(255,255,255,0.06); color: #ccc; } .theme-dark .share-item[data-platform="poster"]:hover { background: rgba(245,158,11,0.12); color: #f59e0b; } +/* Narrow screens: bottom sheet so dropdown never clips off-screen */ +@media (max-width: 767px) { + .share-dropdown { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: auto; + transform: none; + width: 100%; + max-width: none; + z-index: 1200; + padding: 0 0 calc(12px + env(safe-area-inset-bottom, 0px)); + background: transparent; + } + .share-dropdown[aria-hidden="false"] { + animation: shareSheetIn 0.22s ease-out forwards; + } + @keyframes shareSheetIn { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } + } + .share-dropdown__grid { + justify-content: space-around; + flex-wrap: wrap; + gap: 6px; + margin: 0 10px; + padding: 14px 12px 16px; + border-radius: 14px 14px 10px 10px; + box-shadow: 0 -6px 28px rgba(0, 0, 0, 0.16); + } + .share-item { + min-width: 64px; + flex: 1 1 28%; + max-width: 33%; + } + #share-qrcode-box { + position: static; + margin: 10px auto 0; + transform: none; + } + body.aksrt-share-sheet-open { + overflow: hidden; + } + body.aksrt-share-sheet-open::before { + content: ''; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.35); + z-index: 1190; + } +} + .theme-dark .post-drop-meta__menu, html.dark .post-drop-meta__menu { background: var(--surface-1, #323335); diff --git a/assets/js/post-share.js b/assets/js/post-share.js index dd0234b3..56e95fcd 100644 --- a/assets/js/post-share.js +++ b/assets/js/post-share.js @@ -135,7 +135,8 @@ function closeDropdown() { dropdown.setAttribute('aria-hidden', 'true'); - trigger.setAttribute('aria-expanded', 'false'); + triggers.forEach(function (t) { t.setAttribute('aria-expanded', 'false'); }); + document.body.classList.remove('aksrt-share-sheet-open'); if (window.AksrtShare && typeof window.AksrtShare.hideQrFallback === 'function') { window.AksrtShare.hideQrFallback(qrBox); } else if (qrBox) { @@ -152,7 +153,10 @@ } dropdown.setAttribute('aria-hidden', 'false'); - trigger.setAttribute('aria-expanded', 'true'); + triggers.forEach(function (t) { t.setAttribute('aria-expanded', 'true'); }); + if (window.matchMedia && window.matchMedia('(max-width: 767px)').matches) { + document.body.classList.add('aksrt-share-sheet-open'); + } } trigger.addEventListener('click', function (event) { diff --git a/inc/helpers/aksrt-template-tags.php b/inc/helpers/aksrt-template-tags.php index 17ee5b9a..0bcd31d8 100644 --- a/inc/helpers/aksrt-template-tags.php +++ b/inc/helpers/aksrt-template-tags.php @@ -1357,16 +1357,33 @@ if (!function_exists('aksrt_render_footer_tabbar_single_extend')) : endif; if ($share_on) : + $share_title = get_the_title($post_id); + $share_url = get_permalink($post_id) ?: ''; + $share_image = get_the_post_thumbnail_url($post_id, 'large') ?: ''; ?> + Date: Thu, 16 Jul 2026 22:32:03 +0800 Subject: [PATCH 5/6] fix(single): author card follow and identity parity Add follow CTA to after-box author card, wrap bar label in span, and harden follow.js label updates when span is missing. --- assets/js/follow.js | 16 +++- inc/helpers/aksrt-single-render.php | 124 +++++++++++++++----------- templates/parts/single-author-bar.php | 2 +- 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/assets/js/follow.js b/assets/js/follow.js index 6c7b2c7b..e5b9788f 100644 --- a/assets/js/follow.js +++ b/assets/js/follow.js @@ -68,16 +68,28 @@ function updateFollowButton(btn, isFollowing, count) { var icon = btn.querySelector('i'); var text = btn.querySelector('span'); + function setLabel(label) { + if (text) { + text.textContent = label; + return; + } + // Fallback when markup has bare text nodes next to the icon. + var nodes = Array.prototype.slice.call(btn.childNodes); + var textNode = nodes.filter(function (n) { return n.nodeType === 3 && n.textContent.trim() !== ''; })[0]; + if (textNode) { + textNode.textContent = ' ' + label + ' '; + } + } if (isFollowing) { btn.classList.add('is-following'); if (icon) icon.className = 'fas fa-heart'; - if (text) text.textContent = '已关注'; + setLabel('已关注'); btn.title = '点击取消关注'; } else { btn.classList.remove('is-following'); if (icon) icon.className = 'far fa-heart'; - if (text) text.textContent = '关注'; + setLabel('关注'); btn.title = '关注该用户'; } diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php index 8a57a170..5572ba7b 100644 --- a/inc/helpers/aksrt-single-render.php +++ b/inc/helpers/aksrt-single-render.php @@ -299,63 +299,79 @@ if (!function_exists('aksrt_single_render_author_card')) : * @param array $ctx Single context. */ function aksrt_single_render_author_card(array $ctx): void { - $author_id = (int) get_the_author_meta('ID'); - if ($author_id <= 0) { - return; - } - $author_url = get_author_posts_url($author_id); - $author_name = get_the_author(); - $bio = trim((string) get_the_author_meta('description')); - $identity_chips = function_exists('aksrt_render_user_identity_chips') - ? aksrt_render_user_identity_chips($author_id, 'card') - : ''; - $avatar = function_exists('aksrt_get_avatar_html') - ? aksrt_get_avatar_html($author_id, 80, ['class' => 'avatar avatar-lg']) - : get_avatar($author_id, 80, '', esc_attr($author_name), ['class' => 'avatar avatar-lg']); - $recent = get_posts([ - 'author' => $author_id, - 'post_type' => 'post', - 'post_status' => 'publish', - 'posts_per_page' => 6, - 'orderby' => 'date', - 'order' => 'DESC', - 'no_found_rows' => true, - ]); - ?> -
-
-
-
- -
- -
-
    - - -
  • + $author_id = (int) get_the_author_meta('ID'); + if ($author_id <= 0) { + return; + } + $author_url = get_author_posts_url($author_id); + $author_name = get_the_author(); + $bio = trim((string) get_the_author_meta('description')); + $identity_chips = function_exists('aksrt_render_user_identity_chips') + ? aksrt_render_user_identity_chips($author_id, 'card') + : ''; + $avatar = function_exists('aksrt_get_avatar_html') + ? aksrt_get_avatar_html($author_id, 80, ['class' => 'avatar avatar-lg']) + : get_avatar($author_id, 80, '', esc_attr($author_name), ['class' => 'avatar avatar-lg']); + $recent = get_posts([ + 'author' => $author_id, + 'post_type' => 'post', + 'post_status' => 'publish', + 'posts_per_page' => 6, + 'orderby' => 'date', + 'order' => 'DESC', + 'no_found_rows' => true, + ]); + $is_self = is_user_logged_in() && get_current_user_id() === $author_id; + $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink()); + ?> +
    +
    +
    +
    +
+
+ +
+ +
+ + + + + + + + +
+
- + +
+
    + + +
  • + + +
+
+ +
+ + - - From c45a0ecef56da3e97165b1ec2fa6059d965ef18e Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Jul 2026 22:34:30 +0800 Subject: [PATCH 6/6] feat(pay): capability gates for transfer and withdraw AND feature flags with user_caps balance_transfer / points_transfer / balance_withdraw on REST and user-center buttons. --- inc/classes/class-aksrt-balance-service.php | 9 +++++++++ .../settings/class-aksrt-settings-capabilities.php | 1 + inc/helpers/user-center-renderers/credits-balance.php | 4 ++++ inc/helpers/user-center-renderers/credits-wallet.php | 7 +++++++ 4 files changed, 21 insertions(+) diff --git a/inc/classes/class-aksrt-balance-service.php b/inc/classes/class-aksrt-balance-service.php index f4452fa2..db5ed8e3 100644 --- a/inc/classes/class-aksrt-balance-service.php +++ b/inc/classes/class-aksrt-balance-service.php @@ -172,6 +172,9 @@ class AKSRT_Balance_Service { if (empty($cfg['enabled']) || empty($cfg['transfer']['enabled'])) { return new WP_Error('aksrt_transfer_closed', __('余额转账未开启', 'aksrt'), ['status' => 403]); } + if (class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('balance_transfer', $from_id)) { + return new WP_Error('aksrt_transfer_forbidden', __('当前账号无权转账余额', 'aksrt'), ['status' => 403]); + } $amount = round((float) $amount_raw, 2); if ($amount <= 0) { return new WP_Error('aksrt_transfer_amount', __('请输入有效金额', 'aksrt'), ['status' => 400]); @@ -220,6 +223,9 @@ class AKSRT_Balance_Service { if (!class_exists('AKSRT_Settings') || !AKSRT_Settings::credits_enabled() || !$points_transfer_on) { return new WP_Error('aksrt_transfer_closed', __('积分转账未开启', 'aksrt'), ['status' => 403]); } + if (class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('points_transfer', $from_id)) { + return new WP_Error('aksrt_transfer_forbidden', __('当前账号无权转账积分', 'aksrt'), ['status' => 403]); + } $amount = (int) $amount_raw; if ($amount <= 0) { return new WP_Error('aksrt_transfer_amount', __('请输入有效积分', 'aksrt'), ['status' => 400]); @@ -724,6 +730,9 @@ class AKSRT_Balance_Service { $params = $request->get_params(); } $user_id = get_current_user_id(); + if (class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('balance_withdraw', $user_id)) { + return new WP_Error('aksrt_withdraw_forbidden', __('当前账号无权提现', 'aksrt'), ['status' => 403]); + } $amount = round((float) ($params['amount'] ?? 0), 2); $method = sanitize_key((string) ($params['method'] ?? 'alipay')); $account = sanitize_textarea_field((string) ($params['account_info'] ?? $params['account'] ?? '')); diff --git a/inc/classes/settings/class-aksrt-settings-capabilities.php b/inc/classes/settings/class-aksrt-settings-capabilities.php index 7333d4ae..1411f4d7 100644 --- a/inc/classes/settings/class-aksrt-settings-capabilities.php +++ b/inc/classes/settings/class-aksrt-settings-capabilities.php @@ -19,6 +19,7 @@ class AKSRT_Settings_Capabilities { 'user_report' => ['label' => __('举报其它用户', 'aksrt'), 'default' => ['logged' => true]], 'balance_transfer' => ['label' => __('余额转账给其他用户', 'aksrt'), 'exclude_roles' => ['all']], 'points_transfer' => ['label' => __('积分转账给其他用户', 'aksrt'), 'exclude_roles' => ['all']], + 'balance_withdraw' => ['label' => __('余额提现', 'aksrt'), 'exclude_roles' => ['all']], 'set_user_ban' => ['label' => __('设置用户封禁状态', 'aksrt'), 'roles' => ['cat_moderator'], 'default' => ['cat_moderator' => true]], 'medal_manually_set' => ['label' => __('为用户授予徽章', 'aksrt'), 'roles' => ['cat_moderator'], 'default' => ['cat_moderator' => true]], diff --git a/inc/helpers/user-center-renderers/credits-balance.php b/inc/helpers/user-center-renderers/credits-balance.php index 84b9f4dd..afff2943 100644 --- a/inc/helpers/user-center-renderers/credits-balance.php +++ b/inc/helpers/user-center-renderers/credits-balance.php @@ -19,7 +19,11 @@ if (class_exists('AKSRT_Charge_Card')) { $card_pass_credits = is_array($opts['credits'] ?? null) ? $opts['credits'] : $card_pass_credits; } $all_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_all() : []; +$points_uid = (int) ($user_id ?? get_current_user_id()); $points_transfer_on = !empty($all_settings['points_transfer_s']); +if ($points_transfer_on && class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('points_transfer', $points_uid)) { + $points_transfer_on = false; +} $points_buy_on = class_exists('AKSRT_Balance_Service') && AKSRT_Balance_Service::get_instance()->points_buy_enabled(); $points_buy_view = $points_buy_on ? AKSRT_Balance_Service::get_instance()->get_points_buy_view((int) ($user_id ?? 0)) : []; $currency = (string) ($currency_name ?? __('积分', 'aksrt')); diff --git a/inc/helpers/user-center-renderers/credits-wallet.php b/inc/helpers/user-center-renderers/credits-wallet.php index f33b7d7d..ee360ac3 100644 --- a/inc/helpers/user-center-renderers/credits-wallet.php +++ b/inc/helpers/user-center-renderers/credits-wallet.php @@ -21,10 +21,17 @@ $wallet_fmt = class_exists('AKSRT_Balance') $recharge = class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_payment_recharge_settings') ? AKSRT_Settings::get_payment_recharge_settings() : []; +$uid = (int) ($user_id ?? get_current_user_id()); $transfer_on = !empty($recharge['transfer']['enabled']); +if ($transfer_on && class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('balance_transfer', $uid)) { + $transfer_on = false; +} $exchange_on = !empty($recharge['exchange']['enabled']); $all_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_all() : []; $withdraw_on = !empty($all_settings['pay_balance_withdraw_s']); +if ($withdraw_on && class_exists('AKSRT_Settings_Capabilities') && !AKSRT_Settings_Capabilities::user_can('balance_withdraw', $uid)) { + $withdraw_on = false; +} $withdraw_min = max(1, (float) ($all_settings['pay_balance_withdraw_lowest_money'] ?? $all_settings['pay_rebate_withdraw_lowest_money'] ?? 50)); $withdraw_pending_hold = 0.0; $withdraw_history = [];