feat(frontend): unify avatar shape, move VIP icon to badge row
Make --radius-avatar the single source of truth for frontend user avatar shape. A new primitive in components-ext.css (img.avatar / avatar-mini / avatar-md / avatar-lg / .avatar-img img) forces a circle on every avatar image, which covers the bare avatar-mini in the ban and report modals, the classless img inside the VIP modal's .avatar-img, and the post-card meta avatar that previously depended on post-card.css being enqueued. The user center header frame, the public user home hero and the friends page avatar switch to the same token because route CSS loads after the public bundle and would otherwise override the primitive. The VIP icon no longer rides the avatar corner. The user card widget, the after-article author card and the sign-in modal stop opting into aksrt_get_avatar_box()'s corner badge, and the icon renders first in the badge/level row instead, since aksrt_get_user_name already orders name -> VIP -> auth -> medal -> level. The sign-in modal appends it after the display name and keeps the legacy precedence where a ban badge takes the VIP slot. The user card widget name row now shrinks its display name so the extra icon cannot overflow a narrow sidebar. Verified with the new frontend-avatar-shape-contract test plus post-visual, user-card-runtime, circular-button-icon-margin, user-features and page-display contracts, and php -l on every changed PHP file. The public bundle was rebuilt with tools/build-public-assets.mjs.
This commit is contained in:
@@ -2062,6 +2062,22 @@ html.dark .alert--info { color: #93C5FD; }
|
|||||||
.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); }
|
.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); }
|
||||||
.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); }
|
.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* 8b. 头像形状统一 — 圆形基底
|
||||||
|
* --radius-avatar 是前端用户头像形状的唯一来源。任何带头像类名的
|
||||||
|
* <img>(含未包 .avatar-img 的裸 img)都必须是正圆,供后续头像
|
||||||
|
* 挂件/装扮叠加。特异性 0,1,1 高于 main.css 的 .avatar{100px},
|
||||||
|
* 同值为圆;路由 CSS 中显式声明圆角的头像框需各自对齐。
|
||||||
|
* ============================================================ */
|
||||||
|
img.avatar,
|
||||||
|
img.avatar-mini,
|
||||||
|
img.avatar-md,
|
||||||
|
img.avatar-lg,
|
||||||
|
.avatar-img img {
|
||||||
|
border-radius: var(--radius-avatar, 50%);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 9. 涟漪点击效果 — .aksrt-ripple
|
* 9. 涟漪点击效果 — .aksrt-ripple
|
||||||
* 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave
|
* 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave
|
||||||
@@ -2638,7 +2654,8 @@ main.container,
|
|||||||
.user-card.author .user-name > name {
|
.user-card.author .user-name > name {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
.user-card.author .user-name > name > .display-name {
|
.user-card.author .user-name > name > .display-name,
|
||||||
|
.user-card.widget .user-name > name > .display-name {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -424,6 +424,22 @@
|
|||||||
.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); }
|
.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); }
|
||||||
.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); }
|
.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* 8b. 头像形状统一 — 圆形基底
|
||||||
|
* --radius-avatar 是前端用户头像形状的唯一来源。任何带头像类名的
|
||||||
|
* <img>(含未包 .avatar-img 的裸 img)都必须是正圆,供后续头像
|
||||||
|
* 挂件/装扮叠加。特异性 0,1,1 高于 main.css 的 .avatar{100px},
|
||||||
|
* 同值为圆;路由 CSS 中显式声明圆角的头像框需各自对齐。
|
||||||
|
* ============================================================ */
|
||||||
|
img.avatar,
|
||||||
|
img.avatar-mini,
|
||||||
|
img.avatar-md,
|
||||||
|
img.avatar-lg,
|
||||||
|
.avatar-img img {
|
||||||
|
border-radius: var(--radius-avatar, 50%);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 9. 涟漪点击效果 — .aksrt-ripple
|
* 9. 涟漪点击效果 — .aksrt-ripple
|
||||||
* 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave
|
* 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ main.container,
|
|||||||
.user-card.author .user-name > name {
|
.user-card.author .user-name > name {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
.user-card.author .user-name > name > .display-name {
|
.user-card.author .user-name > name > .display-name,
|
||||||
|
.user-card.widget .user-name > name > .display-name {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
.friend-card__avatar {
|
.friend-card__avatar {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius-avatar, 50%);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 140px;
|
width: 140px;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-avatar, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#aksrt-bg-file-input[hidden],
|
#aksrt-bg-file-input[hidden],
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 对齐 legacy: 140px, 6px 圆角 */
|
/* 140px 正方形 → 正圆:统一前端用户头像形状(--radius-avatar) */
|
||||||
.uc-header__avatar img {
|
.uc-header__avatar img {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
.aksrt-author-hero__avatar img {
|
.aksrt-author-hero__avatar img {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius-avatar, 50%);
|
||||||
border: 4px solid var(--surface-1);
|
border: 4px solid var(--surface-1);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
|||||||
@@ -384,12 +384,7 @@
|
|||||||
var avatarItem = node("li");
|
var avatarItem = node("li");
|
||||||
var avatarWrap = node("div", "avatar-img");
|
var avatarWrap = node("div", "avatar-img");
|
||||||
if (member.avatar_url) {
|
if (member.avatar_url) {
|
||||||
var avatar = node("img");
|
append(avatarWrap, imageNode(text(member.avatar_url), "avatar"));
|
||||||
avatar.src = text(member.avatar_url);
|
|
||||||
avatar.alt = "";
|
|
||||||
avatar.width = 42;
|
|
||||||
avatar.height = 42;
|
|
||||||
append(avatarWrap, avatar);
|
|
||||||
}
|
}
|
||||||
append(avatarItem, avatarWrap);
|
append(avatarItem, avatarWrap);
|
||||||
append(userBox, avatarItem);
|
append(userBox, avatarItem);
|
||||||
|
|||||||
@@ -285,11 +285,12 @@ if (!function_exists('aksrt_single_render_author_card')) :
|
|||||||
}
|
}
|
||||||
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
|
||||||
|
|
||||||
$avatar_box = aksrt_get_avatar_box($author_id, 'avatar-img avatar-lg');
|
// 会员/禁封徽章不再挂头像角标,改由名字行的会员位出图(aksrt_get_user_name)。
|
||||||
|
$avatar_box = aksrt_get_avatar_box($author_id, 'avatar-img avatar-lg', true, false);
|
||||||
$name_html = aksrt_get_user_name([
|
$name_html = aksrt_get_user_name([
|
||||||
'id' => $author_id,
|
'id' => $author_id,
|
||||||
'class' => 'flex1 flex ac',
|
'class' => 'flex1 flex ac',
|
||||||
'vip' => false,
|
'vip' => true,
|
||||||
'medal' => true,
|
'medal' => true,
|
||||||
'auth' => true,
|
'auth' => true,
|
||||||
'level' => true,
|
'level' => true,
|
||||||
|
|||||||
@@ -314,8 +314,13 @@ class Signin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$checkin_all_day = $total ? '<div class="mb6 em09">' . sprintf(__('累计签到%s天', 'aksrt'), '<badge class="c-blue">' . esc_html((string) $total) . '</badge>') . '</div>' : '';
|
$checkin_all_day = $total ? '<div class="mb6 em09">' . sprintf(__('累计签到%s天', 'aksrt'), '<badge class="c-blue">' . esc_html((string) $total) . '</badge>') . '</div>' : '';
|
||||||
$avatar_img = aksrt_get_avatar_box($user_id, 'avatar-img avatar-mini mr6', false, true);
|
// 会员图标不再挂头像角标,改到昵称行最前;禁封徽章依旧占据会员图标位。
|
||||||
$html .= '<div class="mb20"><div class="flex ac jsb"><div class="mb10">' . $avatar_img . esc_html($user->display_name) . '</div>' . $checkin_all_day . '</div><div class="flex ab jsb">' . $user_card . '</div></div>';
|
$avatar_img = aksrt_get_avatar_box($user_id, 'avatar-img avatar-mini mr6', false, false);
|
||||||
|
$name_badge = function_exists('aksrt_get_user_ban_name_badge') ? aksrt_get_user_ban_name_badge($user_id) : '';
|
||||||
|
if ($name_badge === '' && function_exists('aksrt_get_vip_icon')) {
|
||||||
|
$name_badge = aksrt_get_vip_icon($user_id);
|
||||||
|
}
|
||||||
|
$html .= '<div class="mb20"><div class="flex ac jsb"><div class="mb10">' . $avatar_img . esc_html($user->display_name) . $name_badge . '</div>' . $checkin_all_day . '</div><div class="flex ab jsb">' . $user_card . '</div></div>';
|
||||||
|
|
||||||
// 每日签到奖励卡片(参考主题:积分徽章 + 经验值徽章,经验值随等级系统展示)
|
// 每日签到奖励卡片(参考主题:积分徽章 + 经验值徽章,经验值随等级系统展示)
|
||||||
$reward_badge = $base ? '<badge class=" p2-10 mr6 c-green" data-toggle="tooltip" title="' . esc_attr(sprintf(__('积分奖励%s', 'aksrt'), $base)) . '">' . $points_svg('icon') . '+' . esc_html((string) $base) . '</badge>' : '';
|
$reward_badge = $base ? '<badge class=" p2-10 mr6 c-green" data-toggle="tooltip" title="' . esc_attr(sprintf(__('积分奖励%s', 'aksrt'), $base)) . '">' . $points_svg('icon') . '+' . esc_html((string) $base) . '</badge>' : '';
|
||||||
|
|||||||
@@ -162,11 +162,12 @@ final class UserRenderer {
|
|||||||
'post_style' => 'card',
|
'post_style' => 'card',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$avatar = aksrt_get_avatar_box($user_id, 'avatar-img avatar-lg');
|
// 会员/禁封徽章不再挂头像角标,改由名字行的会员位出图(aksrt_get_user_name)。
|
||||||
|
$avatar = aksrt_get_avatar_box($user_id, 'avatar-img avatar-lg', true, false);
|
||||||
$name = aksrt_get_user_name([
|
$name = aksrt_get_user_name([
|
||||||
'id' => $user_id,
|
'id' => $user_id,
|
||||||
'class' => 'flex1 flex ac',
|
'class' => 'flex1 flex ac',
|
||||||
'vip' => false,
|
'vip' => true,
|
||||||
'medal' => true,
|
'medal' => true,
|
||||||
'auth' => true,
|
'auth' => true,
|
||||||
'level' => true,
|
'level' => true,
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..', '..');
|
||||||
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||||
|
|
||||||
|
// --radius-avatar is the single source of truth for frontend avatar shape.
|
||||||
|
// Route CSS loads after the public bundle, and `.uc-header__avatar img` /
|
||||||
|
// `.aksrt-author-hero__avatar img` tie the primitive at 0,1,1 specificity,
|
||||||
|
// so every framed avatar must opt in explicitly.
|
||||||
|
const primitiveSelectors = [
|
||||||
|
'img.avatar,',
|
||||||
|
'img.avatar-mini,',
|
||||||
|
'img.avatar-md,',
|
||||||
|
'img.avatar-lg,',
|
||||||
|
'.avatar-img img {',
|
||||||
|
];
|
||||||
|
|
||||||
|
test('the global avatar shape primitive renders every avatar image circular', () => {
|
||||||
|
const css = read('assets/css/core/components-ext.css');
|
||||||
|
const start = css.indexOf('img.avatar,');
|
||||||
|
assert.notEqual(start, -1, 'missing the global avatar shape primitive');
|
||||||
|
const block = css.slice(start, css.indexOf('}', start));
|
||||||
|
|
||||||
|
for (const selector of primitiveSelectors) {
|
||||||
|
assert.ok(block.includes(selector), `primitive is missing selector: ${selector}`);
|
||||||
|
}
|
||||||
|
assert.match(block, /border-radius:\s*var\(--radius-avatar,\s*50%\)/);
|
||||||
|
assert.match(block, /object-fit:\s*cover/);
|
||||||
|
|
||||||
|
// The primitive must follow the existing avatar size system in the same file.
|
||||||
|
const sizeSystem = css.indexOf('.aksrt-avatar--xs {');
|
||||||
|
assert.ok(sizeSystem >= 0 && sizeSystem < start, 'primitive must sit after the avatar size system');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user center and public user home avatar frames are circular', () => {
|
||||||
|
const profile = read('assets/css/routes/profile/layout.css');
|
||||||
|
const userHome = read('assets/css/routes/user-home.css');
|
||||||
|
const friends = read('assets/css/routes/friends.css');
|
||||||
|
|
||||||
|
assert.match(profile, /\.uc-header__avatar\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s);
|
||||||
|
assert.doesNotMatch(profile, /\.uc-header__avatar\s*\{[^}]*border-radius:\s*6px/s);
|
||||||
|
// img and the upload overlay both inherit the frame radius.
|
||||||
|
assert.match(profile, /\.uc-header__avatar img\s*\{[^}]*border-radius:\s*inherit/s);
|
||||||
|
|
||||||
|
assert.match(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s);
|
||||||
|
assert.doesNotMatch(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius\)/s);
|
||||||
|
|
||||||
|
assert.match(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s);
|
||||||
|
assert.doesNotMatch(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius\)/s);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the user card name row can shrink once the VIP icon joins the badge row', () => {
|
||||||
|
const base = read('assets/css/frontend/base.css');
|
||||||
|
const start = base.indexOf('.user-card.author .user-name > name > .display-name,');
|
||||||
|
assert.notEqual(start, -1, 'missing the display-name shrink rule');
|
||||||
|
const block = base.slice(start, base.indexOf('}', start));
|
||||||
|
assert.ok(block.includes('.user-card.widget .user-name > name > .display-name'), 'shrink rule must cover the widget card too');
|
||||||
|
assert.match(block, /min-width:\s*0/);
|
||||||
|
assert.match(block, /overflow:\s*hidden/);
|
||||||
|
assert.match(block, /text-overflow:\s*ellipsis/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the VIP modal avatar carries the canonical avatar class', () => {
|
||||||
|
const payVip = read('assets/js/pay-vip.js');
|
||||||
|
assert.match(payVip, /imageNode\(text\(member\.avatar_url\), "avatar"\)/);
|
||||||
|
assert.doesNotMatch(payVip, /var avatar = node\("img"\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the VIP icon leaves the avatar corner and leads the badge row', () => {
|
||||||
|
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php');
|
||||||
|
const singleRender = read('inc/helpers/aksrt-single-render.php');
|
||||||
|
const signin = read('src/Credits/Signin.php');
|
||||||
|
|
||||||
|
for (const [file, source] of [['UserRenderer.php', userRenderer], ['aksrt-single-render.php', singleRender]]) {
|
||||||
|
assert.match(source, /aksrt_get_avatar_box\([^)]*'avatar-img avatar-lg',\s*true,\s*false\)/, `${file} must drop the avatar corner badge`);
|
||||||
|
assert.match(source, /'vip'\s+=>\s+true/, `${file} must render the VIP icon in the name row`);
|
||||||
|
assert.doesNotMatch(source, /'vip'\s+=>\s+false/);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(signin, /aksrt_get_avatar_box\(\$user_id,\s*'avatar-img avatar-mini mr6',\s*false,\s*false\)/);
|
||||||
|
assert.match(signin, /aksrt_get_user_ban_name_badge\(\$user_id\)/);
|
||||||
|
assert.match(signin, /aksrt_get_vip_icon\(\$user_id\)/);
|
||||||
|
assert.match(signin, /\$avatar_img \. esc_html\(\$user->display_name\) \. \$name_badge/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no frontend call site opts into the avatar corner VIP badge', () => {
|
||||||
|
const offenders = [];
|
||||||
|
const skip = new Set(['node_modules', 'vendor', '.git', 'tests', 'tools']);
|
||||||
|
const walk = (dir) => {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
if (skip.has(entry.name)) continue;
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) { walk(full); continue; }
|
||||||
|
if (!/\.(php|js)$/.test(entry.name) || entry.name.endsWith('.min.js')) continue;
|
||||||
|
const source = fs.readFileSync(full, 'utf8');
|
||||||
|
const pattern = /aksrt_get_avatar_box\([^;]*?,\s*(?:true|false),\s*true\s*\)/g;
|
||||||
|
for (const match of source.matchAll(pattern)) {
|
||||||
|
offenders.push(`${path.relative(root, full).split(path.sep).join('/')}: ${match[0]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(root);
|
||||||
|
assert.deepEqual(offenders, [], 'aksrt_get_avatar_box() must no longer mount a badge on the avatar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the avatar helper keeps its corner badge contract for callers that still want it', () => {
|
||||||
|
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||||
|
assert.match(avatar, /function aksrt_get_avatar_box\(\$id_or_email, string \$class = 'avatar-img', bool \$link = true, bool \$vip = true\)/);
|
||||||
|
assert.match(avatar, /class="avatar-badge"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the built public bundle carries the minified primitive', () => {
|
||||||
|
const bundle = read('assets/css/build/public.min.css');
|
||||||
|
// esbuild keeps the fallback list spacing inside var() untouched.
|
||||||
|
const minified = 'img.avatar,img.avatar-mini,img.avatar-md,img.avatar-lg,.avatar-img img{border-radius:var(--radius-avatar, 50%);object-fit:cover}';
|
||||||
|
assert.notEqual(bundle.indexOf(minified), -1, 'missing the minified avatar shape primitive');
|
||||||
|
});
|
||||||
@@ -301,7 +301,7 @@ test('after-box author card mirrors legacy user-card structure', () => {
|
|||||||
const frontendTags = read('inc/helpers/aksrt-frontend-tags.php');
|
const frontendTags = read('inc/helpers/aksrt-frontend-tags.php');
|
||||||
|
|
||||||
assert.match(render, /avatar-img avatar-lg/);
|
assert.match(render, /avatar-img avatar-lg/);
|
||||||
assert.match(render, /'vip'\s+=>\s+false/);
|
assert.match(render, /'vip'\s+=>\s+true/);
|
||||||
assert.match(render, /'follow'\s+=>\s+true/);
|
assert.match(render, /'follow'\s+=>\s+true/);
|
||||||
assert.match(render, /<div class="author-tag mt10 mini-scrollbar">/);
|
assert.match(render, /<div class="author-tag mt10 mini-scrollbar">/);
|
||||||
assert.match(render, /<div class="user-desc mt10 muted-2-color em09">/);
|
assert.match(render, /<div class="user-desc mt10 muted-2-color em09">/);
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ namespace {
|
|||||||
unset($subject);
|
unset($subject);
|
||||||
return '<img class="' . esc_attr((string) ($attrs['class'] ?? 'avatar')) . '" alt="' . esc_attr((string) ($attrs['alt'] ?? '')) . '" width="' . $size . '" height="' . $size . '">';
|
return '<img class="' . esc_attr((string) ($attrs['class'] ?? 'avatar')) . '" alt="' . esc_attr((string) ($attrs['alt'] ?? '')) . '" width="' . $size . '" height="' . $size . '">';
|
||||||
}
|
}
|
||||||
function aksrt_get_avatar_box($user_id, string $class): string {
|
function aksrt_get_avatar_box($user_id, string $class, bool $link = true, bool $vip = true): string {
|
||||||
return '<a href="https://example.test/user/' . (int) $user_id . '"><span class="' . esc_attr($class) . '"><img class="avatar"></span></a>';
|
$inner = '<span class="' . esc_attr($class) . '"><img class="avatar"></span>';
|
||||||
|
return $link ? '<a href="https://example.test/user/' . (int) $user_id . '">' . $inner . '</a>' : $inner;
|
||||||
}
|
}
|
||||||
function aksrt_get_user_name(array $args): string {
|
function aksrt_get_user_name(array $args): string {
|
||||||
return '<name class="' . esc_attr((string) ($args['class'] ?? '')) . '"><a class="display-name">User ' . (int) ($args['id'] ?? 0) . '</a></name>';
|
return '<name class="' . esc_attr((string) ($args['class'] ?? '')) . '"><a class="display-name">User ' . (int) ($args['id'] ?? 0) . '</a></name>';
|
||||||
|
|||||||
Reference in New Issue
Block a user