fix(auth): align the social-login surfaces with the reference theme
The "other login methods" block drifted from zibll on three axes. Gitee was mapped to fa-code-branch because FA5 Free ships no Gitee brand glyph, so the button showed a generic branch icon; Google showed the monochrome FA glyph instead of the reference's multi-colour mark. The modal also printed an implicit agreement line under the social buttons, which the reference guest card does not have. And the top-nav user popover built its own .social-login-divider / .oauth-row / .oauth-btn markup and then wrapped that in a second separator and .social_loginbar, so the block rendered nested and unlike the reference. aksrt_brand_icon() now routes zibsvg-/customsvg- values through the shared icon renderer, and the brand map resolves gitee to zibsvg-gitee and google to zibsvg-google-color. Both sprite keys already exist in the runtime symbol sheet, which the loader pulls in on every frontend page. Because get_auth_providers_data() and binding_row() both go through aksrt_brand_icon(), one map change fixes the icon on the modal, the user card, the top-nav popover, the user-center binding list and the profile social links. render_social_buttons() is rewritten to the reference shape: an optional .social-separator, a .social_loginbar, and one .social-login-item <type> toggle-radius anchor per enabled provider with rel="nofollow" and a "<label>登录" title. oauth_button_lg switches to the button-lg variant with a label, and the WeChat entry carries qrcode-signin so auth.js opens the QR panel instead of navigating, matching the reference. The navbar and the link apply modal now append the block verbatim instead of re-wrapping it, and the user-card loop adopts the same item attributes. The retired .social-login-divider / .oauth-row / .oauth-btn rules are dropped from base-1.css, and the binding-list icon rule gains an svg selector so the sprite icons keep the 22px the FA glyphs had. Verification: node --test across widget-system, navigation, admin, asset-ledger, dev, front-submit and visual (613 tests), plus php -l on every changed PHP file. Extended the auth source contract with the agreement-line, brand-icon and popover-structure cases, and pinned the user-card guest markup.
This commit is contained in:
1 parent
6dc9265260
commit
c472045ce4
10 files changed
+127
-91
No files matched your search
@@ -166,56 +166,6 @@ html.dark .archive-timeline__badge {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 社交登录分隔线 */
|
||||
.social-login-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24px 0 16px;
|
||||
color: var(--color-ink-3, #94a3b8);
|
||||
font-size: 13px;
|
||||
}
|
||||
.social-login-divider::before,
|
||||
.social-login-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-line, #e2e8f0);
|
||||
}
|
||||
.social-login-divider span {
|
||||
padding: 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oauth-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.oauth-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--radius-btn);
|
||||
border: 1px solid var(--color-line);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-3);
|
||||
transition: border-color 0.2s, color 0.2s;
|
||||
}
|
||||
.oauth-btn i {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oauth-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.auth-footer-link {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
.aksrt-account-list--social .binding-item { border: 0; }
|
||||
.aksrt-account-list--social .aksrt-account-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto; text-decoration: none; }
|
||||
.aksrt-account-list--social .binding-item__icon { display: grid; place-items: center; width: 24px; height: 24px; color: var(--color-ink, #333); }
|
||||
.aksrt-account-list--social .binding-item__icon i { font-size: 22px; line-height: 1; }
|
||||
.aksrt-account-list--social .binding-item__icon i,
|
||||
.aksrt-account-list--social .binding-item__icon svg { font-size: 22px; line-height: 1; }
|
||||
.aksrt-account-list--social .binding-status { display: inline-flex; align-items: center; gap: 4px; color: var(--color-success, #16a34a); font-size: 11px; white-space: nowrap; }
|
||||
.aksrt-account-list--social .binding-status--idle { color: var(--muted-2-color, var(--color-ink-4, #999)); }
|
||||
.aksrt-account-list--social .binding-unbind-btn { color: var(--this-color, #e4a11b); }
|
||||
|
||||
@@ -42,7 +42,7 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_brand_icon_map')) :
|
||||
/**
|
||||
* Short key → FA5 class string (allowlisted).
|
||||
* Short key → FA5 class string or built-in SVG sprite key (allowlisted).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
@@ -52,10 +52,12 @@ function aksrt_brand_icon_map(): array {
|
||||
return $map;
|
||||
}
|
||||
|
||||
// FA5 Free has no bilibili/gitee brands — use solid stand-ins.
|
||||
// FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so
|
||||
// those two resolve to the theme's built-in SVG sprite (zibsvg-*) instead of
|
||||
// a solid stand-in; every other key stays on FA5.
|
||||
$map = [
|
||||
'github' => 'fa fab fa-github',
|
||||
'google' => 'fa fab fa-google',
|
||||
'google' => 'zibsvg-google-color',
|
||||
'x' => 'fa fab fa-twitter',
|
||||
'twitter' => 'fa fab fa-twitter',
|
||||
'email' => 'fa fas fa-envelope',
|
||||
@@ -73,7 +75,7 @@ function aksrt_brand_icon_map(): array {
|
||||
'instagram' => 'fa fab fa-instagram',
|
||||
'web' => 'fa fas fa-globe',
|
||||
'globe' => 'fa fas fa-globe',
|
||||
'gitee' => 'fa fas fa-code-branch',
|
||||
'gitee' => 'zibsvg-gitee',
|
||||
'baidu' => 'fa fas fa-paw',
|
||||
'alipay' => 'fa fab fa-alipay',
|
||||
];
|
||||
@@ -127,10 +129,10 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_brand_icon_class')) :
|
||||
/**
|
||||
* Resolve a short key or FA class string to an allowlisted FA5 class.
|
||||
* Resolve a short key, FA class string or sprite key to its icon value.
|
||||
*
|
||||
* @param string $name Short key or full FA class string.
|
||||
* @return string e.g. 'fa fab fa-github', or '' if unknown.
|
||||
* @param string $name Short key, full FA class string or zibsvg-/customsvg- value.
|
||||
* @return string e.g. 'fa fab fa-github' or 'zibsvg-gitee', or '' if unknown.
|
||||
*/
|
||||
function aksrt_brand_icon_class(string $name): string {
|
||||
$raw = trim($name);
|
||||
@@ -138,6 +140,12 @@ function aksrt_brand_icon_class(string $name): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Built-in SVG sprite and inline SVG values pass straight through; they are
|
||||
// not FA class strings and must survive to the markup renderer untouched.
|
||||
if (preg_match('/^(?:zibsvg|customsvg)-/i', $raw)) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
// Pass through already-valid full FA class strings (footer contact_more).
|
||||
if (preg_match('/^fa\s+(?:fas|far|fab)\s+fa-[\w-]+(?:\s+fa-[\w-]+)*$/i', $raw)) {
|
||||
// Prefer mapped short key when strip matches our catalog (normalizes weixin etc.).
|
||||
@@ -164,14 +172,21 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_brand_icon')) :
|
||||
/**
|
||||
* Escaped FA <i> markup for a brand/contact icon.
|
||||
* Escaped markup for a brand/contact icon (FA5 <i> or built-in SVG sprite).
|
||||
*
|
||||
* @param string $name Short key or FA class string.
|
||||
* @param string $name Short key, FA class string or zibsvg-/customsvg- value.
|
||||
* @param array|string $extra_classes Optional extra classes.
|
||||
* @return string Safe HTML or empty string when unknown.
|
||||
*/
|
||||
function aksrt_brand_icon(string $name, $extra_classes = []): string {
|
||||
$class = aksrt_brand_icon_class($name);
|
||||
|
||||
// Sprite and inline-SVG values carry their own element, so the shared icon
|
||||
// renderer owns the markup (it emits <svg class="icon"><use …>).
|
||||
if (strpos($class, 'zibsvg-') === 0 || strpos($class, 'customsvg-') === 0) {
|
||||
return aksrt_icon_markup($class, $extra_classes);
|
||||
}
|
||||
|
||||
if ($class === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -428,10 +428,7 @@ if (!function_exists('aksrt_submit_links_modal')) :
|
||||
$body .= '<p><a href="' . esc_url($sign_url) . '" class="signin-loader but c-blue padding-lg">'
|
||||
. '<i class="fa fas fa-fw fa-sign-in-alt mr10" aria-hidden="true"></i>' . esc_html__('登录', 'aksrt') . '</a></p>';
|
||||
if (class_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'render_social_buttons')) {
|
||||
$social = (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('使用以下方式快速登录', 'aksrt'));
|
||||
if ($social !== '') {
|
||||
$body .= '<div class="social_loginbar">' . $social . '</div>';
|
||||
}
|
||||
$body .= (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('使用以下方式快速登录', 'aksrt'));
|
||||
}
|
||||
$body .= '</div>';
|
||||
} else {
|
||||
|
||||
@@ -286,11 +286,7 @@ if (!function_exists('aksrt_header_user_box_html')) :
|
||||
|
||||
$con = '<div class="text-center">' . $href . '</div>';
|
||||
if (class_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'render_social_buttons')) {
|
||||
$oauth_html = (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('快速登录', 'aksrt'));
|
||||
if ($oauth_html !== '') {
|
||||
$con .= '<p class="social-separator separator muted-3-color em09 mt10">' . esc_html__('快速登录', 'aksrt') . '</p>';
|
||||
$con .= '<div class="social_loginbar">' . $oauth_html . '</div>';
|
||||
}
|
||||
$con .= (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('快速登录', 'aksrt'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-18
@@ -225,29 +225,48 @@ final class OAuth {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference-parity social login block (legacy zib_social_login()).
|
||||
*
|
||||
* Emits the muted separator plus a .social_loginbar of .social-login-item
|
||||
* buttons, one per enabled provider. Returns '' when nothing is enabled.
|
||||
*
|
||||
* @param string $title Separator label; an empty title drops the separator.
|
||||
*/
|
||||
public function render_social_buttons(string $title = ''): string {
|
||||
$items = $this->get_auth_providers_data();
|
||||
if ($items === []) {
|
||||
return '';
|
||||
}
|
||||
ob_start();
|
||||
?>
|
||||
<div class="social-login-divider">
|
||||
<span><?php echo esc_html($title) ?: esc_html__('or', 'aksrt'); ?></span>
|
||||
</div>
|
||||
<div class="oauth-row">
|
||||
<?php foreach ($items as $item) : ?>
|
||||
<a href="<?php echo esc_url((string) $item['url']); ?>"
|
||||
class="oauth-btn oauth-btn--<?php echo esc_attr((string) $item['name']); ?>"
|
||||
data-provider="<?php echo esc_attr((string) $item['name']); ?>"
|
||||
aria-label="<?php echo esc_attr((string) $item['label']); ?>"
|
||||
title="<?php echo esc_attr((string) $item['label']); ?>">
|
||||
<?php echo $item['icon']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
return (string) ob_get_clean();
|
||||
|
||||
$large = (bool) \AKSRT\Settings\Settings::get_value('oauth_button_lg', false);
|
||||
$html = $title !== ''
|
||||
? '<p class="social-separator separator muted-3-color em09 mt10">' . esc_html($title) . '</p>'
|
||||
: '';
|
||||
$html .= '<div class="social_loginbar">';
|
||||
foreach ($items as $item) {
|
||||
$name = sanitize_html_class((string) ($item['name'] ?? ''));
|
||||
$type = str_starts_with($name, 'clogin_') ? substr($name, 7) : $name;
|
||||
$label = trim((string) ($item['label'] ?? ''));
|
||||
$url = (string) ($item['url'] ?? '');
|
||||
if ($name === '' || $type === '' || $label === '' || $url === '') {
|
||||
continue;
|
||||
}
|
||||
$class = 'social-login-item ' . $type . ($large ? ' button-lg' : ' toggle-radius');
|
||||
if ($name === 'weixingzh') {
|
||||
// Reference parity: the WeChat entry opens the QR panel instead of
|
||||
// navigating, so auth.js routes it to the modal's oauth tab.
|
||||
$class .= ' qrcode-signin';
|
||||
}
|
||||
$html .= '<a rel="nofollow" href="' . esc_url($url) . '" class="' . esc_attr($class) . '"'
|
||||
. ' data-provider="' . esc_attr($name) . '"'
|
||||
. ' aria-label="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '"'
|
||||
. ' title="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '">'
|
||||
. $item['icon'] // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
. ($large ? '<span>' . esc_html(sprintf(__('%s登录', 'aksrt'), $label)) . '</span>' : '')
|
||||
. '</a>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
public function get_frontend_bindings_data(int $user_id): array {
|
||||
|
||||
@@ -125,7 +125,7 @@ final class UserRenderer {
|
||||
if ($name === '' || $style_name === '' || $label === '' || $url === '') {
|
||||
continue;
|
||||
}
|
||||
$html .= '<a rel="nofollow" href="' . esc_url($url) . '" class="social-login-item ' . esc_attr($style_name) . '" data-provider="' . esc_attr($name) . '" aria-label="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '" title="' . esc_attr($label) . '">' . (string) ($item['icon'] ?? '') . '</a>';
|
||||
$html .= '<a rel="nofollow" href="' . esc_url($url) . '" class="social-login-item ' . esc_attr($style_name) . ' toggle-radius" data-provider="' . esc_attr($name) . '" aria-label="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '" title="' . esc_attr(sprintf(__('%s登录', 'aksrt'), $label)) . '">' . (string) ($item['icon'] ?? '') . '</a>';
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
@@ -147,6 +147,5 @@ $oauth_bind_pass = $oauth_intent === 'bind'
|
||||
<?php foreach ($oauth_providers as $provider) : ?><?php if (($provider['name'] ?? '') === 'weixingzh') continue; ?><?php $provider_label = (string) ($provider['label'] ?? $provider['name'] ?? ''); ?><a class="social-login-item <?php echo esc_attr((string) ($provider['name'] ?? '')); ?>" href="<?php echo esc_url((string) ($provider['url'] ?? '#')); ?>" data-provider="<?php echo esc_attr((string) ($provider['name'] ?? '')); ?>" aria-label="<?php echo esc_attr(sprintf(__('%s登录', 'aksrt'), $provider_label)); ?>" title="<?php echo esc_attr($provider_label); ?>"><?php echo $provider['icon'] ?? ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php if ($oauth_large) : ?><span><?php printf(esc_html__('%s登录', 'aksrt'), esc_html($provider_label)); ?></span><?php endif; ?></a><?php endforeach; ?>
|
||||
<?php if ($wechat_qr) : ?><button type="button" class="social-login-item weixingzh" data-auth-tab="oauth" aria-label="<?php esc_attr_e('微信扫码登录', 'aksrt'); ?>" title="<?php esc_attr_e('微信扫码登录', 'aksrt'); ?>"><i class="fa fab fa-weixin" aria-hidden="true"></i><?php if ($oauth_large) : ?><span><?php esc_html_e('微信登录', 'aksrt'); ?></span><?php endif; ?></button><?php endif; ?>
|
||||
</div></div>
|
||||
<?php if (!empty($agreement['enabled'])) : ?><div class="sign-agreement sign-agreement-implicit"><?php esc_html_e('使用社交账号登录即表示', 'aksrt'); ?><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -239,3 +239,58 @@ test('OAuth frontend is accessible, redirect-aware, and removes retired provider
|
||||
assert.doesNotMatch(signCss + mainCss, /social-login-item[^\n{]*(?:baidu|alipay)|social-login-item\.(?:baidu|alipay)/i);
|
||||
assert.match(mainCss, /\.pay-box \.but\.baidu/);
|
||||
});
|
||||
|
||||
test('the social-login area drops the implicit agreement line', () => {
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php');
|
||||
|
||||
// The reference shows no agreement copy under the social buttons; only the
|
||||
// registration panel keeps its own, and it stays reachable.
|
||||
assert.doesNotMatch(shared, /使用社交账号登录即表示/);
|
||||
assert.match(shared, /注册即表示/);
|
||||
assert.match(shared, /name="agree_terms"[\s\S]*?required/);
|
||||
assert.doesNotMatch(userRenderer, /使用社交账号登录即表示|我已阅读并同意/);
|
||||
});
|
||||
|
||||
test('brand icons resolve Gitee and Google to the reference SVG sprite', () => {
|
||||
const icons = read('inc/helpers/aksrt-icons.php');
|
||||
const sprites = read('assets/js/runtime/svg-icon.js');
|
||||
|
||||
// FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so both
|
||||
// resolve to the theme's own symbol sheet exactly like the reference does.
|
||||
assert.match(icons, /'gitee'\s*=>\s*'zibsvg-gitee'/);
|
||||
assert.match(icons, /'google'\s*=>\s*'zibsvg-google-color'/);
|
||||
assert.match(icons, /'github'\s*=>\s*'fa fab fa-github'/);
|
||||
assert.match(icons, /'wechat'\s*=>\s*'fa fab fa-weixin'/);
|
||||
assert.match(sprites, /aksrt_svgs\['gitee'\]/);
|
||||
assert.match(sprites, /aksrt_svgs\['google-color'\]/);
|
||||
// Sprite values must reach the shared icon renderer instead of being wrapped
|
||||
// in a bogus <i class="zibsvg-…">.
|
||||
assert.match(icons, /strpos\(\$class, 'zibsvg-'\) === 0[\s\S]*?aksrt_icon_markup\(\$class, \$extra_classes\)/);
|
||||
});
|
||||
|
||||
test('the top-nav user popover reproduces the reference quick-login block', () => {
|
||||
const oauth = read('src/Auth/OAuth.php');
|
||||
const header = read('inc/helpers/header/aksrt-header-navbar.php');
|
||||
const links = read('inc/helpers/aksrt-links.php');
|
||||
const legacyCss = read('assets/css/frontend/pages/base-1.css');
|
||||
|
||||
assert.match(oauth, /social-separator separator muted-3-color em09 mt10/);
|
||||
assert.match(oauth, /'<div class="social_loginbar">'/);
|
||||
assert.match(oauth, /'social-login-item ' \. \$type/);
|
||||
assert.match(oauth, /' button-lg' : ' toggle-radius'/);
|
||||
assert.match(oauth, /rel="nofollow"/);
|
||||
assert.match(oauth, /qrcode-signin/);
|
||||
assert.match(oauth, /oauth_button_lg/);
|
||||
|
||||
// Callers append the block verbatim — no hand-rolled separator or wrapper.
|
||||
assert.match(header, /render_social_buttons\(__\('快速登录', 'aksrt'\)\)/);
|
||||
assert.doesNotMatch(header, /social-separator|social_loginbar/);
|
||||
assert.match(links, /render_social_buttons\(__\('使用以下方式快速登录', 'aksrt'\)\)/);
|
||||
assert.doesNotMatch(links, /social_loginbar/);
|
||||
|
||||
// The retired .social-login-divider / .oauth-row / .oauth-btn markup and its
|
||||
// stylesheet are gone.
|
||||
assert.doesNotMatch(oauth, /oauth-row|oauth-btn|social-login-divider/);
|
||||
assert.doesNotMatch(legacyCss, /\.oauth-row|\.oauth-btn|\.social-login-divider/);
|
||||
});
|
||||
@@ -23,9 +23,13 @@ test('user and author widgets render the reference state contract', () => {
|
||||
assert.match(data.guest, /signup-loader ml10 but jb-yellow padding-lg/)
|
||||
assert.match(data.guest, /social-separator separator muted-3-color em09 mt20 mb20/)
|
||||
assert.match(data.guest, /social_loginbar/)
|
||||
assert.match(data.guest, /class="social-login-item github"/)
|
||||
assert.match(data.guest, /class="social-login-item qq" data-provider="clogin_qq"/)
|
||||
assert.match(data.guest, /class="social-login-item github toggle-radius"/)
|
||||
assert.match(data.guest, /class="social-login-item qq toggle-radius" data-provider="clogin_qq"/)
|
||||
assert.match(data.guest, /title="GitHub登录"/)
|
||||
assert.doesNotMatch(data.guest, /aksrt-widget|aksrt-panel-wrap|user-card__auth-btns|oauth-row|oauth-btn/)
|
||||
// The social-login area carries no agreement copy; the reference guest card
|
||||
// only shows the separator, the .social_loginbar and the provider buttons.
|
||||
assert.doesNotMatch(data.guest, /我已阅读并同意|使用社交账号登录即表示|注册即表示/)
|
||||
|
||||
assert.match(data.guest_no_registration, /signin-loader but jb-blue padding-lg/)
|
||||
assert.doesNotMatch(data.guest_no_registration, /signup-loader/)
|
||||
|
||||
Reference in new issue
Block a user