Merge pull request 'fix(ui): render user level icon inline beside the name' (#328) from fix/level-icon-inline into main

Reviewed-on: CYL/wordpress-theme-aksrt#328
This commit is contained in:
2026-07-30 18:20:16 +08:00
13 changed files with 134 additions and 34 deletions
+47
View File
@@ -162,6 +162,53 @@ main.container,
}
}
/* ========== User identity inline icons ==========
* Level / medal icons ride inline to the right of a username. Centralized here
* so every route (header panel, user center, author hero, single, comments,
* widgets) renders the same compact badge instead of the raw intrinsic PNG size.
*
* Mirrors zibll `.img-icon { height: 1.1em; display: inline-block; vertical-align: -.15em }`
* — sizes to the surrounding line height, never dominates the row. Localized
* exceptions (UC level hero, admin column) override `height` per context.
*/
.aksrt-user-level-icon,
.aksrt-identity-medal {
display: inline-block;
width: auto;
height: 1.1em;
max-width: 6em;
object-fit: contain;
vertical-align: -.15em;
flex: 0 0 auto;
}
/* Comment context: a touch smaller to fit the 14px author line. */
.aksrt-identity-medal--comment,
.aksrt-user-level-icon--comment {
height: 1em;
vertical-align: -.12em;
}
/* No-wrap inline name rows — keep the level icon on the same line as the name.
* The non-standard <name> element (zibll compat) plus .user-name / author-hero /
* uc-header name rows must not flex-wrap, or the badge drops to a new line.
* The comment header may still wrap (avatar + name + like can overflow on
* narrow viewports), so it keeps flex-wrap but the inner <name> never does. */
.article-avatar name,
.article-avatar .name,
.user-name,
.aksrt-author-hero__name,
.uc-header__name,
.aksrt-header-badges {
flex-wrap: nowrap;
white-space: nowrap;
}
.aksrt-author-hero__name,
.uc-header__name {
display: inline-flex;
align-items: center;
gap: 4px;
}
/* ========== 通用空状态 ========== */
.no-results {
padding: 48px 20px;
@@ -30,6 +30,9 @@
.comment-header .author-box { display: flex; align-items: center; gap: 8px; width: 100%; }
.comment-header .author-box name,
.comment-header .author-box .flex1 { flex: 1; min-width: 0; }
/* Keep name + level badge on one line; clamp the name so the badge never wraps. */
.comment-header .author-box name { flex-wrap: nowrap; }
.comment-header .author-box .aksrt-comment__author { max-width: 14em; }
.comment-header .aksrt-comment__like,
.comment-header .action-comment-like {
margin-left: auto; display: inline-flex; align-items: center; gap: 4px;
+12 -5
View File
@@ -754,15 +754,16 @@ body.dark-theme .comment-login-prompt__inner {
.medal-icon.img-icon,
.aksrt-identity-medal {
display: inline-block;
width: 1.35em;
height: 1.35em;
width: auto;
height: 1.1em;
max-width: 6em;
object-fit: contain;
vertical-align: middle;
vertical-align: -.15em;
margin-left: 4px;
}
.aksrt-identity-medal--comment {
width: 1.15em;
height: 1.15em;
width: auto;
height: 1em;
}
.aksrt-identity-medal.is-wear {
outline: 1px solid rgba(245, 158, 11, 0.45);
@@ -782,3 +783,9 @@ body.dark-theme .comment-login-prompt__inner {
align-items: center;
gap: 4px;
}
/* Inline name + level badge stay on one line; only the outer author-box wraps. */
.aksrt-comment__header name,
.aksrt-comment__header .author-box > name {
flex-wrap: nowrap;
white-space: nowrap;
}
@@ -210,6 +210,32 @@
flex-wrap: wrap;
gap: 4px;
}
/* Inline name + level badge in the dropdown profile row — one line, no wrap. */
.sub-user-box .user-right > .flex1 {
min-width: 0;
}
.sub-user-box .user-right b {
display: inline-flex;
align-items: center;
gap: 4px;
flex-wrap: nowrap;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
}
.sub-user-box .user-right b .display-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
max-width: 100%;
}
.sub-user-box .user-right .aksrt-user-level-icon,
.sub-user-box .user-right .aksrt-identity-medal {
height: 1em;
vertical-align: -.12em;
flex: 0 0 auto;
}
/* User dropdown light-theme text contrast */
.user-dropdown.sub-user-box,
@@ -640,7 +640,7 @@ html.dark .post-drop-meta__item:hover {
.user-card.author .user-avatar { display: flex; justify-content: center; }
.user-card.author .user-avatar .avatar,
.user-card.author .user-avatar img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; }
.user-card.author .user-name { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px; }
.user-card.author .user-name { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 4px; }
.user-card.author .user-name .display-name { font-weight: 700; color: var(--key-color, inherit); }
.user-card.author .user-desc { text-align: center; }
.user-card.author .user-btns { text-align: center; }
+7 -5
View File
@@ -277,7 +277,7 @@
}
.article-avatar name.flex {
gap: 4px;
flex-wrap: wrap;
flex-wrap: nowrap;
}
.article-avatar .aksrt-vip-badge,
.article-avatar .aksrt-identity-chip {
@@ -286,11 +286,13 @@
vertical-align: middle;
}
.article-avatar .medal-icon.img-icon,
.article-avatar .aksrt-identity-medal {
width: 1.4em;
height: 1.4em;
.article-avatar .aksrt-identity-medal,
.article-avatar .aksrt-user-level-icon {
width: auto;
height: 1.2em;
max-width: 6em;
object-fit: contain;
vertical-align: middle;
vertical-align: -.15em;
margin-left: 4px;
}
.article-avatar .display-name {
+1 -8
View File
@@ -535,14 +535,7 @@
color: #fff;
}
.aksrt-user-level-icon {
display: inline-block;
width: auto;
height: 1.15em;
max-width: 6em;
object-fit: contain;
vertical-align: -.18em;
}
/* Level icon base sizing is centralized in assets/css/frontend/base.css. */
/* 用户中心 layout 间距;双栏 / sticky 见 user-center/shell.css(body.aksrt-user-center)。 */
.aksrt-profile__layout {
+3 -9
View File
@@ -1,12 +1,6 @@
/* User level — dedicated center panel */
.aksrt-user-level-icon {
display: inline-block;
width: auto;
height: 1.35em;
max-width: 6em;
object-fit: contain;
vertical-align: -.2em;
}
/* User level — dedicated center panel.
* Base level icon sizing lives in assets/css/frontend/base.css (1.1em, inline).
* This file keeps only the UC-level-specific hero/road overrides + admin column. */
.aksrt-user-level-icon--admin { height: 22px; }
.uc-level-page { display: grid; gap: 16px; }
+2 -2
View File
@@ -91,8 +91,8 @@ if (!function_exists('aksrt_header_user_badges_row')) :
if ($user_id <= 0 || !function_exists('aksrt_render_user_identity_chips')) {
return '';
}
// VIP already shown next to display name in the header profile row.
$chips = aksrt_render_user_identity_chips($user_id, 'card', ['include_vip' => false]);
// VIP + level already shown next to display name in the header profile row.
$chips = aksrt_render_user_identity_chips($user_id, 'card', ['include_vip' => false, 'skip_level' => true]);
if ($chips === '') {
return '';
}
+9 -2
View File
@@ -35,6 +35,7 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
* Optional. Additional options.
*
* @type bool $include_vip Whether to prepend VIP badge. Default true.
* @type bool $skip_level Whether to omit the level chip (rendered inline elsewhere). Default false.
* }
*/
function aksrt_render_user_identity_chips(int $user_id, string $context = 'author', array $args = []): string {
@@ -43,6 +44,7 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
}
$args = wp_parse_args($args, [
'include_vip' => true,
'skip_level' => false,
]);
$html = '';
if (!empty($args['include_vip']) && function_exists('aksrt_render_vip_badge')) {
@@ -63,12 +65,16 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
if ($label === '') {
continue;
}
$type = sanitize_html_class((string) ($signal['type'] ?? ''));
// Level is rendered inline next to the name by the caller; skip it here.
if (!empty($args['skip_level']) && $type === 'level') {
continue;
}
$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 !== '') {
@@ -162,7 +168,8 @@ if (!function_exists('aksrt_get_user_name')) {
if ($icon_url !== '') {
$level_html = '<img class="aksrt-user-level-icon img-icon ml3" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" decoding="async" />';
} else {
$level_html = '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--info" title="' . esc_attr($title) . '"><i class="fa fa-layer-group" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
// Text fallback mirrors zibll LV badge: small pill, never oversized.
$level_html = '<span class="badg badg-sm ml3 aksrt-user-level-badge" title="' . esc_attr($title) . '">' . esc_html($label) . '</span>';
}
} elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) {
if ($icon_url !== '') {
+5 -1
View File
@@ -151,6 +151,10 @@ if (!function_exists('aksrt_header_user_box_html')) :
$avatar = get_avatar($user_id, 44, '', esc_attr($display_name), ['loading' => 'lazy']);
$desc = (string) ($ctx['user_desc'] ?? '');
$vip_badge = function_exists('aksrt_render_vip_badge') ? aksrt_render_vip_badge($user_id, 'sm') : '';
// Level icon rides inline to the right of the display name (zibll parity).
$level_badge = (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html'))
? \AKSRT\User\Level::get_badge_html($user_id, 'aksrt-user-level-icon--header')
: '';
$payvip = function_exists('aksrt_header_payvip_bar') ? aksrt_header_payvip_bar($user_id) : '';
// nav_user_pay_vip gate: empty payvip when switch off for non-VIP CTA only — helper already handles VIP status.
if (empty($ctx['nav_user_pay_vip']) && $payvip !== '' && strpos($payvip, 'is-vip') === false) {
@@ -239,7 +243,7 @@ if (!function_exists('aksrt_header_user_box_html')) :
$con .= $avatar;
$con .= '<div class="user-right flex flex1 ac jsb ml10">';
$con .= '<div class="flex1" style="max-width: calc(100% - 40px);"><b>'
. esc_html($display_name) . $vip_badge
. '<span class="display-name text-ellipsis">' . esc_html($display_name) . '</span>' . $vip_badge . $level_badge
. '</b><div class="px12 muted-2-color text-ellipsis">' . esc_html($desc) . '</div></div>';
$con .= '</div>';
$con .= $msg_icon;
@@ -41,6 +41,10 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
if ($vip_badge) {
echo $vip_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// Level icon inline to the right of the name (zibll parity).
if (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html')) {
echo \AKSRT\User\Level::get_badge_html((int) $user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</h1>
<?php if ($target_user->description) : ?>
@@ -49,6 +53,12 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
<?php if (!empty($profile_signals)) : ?>
<div class="aksrt-identity-chips">
<?php foreach ($profile_signals as $signal) : ?>
<?php
// Level is rendered inline next to the name above; skip it here.
if ((string) ($signal['type'] ?? '') === 'level') {
continue;
}
?>
<span class="aksrt-identity-chip aksrt-identity-chip--<?php echo esc_attr($signal['tone']); ?>">
<i class="fas fa-<?php echo esc_attr($signal['icon']); ?>" aria-hidden="true"></i>
<span><?php echo esc_html($signal['label']); ?></span>
+8 -1
View File
@@ -98,6 +98,10 @@ $pm_url = ($pm_enabled && $messages_url !== '')
if ($vip_badge) {
echo $vip_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// Level icon inline to the right of the name (zibll parity).
if (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html')) {
echo \AKSRT\User\Level::get_badge_html($user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</h1>
<?php endif; ?>
@@ -124,7 +128,10 @@ $pm_url = ($pm_enabled && $messages_url !== '')
<?php echo $city_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif; ?>
<?php if (function_exists('aksrt_render_user_identity_chips')) : ?>
<?php echo aksrt_render_user_identity_chips($user_id, 'author', ['include_vip' => false]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php
// Level already rendered inline next to the name above; skip it here.
echo aksrt_render_user_identity_chips($user_id, 'author', ['include_vip' => false, 'skip_level' => true]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<?php endif; ?>
</div>
<?php endif; ?>