fix: mobile hub AI keyboard layout and instance top nav scroll
Sync hub shell to visualViewport when the keyboard opens to prevent white screen above chat input. Make instance tabs horizontally scrollable with active tab centered on phone. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
<html lang="zh-CN" data-theme="dark">
|
<html lang="zh-CN" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<script src="/static/instance_theme.js?v=5"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
|
<script src="/static/instance_theme.js?v=6"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||||
|
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=12">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=13">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body data-page="{{ page }}">
|
<body data-page="{{ page }}">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<html lang="zh-CN" data-theme="dark">
|
<html lang="zh-CN" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<script src="/static/instance_theme.js?v=5"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
|
<script src="/static/instance_theme.js?v=6"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||||
|
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=12">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=13">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body data-page="{{ page }}">
|
<body data-page="{{ page }}">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<html lang="zh-CN" data-theme="dark">
|
<html lang="zh-CN" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<script src="/static/instance_theme.js?v=5"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
|
<script src="/static/instance_theme.js?v=6"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||||
|
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
@@ -269,7 +270,7 @@
|
|||||||
.stats-split-row{grid-template-columns:1fr}
|
.stats-split-row{grid-template-columns:1fr}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=12">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=13">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body data-page="{{ page }}">
|
<body data-page="{{ page }}">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<html lang="zh-CN" data-theme="dark">
|
<html lang="zh-CN" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<script src="/static/instance_theme.js?v=5"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
|
<script src="/static/instance_theme.js?v=6"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||||
|
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=12">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=13">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body data-page="{{ page }}">
|
<body data-page="{{ page }}">
|
||||||
|
|||||||
@@ -3815,15 +3815,31 @@ body.hub-page-ai #page-ai {
|
|||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
body.hub-page-ai .app-shell {
|
body.hub-page-ai .app-shell {
|
||||||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||||||
height: 100dvh;
|
height: var(--hub-vvh, 100dvh);
|
||||||
max-height: 100dvh;
|
max-height: var(--hub-vvh, 100dvh);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
will-change: transform, height;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.hub-page-ai {
|
body.hub-page-ai {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.hub-page-ai.hub-ai-keyboard-open .app-header,
|
||||||
|
body.hub-page-ai.hub-ai-keyboard-open #page-ai .page-head {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.hub-page-ai.hub-ai-keyboard-open .ai-mobile-tabs {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.hub-page-ai.hub-ai-keyboard-open .ai-panel-head {
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.hub-page-ai #page-ai {
|
body.hub-page-ai #page-ai {
|
||||||
@@ -3896,23 +3912,29 @@ body.hub-page-ai #page-ai {
|
|||||||
|
|
||||||
body.hub-page-ai .ai-chat-panel {
|
body.hub-page-ai .ai-chat-panel {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.hub-page-ai .ai-chat-messages {
|
body.hub-page-ai .ai-chat-messages {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.hub-page-ai .ai-chat-form {
|
body.hub-page-ai .ai-chat-form {
|
||||||
position: sticky;
|
position: relative;
|
||||||
bottom: 0;
|
flex-shrink: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px 0 max(10px, env(safe-area-inset-bottom));
|
padding: 10px 0 max(10px, env(safe-area-inset-bottom));
|
||||||
background: color-mix(in srgb, var(--panel) 92%, transparent);
|
background: var(--panel);
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-top: 1px solid var(--border-soft);
|
border-top: 1px solid var(--border-soft);
|
||||||
box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.28);
|
box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.hub-page-ai .ai-chat-form textarea {
|
body.hub-page-ai .ai-chat-form textarea {
|
||||||
|
|||||||
@@ -652,6 +652,7 @@
|
|||||||
el.classList.toggle("hidden", el.id !== pageId);
|
el.classList.toggle("hidden", el.id !== pageId);
|
||||||
});
|
});
|
||||||
document.body.classList.toggle("hub-page-ai", page === "ai");
|
document.body.classList.toggle("hub-page-ai", page === "ai");
|
||||||
|
syncHubAiMobileViewport();
|
||||||
if (page === "monitor") startMonitorPoll();
|
if (page === "monitor") startMonitorPoll();
|
||||||
else stopMonitorPoll();
|
else stopMonitorPoll();
|
||||||
if (page === "settings") loadSettingsUI();
|
if (page === "settings") loadSettingsUI();
|
||||||
@@ -1005,8 +1006,73 @@
|
|||||||
applyAiMobileTab();
|
applyAiMobileTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let syncHubAiMobileViewport = () => {};
|
||||||
|
|
||||||
|
function initHubAiMobileViewport() {
|
||||||
|
const mq = window.matchMedia("(max-width: 720px)");
|
||||||
|
const shell = document.querySelector(".app-shell");
|
||||||
|
const chatInput = document.getElementById("ai-chat-input");
|
||||||
|
if (!shell || !window.visualViewport) {
|
||||||
|
syncHubAiMobileViewport = () => {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollChatToEnd = () => {
|
||||||
|
const box = document.getElementById("ai-chat-messages");
|
||||||
|
if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; });
|
||||||
|
};
|
||||||
|
|
||||||
|
syncHubAiMobileViewport = () => {
|
||||||
|
const onAi = document.body.classList.contains("hub-page-ai");
|
||||||
|
if (!onAi || !mq.matches) {
|
||||||
|
shell.style.removeProperty("height");
|
||||||
|
shell.style.removeProperty("max-height");
|
||||||
|
shell.style.removeProperty("width");
|
||||||
|
shell.style.removeProperty("transform");
|
||||||
|
document.documentElement.style.removeProperty("--hub-vvh");
|
||||||
|
document.body.classList.remove("hub-ai-keyboard-open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const vv = window.visualViewport;
|
||||||
|
const h = Math.max(240, Math.round(vv.height));
|
||||||
|
const top = Math.round(vv.offsetTop || 0);
|
||||||
|
const left = Math.round(vv.offsetLeft || 0);
|
||||||
|
document.documentElement.style.setProperty("--hub-vvh", `${h}px`);
|
||||||
|
shell.style.height = `${h}px`;
|
||||||
|
shell.style.maxHeight = `${h}px`;
|
||||||
|
shell.style.width = `${Math.round(vv.width)}px`;
|
||||||
|
shell.style.transform =
|
||||||
|
top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : "";
|
||||||
|
const keyboardLikely =
|
||||||
|
top > 0 || h < window.innerHeight * 0.82 || document.activeElement === chatInput;
|
||||||
|
document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.visualViewport.addEventListener("resize", syncHubAiMobileViewport);
|
||||||
|
window.visualViewport.addEventListener("scroll", syncHubAiMobileViewport);
|
||||||
|
window.addEventListener("resize", syncHubAiMobileViewport);
|
||||||
|
window.addEventListener("orientationchange", () => {
|
||||||
|
setTimeout(syncHubAiMobileViewport, 80);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chatInput) {
|
||||||
|
chatInput.addEventListener("focus", () => {
|
||||||
|
syncHubAiMobileViewport();
|
||||||
|
scrollChatToEnd();
|
||||||
|
setTimeout(syncHubAiMobileViewport, 50);
|
||||||
|
setTimeout(syncHubAiMobileViewport, 280);
|
||||||
|
});
|
||||||
|
chatInput.addEventListener("blur", () => {
|
||||||
|
setTimeout(syncHubAiMobileViewport, 80);
|
||||||
|
setTimeout(syncHubAiMobileViewport, 320);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
syncHubAiMobileViewport();
|
||||||
|
}
|
||||||
|
|
||||||
function initMobileLayout() {
|
function initMobileLayout() {
|
||||||
initAiMobileTabs();
|
initAiMobileTabs();
|
||||||
|
initHubAiMobileViewport();
|
||||||
let resizeTimer = null;
|
let resizeTimer = null;
|
||||||
let wasMobile = isMobileLayout();
|
let wasMobile = isMobileLayout();
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script src="/assets/theme.js?v=20260604-hub-inst-theme"></script>
|
<script src="/assets/theme.js?v=20260604-hub-inst-theme"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
||||||
<meta name="theme-color" content="#0b0e18" />
|
<meta name="theme-color" content="#0b0e18" />
|
||||||
<meta name="apple-mobile-web-app-title" content="中控" />
|
<meta name="apple-mobile-web-app-title" content="中控" />
|
||||||
<link rel="icon" href="/assets/icons/favicon.ico" sizes="32x32" />
|
<link rel="icon" href="/assets/icons/favicon.ico" sizes="32x32" />
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260609-hub-mobile-ai-v2" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260609-hub-mobile-ai-v3" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-bg" aria-hidden="true"></div>
|
<div class="app-bg" aria-hidden="true"></div>
|
||||||
@@ -424,6 +424,6 @@
|
|||||||
<script src="/assets/chart.js?v=20260608-market-tz8"></script>
|
<script src="/assets/chart.js?v=20260608-market-tz8"></script>
|
||||||
<script src="/assets/archive.js?v=20260608-hub-archive-history"></script>
|
<script src="/assets/archive.js?v=20260608-hub-archive-history"></script>
|
||||||
<script src="/assets/ai_review_render.js?v=2"></script>
|
<script src="/assets/ai_review_render.js?v=2"></script>
|
||||||
<script src="/assets/app.js?v=20260609-hub-mobile-ai-v2"></script>
|
<script src="/assets/app.js?v=20260609-hub-mobile-ai-v3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -27,14 +27,31 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
.top-nav {
|
.top-nav {
|
||||||
display: flex;
|
display: flex !important;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap !important;
|
||||||
overflow-x: auto;
|
justify-content: flex-start !important;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
gap: 6px;
|
gap: 6px !important;
|
||||||
padding-bottom: 2px;
|
margin-bottom: 12px !important;
|
||||||
|
padding: 2px 2px 6px;
|
||||||
|
scroll-padding-inline: 10px;
|
||||||
|
touch-action: pan-x;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-nav::-webkit-scrollbar {
|
.top-nav::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -251,6 +251,32 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initMobileTopNav() {
|
||||||
|
const mq = window.matchMedia("(max-width: 720px)");
|
||||||
|
|
||||||
|
function scrollActiveTab(nav) {
|
||||||
|
const active = nav.querySelector("a.active");
|
||||||
|
if (!active) return;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
try {
|
||||||
|
active.scrollIntoView({ inline: "center", block: "nearest", behavior: "instant" });
|
||||||
|
} catch (_) {
|
||||||
|
active.scrollIntoView(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply() {
|
||||||
|
if (!mq.matches) return;
|
||||||
|
document.querySelectorAll(".top-nav").forEach(scrollActiveTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply();
|
||||||
|
mq.addEventListener("change", apply);
|
||||||
|
window.addEventListener("resize", apply);
|
||||||
|
window.addEventListener("orientationchange", apply);
|
||||||
|
}
|
||||||
|
|
||||||
function initFromHubMessage(data) {
|
function initFromHubMessage(data) {
|
||||||
if (!data || data.type !== "hub-theme-sync") return;
|
if (!data || data.type !== "hub-theme-sync") return;
|
||||||
if (!isHubLinked()) return;
|
if (!isHubLinked()) return;
|
||||||
@@ -285,6 +311,7 @@
|
|||||||
|
|
||||||
const onReady = () => {
|
const onReady = () => {
|
||||||
initToggleUI();
|
initToggleUI();
|
||||||
|
initMobileTopNav();
|
||||||
syncInlineStyles(get());
|
syncInlineStyles(get());
|
||||||
patchHubNavLinks(get());
|
patchHubNavLinks(get());
|
||||||
observeDynamicLists();
|
observeDynamicLists();
|
||||||
|
|||||||
Reference in New Issue
Block a user