From 995ee8d2e1fb85cbb691da1f8d3921e6f927f76c Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 5 Jun 2026 13:23:51 +0800 Subject: [PATCH] fix: prevent theme flash when navigating instance pages from hub iframe Co-authored-by: Cursor --- crypto_monitor_binance/templates/index.html | 3 +- crypto_monitor_gate/templates/index.html | 3 +- crypto_monitor_gate_bot/templates/index.html | 3 +- crypto_monitor_okx/templates/index.html | 3 +- hub_bridge.py | 4 + manual_trading_hub/static/app.js | 23 +++--- manual_trading_hub/static/index.html | 2 +- static/instance_theme.js | 84 ++++++++++++++++++-- static/instance_theme_early.css | 28 +++++++ 9 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 static/instance_theme_early.css diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 5bc9b0e..37d6623 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -2,7 +2,8 @@ - + + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 5bc9b0e..37d6623 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -2,7 +2,8 @@ - + + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 6f17360..9ea2c40 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -2,7 +2,8 @@ - + + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 9a341e3..c7d87bc 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -2,7 +2,8 @@ - + + diff --git a/hub_bridge.py b/hub_bridge.py index 93618f9..82206c8 100644 --- a/hub_bridge.py +++ b/hub_bridge.py @@ -50,7 +50,11 @@ def install_instance_theme_static(app) -> None: repo_static = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") assets = { "instance_theme.js": "application/javascript; charset=utf-8", + "instance_theme_early.css": "text/css; charset=utf-8", "instance_theme.css": "text/css; charset=utf-8", + "instance_ui.js": "application/javascript; charset=utf-8", + "ai_review_render.js": "application/javascript; charset=utf-8", + "form_submit_guard.js": "application/javascript; charset=utf-8", "focus_chart_page.js": "application/javascript; charset=utf-8", "focus_chart_page.css": "text/css; charset=utf-8", } diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index f8721de..93de5e4 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -138,16 +138,19 @@ shell.classList.remove("hidden"); shell.setAttribute("aria-hidden", "false"); document.body.classList.add("hub-instance-frame-open"); - frame.addEventListener("load", function syncInstanceFrameTheme() { - try { - if (globalThis.HubTheme && typeof HubTheme.get === "function" && frame.contentWindow) { - frame.contentWindow.postMessage( - { type: "hub-theme-sync", theme: HubTheme.get() }, - "*" - ); - } - } catch (_) {} - }); + if (frame.dataset.themeSyncBound !== "1") { + frame.dataset.themeSyncBound = "1"; + frame.addEventListener("load", function syncInstanceFrameTheme() { + try { + if (globalThis.HubTheme && typeof HubTheme.get === "function" && frame.contentWindow) { + frame.contentWindow.postMessage( + { type: "hub-theme-sync", theme: HubTheme.get() }, + "*" + ); + } + } catch (_) {} + }); + } } function closeInstanceFrame() { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 3957d13..30ba99d 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -250,6 +250,6 @@
- + diff --git a/static/instance_theme.js b/static/instance_theme.js index 0758ff3..fd42da5 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -3,6 +3,7 @@ */ (function (global) { const STANDALONE_KEY = "instance-theme"; + const HUB_LINKED_THEME_KEY = "hub-linked-theme"; const META = { dark: "#0b0d14", light: "#d8e2ec" }; function normalize(theme) { @@ -32,6 +33,21 @@ return null; } + function readLinkedThemeStorage() { + try { + const t = sessionStorage.getItem(HUB_LINKED_THEME_KEY); + if (t === "light" || t === "dark") return t; + } catch (_) {} + return null; + } + + function writeLinkedThemeStorage(theme) { + if (!isHubLinked()) return; + try { + sessionStorage.setItem(HUB_LINKED_THEME_KEY, normalize(theme)); + } catch (_) {} + } + function getStandalone() { try { return normalize(localStorage.getItem(STANDALONE_KEY)); @@ -46,15 +62,15 @@ } catch (_) {} } + let _linkedTheme = null; + function get() { if (isHubLinked()) { - return themeFromUrl() || _linkedTheme || "dark"; + return themeFromUrl() || _linkedTheme || readLinkedThemeStorage() || "dark"; } return getStandalone(); } - let _linkedTheme = null; - /** 模板内联暗色 → 亮色(切换时重写 style 属性) */ const INLINE_HEX_LIGHT = { "#cfd3ef": "#1a2838", @@ -141,12 +157,43 @@ }); } + function mergeHubQueryIntoHref(href, theme) { + if (!href || href.startsWith("#") || href.startsWith("javascript:")) return href; + try { + const u = new URL(href, location.origin); + if (u.origin !== location.origin) return href; + if (isHubLinked()) { + u.searchParams.set("embed", "1"); + if (theme === "light" || theme === "dark") { + u.searchParams.set("hub_theme", theme); + } + } + return u.pathname + u.search + u.hash; + } catch (_) { + return href; + } + } + + function patchHubNavLinks(theme) { + if (!isHubLinked()) return; + const t = normalize(theme || get()); + document + .querySelectorAll(".top-nav a[href], .strategy-subnav a[href]") + .forEach((a) => { + const href = a.getAttribute("href"); + if (!href) return; + const next = mergeHubQueryIntoHref(href, t); + if (next !== href) a.setAttribute("href", next); + }); + } + function apply(theme, opts) { const options = opts || {}; const linked = isHubLinked(); const t = normalize(theme); if (linked) { _linkedTheme = t; + writeLinkedThemeStorage(t); } else if (!options.skipStore) { setStandalone(t); } @@ -155,7 +202,19 @@ const meta = document.querySelector('meta[name="theme-color"]'); if (meta) meta.setAttribute("content", META[t]); root.style.colorScheme = t; - syncInlineStyles(t); + if (document.body) { + syncInlineStyles(t); + patchHubNavLinks(t); + } else { + document.addEventListener( + "DOMContentLoaded", + function onDom() { + syncInlineStyles(t); + patchHubNavLinks(t); + }, + { once: true } + ); + } syncToggleUI(); document.dispatchEvent( new CustomEvent("instance-theme-change", { detail: { theme: t, hubLinked: linked } }) @@ -200,7 +259,7 @@ function boot() { if (isHubLinked()) { - apply(themeFromUrl() || "dark", { skipStore: true }); + apply(get(), { skipStore: true }); window.addEventListener("message", (ev) => initFromHubMessage(ev.data)); try { window.parent.postMessage({ type: "instance-theme-ready" }, "*"); @@ -208,12 +267,16 @@ } else { apply(getStandalone()); } + function observeDynamicLists() { ["journal-list", "review-list"].forEach((id) => { const el = document.getElementById(id); if (!el || el.dataset.instThemeObserved === "1") return; el.dataset.instThemeObserved = "1"; - new MutationObserver(() => syncInlineStyles(get())).observe(el, { + new MutationObserver(() => { + syncInlineStyles(get()); + patchHubNavLinks(get()); + }).observe(el, { childList: true, subtree: true, }); @@ -223,6 +286,7 @@ const onReady = () => { initToggleUI(); syncInlineStyles(get()); + patchHubNavLinks(get()); observeDynamicLists(); }; if (document.readyState === "loading") { @@ -232,7 +296,10 @@ } document.addEventListener("instance-theme-change", (ev) => { const t = ev.detail && ev.detail.theme; - if (t) syncInlineStyles(t); + if (t) { + syncInlineStyles(t); + patchHubNavLinks(t); + } }); } @@ -240,11 +307,14 @@ global.InstanceTheme = { STANDALONE_KEY, + HUB_LINKED_THEME_KEY, isHubLinked, get, apply, initToggleUI, syncToggleUI, syncInlineStyles, + patchHubNavLinks, + mergeHubQueryIntoHref, }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/static/instance_theme_early.css b/static/instance_theme_early.css new file mode 100644 index 0000000..a122d9b --- /dev/null +++ b/static/instance_theme_early.css @@ -0,0 +1,28 @@ +/* 紧接 instance_theme.js 之后加载,避免亮色下先闪暗色底 */ +html[data-theme="light"] body { + background: #d8e2ec !important; + color: #1a2838 !important; +} + +html[data-theme="light"] .header h1 { + color: #142232 !important; +} + +html[data-theme="light"] .top-nav a, +html[data-theme="light"] .strategy-subnav a { + background: #fff !important; + color: #006e9a !important; + border-color: rgba(0, 95, 140, 0.22) !important; +} + +html[data-theme="light"] .top-nav a.active, +html[data-theme="light"] .strategy-subnav a.active { + background: rgba(0, 110, 154, 0.12) !important; + color: #142232 !important; +} + +html[data-theme="light"] .card, +html[data-theme="light"] .stat-item { + background: #fff !important; + border-color: #b8c8d8 !important; +}