diff --git a/assets/css/frontend/pages/base-1.css b/assets/css/frontend/pages/base-1.css index 2cccdec1..51b25726 100644 --- a/assets/css/frontend/pages/base-1.css +++ b/assets/css/frontend/pages/base-1.css @@ -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; diff --git a/assets/css/routes/user-center/settings.css b/assets/css/routes/user-center/settings.css index 9dafdad8..fbdc466f 100644 --- a/assets/css/routes/user-center/settings.css +++ b/assets/css/routes/user-center/settings.css @@ -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); } diff --git a/inc/helpers/aksrt-icons.php b/inc/helpers/aksrt-icons.php index 828498b8..2d2d1b8d 100644 --- a/inc/helpers/aksrt-icons.php +++ b/inc/helpers/aksrt-icons.php @@ -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 */ @@ -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 markup for a brand/contact icon. + * Escaped markup for a brand/contact icon (FA5 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 ). + if (strpos($class, 'zibsvg-') === 0 || strpos($class, 'customsvg-') === 0) { + return aksrt_icon_markup($class, $extra_classes); + } + if ($class === '') { return ''; } diff --git a/inc/helpers/aksrt-links.php b/inc/helpers/aksrt-links.php index 6e553f7e..a425a332 100644 --- a/inc/helpers/aksrt-links.php +++ b/inc/helpers/aksrt-links.php @@ -428,10 +428,7 @@ if (!function_exists('aksrt_submit_links_modal')) : $body .= '

'; 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 .= ''; - } + $body .= (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('使用以下方式快速登录', 'aksrt')); } $body .= ''; } else { diff --git a/inc/helpers/header/aksrt-header-navbar.php b/inc/helpers/header/aksrt-header-navbar.php index 4c96ce6a..aab7fd4f 100644 --- a/inc/helpers/header/aksrt-header-navbar.php +++ b/inc/helpers/header/aksrt-header-navbar.php @@ -286,11 +286,7 @@ if (!function_exists('aksrt_header_user_box_html')) : $con = '
' . $href . '
'; 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 .= ''; - $con .= ''; - } + $con .= (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('快速登录', 'aksrt')); } } diff --git a/src/Auth/OAuth.php b/src/Auth/OAuth.php index a0bcde1f..afe21ca5 100644 --- a/src/Auth/OAuth.php +++ b/src/Auth/OAuth.php @@ -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(); - ?> - -
- - - - - -
- ' . esc_html($title) . '

' + : ''; + $html .= ''; } public function get_frontend_bindings_data(int $user_id): array { diff --git a/src/WidgetSystem/Renderers/UserRenderer.php b/src/WidgetSystem/Renderers/UserRenderer.php index 2bc8bd40..131e6c64 100644 --- a/src/WidgetSystem/Renderers/UserRenderer.php +++ b/src/WidgetSystem/Renderers/UserRenderer.php @@ -125,7 +125,7 @@ final class UserRenderer { if ($name === '' || $style_name === '' || $label === '' || $url === '') { continue; } - $html .= ''; + $html .= ''; } return $html . ''; } diff --git a/templates/parts/auth-content.php b/templates/parts/auth-content.php index 0bbcf171..87999052 100644 --- a/templates/parts/auth-content.php +++ b/templates/parts/auth-content.php @@ -147,6 +147,5 @@ $oauth_bind_pass = $oauth_intent === 'bind' -
diff --git a/tests/widget-system/auth-source-contract.test.cjs b/tests/widget-system/auth-source-contract.test.cjs index 9f7c2031..0e915734 100644 --- a/tests/widget-system/auth-source-contract.test.cjs +++ b/tests/widget-system/auth-source-contract.test.cjs @@ -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 . + 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, /'