From 00ae62f62b919b0dba79a1a317c000a12a15766a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Nov 2026 16:51:41 +0800 Subject: [PATCH] fix(auth): close the modal regressions and keep the header component off /sign/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defects slipped into the sign-card rebuild: The hidden-glyph rule was dropped. `.fa` sets display:inline-block, which outranks the UA [hidden] rule, so every hidden glyph stayed visible — most visibly a spinner rendered beside the login button at rest. `.sign [hidden]` restores the override. Closing account login produced an empty shell. The whole sign-in pane was gated on `!$close_signin`, and the social block carried the same gate, so a site with account login off but registration on still opened on an empty panel with no social entry. The pane is now rendered in three shapes: open registration falls through to the signup tab, a QR entry falls through to the QR tab, social-only renders a notice plus the social bar, and when nothing is available it states that login is closed. The modal close button sat in normal flow and pushed the card content down; `#u_sign .sign > .close` pins it back to the top-right corner. The /sign/ route is `index.php?jio_sign=1`, so WordPress reports it as home. The header multifunction component resolves page type by query flags and the home branch won, so the component appeared on the auth page whenever "首页" was in its scope — even though the sign page template itself was already excluded. `jio_header_slide_current_page_type()` now bails out on `jio_sign` before any of the is_* checks. The breakpoint overrides that were fighting the shared sheet between 641px and 991px are narrowed to the one offset that actually needed adjusting. Verification: the modal was rendered through a PHP stub, served with the real theme stylesheets, and driven by the real auth.js in a headless browser — tab switching, signin-mode switching, password reveal, close button and geometry at 1440/1100/900/760/500/390px. The header harness gained a `jio_sign` case asserting no markup, and 860 widget/navigation tests pass. --- assets/css/routes/sign.css | 34 ++++++++----------- inc/helpers/homepage/header-components.php | 6 ++++ templates/parts/auth-content.php | 29 +++++++++++++--- .../auth-source-contract.test.cjs | 3 +- .../header-multifunction-render-harness.php | 16 ++++++++- .../header-multifunction-render.test.cjs | 7 ++++ 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/assets/css/routes/sign.css b/assets/css/routes/sign.css index ee51c7f9..5bfd96eb 100644 --- a/assets/css/routes/sign.css +++ b/assets/css/routes/sign.css @@ -38,6 +38,8 @@ object-fit: cover; } +/* The shared sheet leaves the modal close button in normal flow, so it would + * push the card content down. */ #u_sign .sign > .close { position: absolute; top: 10px; @@ -46,6 +48,12 @@ font-size: 18px; } +/* .fa sets display:inline-block, which outranks the UA [hidden] rule, so every + * hidden glyph (spinner, password eye) stayed visible without this. */ +.sign [hidden] { + display: none !important; +} + body.auth-modal-open { overflow: hidden; } @@ -90,6 +98,7 @@ body.auth-modal-open { z-index: 2; } +/* ---- card internals ---- */ .sign .sign-passw { border: 0; background: none; @@ -124,25 +133,10 @@ body.auth-modal-open { color: #ff5473; } -@media (max-width: 991px) { - #u_sign .modal-dialog, - #u_sign.has-sign-img .modal-dialog { - width: auto; - margin: 30px; - } -} - -@media (max-width: 767px) { - .sign-page { - padding-top: 20px; - } - - .sign-row { - min-height: calc(100vh - 110px); - } - - .sign-page .sign { - width: 340px; - max-width: 100%; +/* Between the 992px split and the 640px collapse the shared sheet still offsets + * the card by 55%, but the dialog has already shrunk — give the card the room. */ +@media (max-width: 991px) and (min-width: 641px) { + #u_sign .sign-img + .sign { + margin-left: 45%; } } diff --git a/inc/helpers/homepage/header-components.php b/inc/helpers/homepage/header-components.php index a59e85c6..8d309556 100644 --- a/inc/helpers/homepage/header-components.php +++ b/inc/helpers/homepage/header-components.php @@ -53,6 +53,12 @@ if (!function_exists('jio_header_slide_current_page_type')) { * opt out entirely, like the excluded templates. */ function jio_header_slide_current_page_type(): string { + // The /sign/ route is `index.php?jio_sign=1`, which WordPress reports as + // home — the auth surface must never inherit the home component scope. + // (Pages using the sign template are already excluded further down.) + if ((string) get_query_var('jio_sign') !== '') { + return ''; + } if ((string) get_query_var('jio_go_redirect') !== '') { return ''; } diff --git a/templates/parts/auth-content.php b/templates/parts/auth-content.php index 2689a674..9efe71cc 100644 --- a/templates/parts/auth-content.php +++ b/templates/parts/auth-content.php @@ -63,14 +63,16 @@ $oauth_bind_pass = $oauth_intent === 'bind' : ''; $initial_tab = $auth_tab; -if ($initial_tab === 'signin' && $close_signin && $registration_open) { - $initial_tab = 'signup'; +if ($initial_tab === 'signin' && $close_signin) { + // Account login is off: land on registration when it is open, otherwise on + // the social/QR pane so the modal never opens on an empty sign-in panel. + $initial_tab = $registration_open ? 'signup' : ($wechat_qr ? 'oauth' : 'signin'); } if ($initial_tab === 'signup' && !$registration_open) { $initial_tab = 'signin'; } if ($initial_tab === 'oauth' && !$wechat_qr) { - $initial_tab = 'signin'; + $initial_tab = $close_signin && $registration_open ? 'signup' : 'signin'; } if ($initial_tab === 'bind' && !$is_oauth_bind) { $initial_tab = 'signin'; @@ -81,7 +83,7 @@ $show_pas = !$passwordless || $passwordless_mode !== 'only_nopas'; $nopas_active = $show_nopas && $passwordless_mode !== 'pas'; $signin_user_hint = ($passwordless || $signin_phone) ? __('用户名/手机号/邮箱', 'jio') : __('用户名或邮箱', 'jio'); $social_html = ''; -if (!$is_oauth_bind && !$close_signin && !empty($oauth_providers) && class_exists('\Jio\Auth\OAuth')) { +if (!$is_oauth_bind && !empty($oauth_providers) && class_exists('\Jio\Auth\OAuth')) { $social_html = \Jio\Auth\OAuth::get_instance()->render_social_buttons(__('社交账号登录', 'jio')); } $ref_tooltip = $ref_hint !== '' ? wp_strip_all_tags($ref_hint) : ''; @@ -96,7 +98,24 @@ $invite_tooltip = ($invite_mode === 'optional' && $invite_hint !== '') ? wp_stri
- + +
+
+
+
+
+
+ +
> +
+
+ +
+
+ +
+
+
>
diff --git a/tests/widget-system/auth-source-contract.test.cjs b/tests/widget-system/auth-source-contract.test.cjs index 44fb29cb..36b83a38 100644 --- a/tests/widget-system/auth-source-contract.test.cjs +++ b/tests/widget-system/auth-source-contract.test.cjs @@ -41,7 +41,8 @@ test('authentication shell reuses the shared sign-card CSS contract', () => { assert.match(mainCss, /\.signin-qrcode-img\s*\{[\s\S]*?width:\s*180px/); assert.match(mainCss, /#u_sign \.modal-dialog\s*\{\s*width:\s*800px;/); assert.match(signCss, /\.sign-page \.sign\s*\{\s*width:\s*350px/); - assert.match(signCss, /@media \(max-width:\s*767px\)[\s\S]*?\.sign-page \.sign\s*\{[\s\S]*?width:\s*340px/); + // Hidden glyphs must stay hidden even though .fa forces display:inline-block. + assert.match(signCss, /\.sign \[hidden\]\s*\{\s*display:\s*none !important;/); }); test('authentication settings use reference defaults and canonical keys', () => { diff --git a/tests/widget-system/header-multifunction-render-harness.php b/tests/widget-system/header-multifunction-render-harness.php index e9f8f622..876446c6 100644 --- a/tests/widget-system/header-multifunction-render-harness.php +++ b/tests/widget-system/header-multifunction-render-harness.php @@ -34,7 +34,8 @@ namespace { function is_search(): bool { return false; } function is_page(): bool { return false; } function is_home(): bool { return false; } - function get_query_var(string $key) { return ''; } + $GLOBALS['jio_query_vars'] = []; + function get_query_var(string $key) { return $GLOBALS['jio_query_vars'][$key] ?? ''; } function sanitize_key($value): string { return preg_replace('/[^a-z0-9_-]/', '', strtolower((string) $value)); } function sanitize_html_class($value): string { return preg_replace('/[^a-zA-Z0-9_-]/', '', (string) $value); } function wp_parse_args($args, array $defaults = []): array { return array_merge($defaults, is_array($args) ? $args : []); } @@ -111,6 +112,16 @@ namespace { 'button' => true, ]); + // /sign/ is `index.php?jio_sign=1`, so is_front_page() stays true. The auth + // surface must not inherit the home scope. + $GLOBALS['jio_query_vars']['jio_sign'] = '1'; + $auth_page_show = jio_header_slide_is_show(); + $auth_page_type = jio_header_slide_current_page_type(); + ob_start(); + jio_header_slide(); + $auth_page_markup = (string) ob_get_clean(); + $GLOBALS['jio_query_vars']['jio_sign'] = ''; + require_once dirname(__DIR__, 2) . '/src/Admin/PageConfigMetaBox.php'; $sanitize_cards = new \ReflectionMethod('Jio\\Admin\\PageConfigMetaBox', 'sanitize_card_options'); $page_config_save = $sanitize_cards->invoke(null, ['show_widget_bg' => true, 'cards' => []]); @@ -122,5 +133,8 @@ namespace { 'lazy_disabled' => $lazy_disabled, 'single_slide' => $single_slide, 'page_config_save' => $page_config_save, + 'auth_page_show' => $auth_page_show, + 'auth_page_type' => $auth_page_type, + 'auth_page_markup' => $auth_page_markup, ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } diff --git a/tests/widget-system/header-multifunction-render.test.cjs b/tests/widget-system/header-multifunction-render.test.cjs index aed31fd4..826a1225 100644 --- a/tests/widget-system/header-multifunction-render.test.cjs +++ b/tests/widget-system/header-multifunction-render.test.cjs @@ -42,6 +42,13 @@ test('header multifunction renders reference slide, search, and card contracts o assert.match(moduleBackground, /
Card one<\/div>/); assert.equal(JSON.parse(rendered.stdout).page_config_save.show_widget_bg, true); + // /sign/ resolves to index.php?jio_sign=1, so WordPress still reports it as + // home; the component must stay off even when "首页" is in its scope list. + const { auth_page_show: authShow, auth_page_type: authType, auth_page_markup: authMarkup } = JSON.parse(rendered.stdout); + assert.equal(authShow, false); + assert.equal(authType, ''); + assert.equal(authMarkup, ''); + const { lazy_disabled: eager, single_slide: single } = JSON.parse(rendered.stdout); assert.match(eager, /