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.
This commit is contained in:
1 parent
a3585b0f96
commit
1d75170f8e
7 files changed
+89
-11
No files matched your search
@@ -25,7 +25,14 @@ export const badgeSchema = {
|
|||||||
title: '徽章字典',
|
title: '徽章字典',
|
||||||
icon: 'fas fa-bookmark',
|
icon: 'fas fa-bookmark',
|
||||||
fields: [
|
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 },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -688,6 +688,23 @@ body.dark-theme .comment-login-prompt__inner {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 0 6px;
|
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 {
|
.aksrt-comment__badge--hot {
|
||||||
color: #ea580c;
|
color: #ea580c;
|
||||||
background: rgba(234, 88, 12, 0.1);
|
background: rgba(234, 88, 12, 0.1);
|
||||||
|
|||||||
@@ -285,6 +285,14 @@
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
vertical-align: middle;
|
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 {
|
.article-avatar .display-name {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -648,7 +648,13 @@ class AKSRT_Admin_Schema_Users {
|
|||||||
['key' => 'user_badge_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8],
|
['key' => 'user_badge_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8],
|
||||||
]],
|
]],
|
||||||
['key' => 'dictionary', 'title' => '徽章字典', 'fields' => [
|
['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 指定当前佩戴。',
|
||||||
|
],
|
||||||
]],
|
]],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class AKSRT_User_Home {
|
|||||||
/**
|
/**
|
||||||
* 获取公开展示的身份信号(认证 / 等级 / 徽章)。
|
* 获取公开展示的身份信号(认证 / 等级 / 徽章)。
|
||||||
*
|
*
|
||||||
* @return array<int,array{label:string,icon:string,tone:string}>
|
* @return array<int,array{label:string,icon:string,tone:string,icon_url?:string,type?:string,slug?:string,wear?:bool}>
|
||||||
*/
|
*/
|
||||||
public function get_profile_signals(int $user_id): array {
|
public function get_profile_signals(int $user_id): array {
|
||||||
if ($user_id <= 0 || !class_exists('AKSRT_Settings')) {
|
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'])) {
|
if (!empty($settings['verify']['enabled']) && !empty($settings['verify']['public_display']) && $this->is_user_verified($user_id, $settings['verify'])) {
|
||||||
$signals[] = [
|
$signals[] = [
|
||||||
|
'type' => 'verify',
|
||||||
'label' => (string) $settings['verify']['badge_text'],
|
'label' => (string) $settings['verify']['badge_text'],
|
||||||
'icon' => 'check-circle',
|
'icon' => 'check-circle',
|
||||||
'tone' => 'success',
|
'tone' => 'success',
|
||||||
@@ -152,6 +153,7 @@ class AKSRT_User_Home {
|
|||||||
$level = $this->get_user_level($user_id, $settings['level']);
|
$level = $this->get_user_level($user_id, $settings['level']);
|
||||||
if ($level > 0) {
|
if ($level > 0) {
|
||||||
$signals[] = [
|
$signals[] = [
|
||||||
|
'type' => 'level',
|
||||||
'label' => (string) $settings['level']['label_prefix'] . $level,
|
'label' => (string) $settings['level']['label_prefix'] . $level,
|
||||||
'icon' => 'layer-group',
|
'icon' => 'layer-group',
|
||||||
'tone' => 'info',
|
'tone' => 'info',
|
||||||
@@ -171,21 +173,36 @@ class AKSRT_User_Home {
|
|||||||
if (!is_array($assigned)) {
|
if (!is_array($assigned)) {
|
||||||
$assigned = preg_split('/[\s,]+/', (string) $assigned, -1, PREG_SPLIT_NO_EMPTY);
|
$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));
|
$limit = max(1, (int) ($settings['badge']['limit'] ?? 3));
|
||||||
foreach ((array) $assigned as $slug) {
|
$badge_count = 0;
|
||||||
$slug = sanitize_key((string) $slug);
|
foreach ($assigned as $slug) {
|
||||||
if ($slug === '' || !isset($badge_map[$slug])) {
|
if ($slug === '' || !isset($badge_map[$slug])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$icon_url = (string) ($badge_map[$slug]['icon_url'] ?? '');
|
||||||
$signals[] = [
|
$signals[] = [
|
||||||
|
'type' => 'badge',
|
||||||
|
'slug' => $slug,
|
||||||
'label' => (string) $badge_map[$slug]['label'],
|
'label' => (string) $badge_map[$slug]['label'],
|
||||||
'icon' => 'award',
|
'icon' => 'award',
|
||||||
'tone' => (string) ($badge_map[$slug]['tone'] ?? 'neutral'),
|
'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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class AKSRT_Settings_User_Runtime {
|
|||||||
* @return array{
|
* @return array{
|
||||||
* verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool},
|
* 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<int,int>},
|
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
||||||
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string}>},
|
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string,icon_url?:string}>},
|
||||||
* report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array<int,array{slug:string,label:string}>}
|
* report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array<int,array{slug:string,label:string}>}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
@@ -238,7 +238,7 @@ class AKSRT_Settings_User_Runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int,array{slug:string,label:string,tone:string}>
|
* @return array<int,array{slug:string,label:string,tone:string,icon_url:string}>
|
||||||
*/
|
*/
|
||||||
public static function parse_badge_dictionary(string $raw): array {
|
public static function parse_badge_dictionary(string $raw): array {
|
||||||
$lines = preg_split('/[\r\n]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
|
$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]);
|
$slug = sanitize_key((string) $parts[0]);
|
||||||
$label = sanitize_text_field((string) $parts[1]);
|
$label = sanitize_text_field((string) $parts[1]);
|
||||||
$tone = isset($parts[2]) ? sanitize_key((string) $parts[2]) : 'neutral';
|
$tone = isset($parts[2]) ? sanitize_key((string) $parts[2]) : 'neutral';
|
||||||
|
$icon_url = isset($parts[3]) ? esc_url_raw((string) $parts[3]) : '';
|
||||||
|
|
||||||
if ($slug === '' || $label === '') {
|
if ($slug === '' || $label === '') {
|
||||||
continue;
|
continue;
|
||||||
@@ -267,6 +268,7 @@ class AKSRT_Settings_User_Runtime {
|
|||||||
'slug' => $slug,
|
'slug' => $slug,
|
||||||
'label' => $label,
|
'label' => $label,
|
||||||
'tone' => $tone,
|
'tone' => $tone,
|
||||||
|
'icon_url' => $icon_url,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ defined('ABSPATH') || exit;
|
|||||||
|
|
||||||
if (!function_exists('aksrt_get_user_identity_signals')) {
|
if (!function_exists('aksrt_get_user_identity_signals')) {
|
||||||
/**
|
/**
|
||||||
* @return list<array{type:string,label:string,icon:string,tone:string,title?:string}>
|
* @return list<array{type?:string,label:string,icon:string,tone:string,title?:string,icon_url?:string,slug?:string,wear?:bool}>
|
||||||
*/
|
*/
|
||||||
function aksrt_get_user_identity_signals(int $user_id): array {
|
function aksrt_get_user_identity_signals(int $user_id): array {
|
||||||
if ($user_id <= 0) {
|
if ($user_id <= 0) {
|
||||||
@@ -27,6 +27,8 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
|
|||||||
/**
|
/**
|
||||||
* Render compact identity chips (verify / level / badges).
|
* Render compact identity chips (verify / level / badges).
|
||||||
*
|
*
|
||||||
|
* Badge signals with icon_url render as Zibll-like medal images.
|
||||||
|
*
|
||||||
* @param int $user_id
|
* @param int $user_id
|
||||||
* @param string $context author|comment|card
|
* @param string $context author|comment|card
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +44,7 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$signals = aksrt_get_user_identity_signals($user_id);
|
$signals = aksrt_get_user_identity_signals($user_id);
|
||||||
|
// Comments stay compact; author/card can show more.
|
||||||
$limit = $context === 'comment' ? 3 : 6;
|
$limit = $context === 'comment' ? 3 : 6;
|
||||||
$n = 0;
|
$n = 0;
|
||||||
foreach ($signals as $signal) {
|
foreach ($signals as $signal) {
|
||||||
@@ -54,12 +57,30 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
|
|||||||
}
|
}
|
||||||
$tone = sanitize_html_class((string) ($signal['tone'] ?? 'info'));
|
$tone = sanitize_html_class((string) ($signal['tone'] ?? 'info'));
|
||||||
$icon = sanitize_html_class((string) ($signal['icon'] ?? 'check'));
|
$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 .= '<img class="' . esc_attr($classes) . '" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" decoding="async" />';
|
||||||
|
$n++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Font Awesome: map short names to fa-* if needed.
|
// Font Awesome: map short names to fa-* if needed.
|
||||||
if ($icon !== '' && !str_contains($icon, 'fa-')) {
|
if ($icon !== '' && !str_contains($icon, 'fa-')) {
|
||||||
$icon = 'fa-' . $icon;
|
$icon = 'fa-' . $icon;
|
||||||
}
|
}
|
||||||
$title = (string) ($signal['title'] ?? $label);
|
$html .= '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--' . esc_attr($tone) . ' aksrt-identity-chip--' . esc_attr($context) . ($is_wear ? ' is-wear' : '') . '" title="' . esc_attr($title) . '">';
|
||||||
$html .= '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--' . esc_attr($tone) . ' aksrt-identity-chip--' . esc_attr($context) . '" title="' . esc_attr($title) . '">';
|
|
||||||
if ($icon !== '') {
|
if ($icon !== '') {
|
||||||
$html .= '<i class="fa ' . esc_attr($icon) . '" aria-hidden="true"></i> ';
|
$html .= '<i class="fa ' . esc_attr($icon) . '" aria-hidden="true"></i> ';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user