fix: prevent theme flash when navigating instance pages from hub iframe
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/static/instance_theme.js?v=4"></script>
|
||||
<script src="/static/instance_theme.js?v=5"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<meta name="apple-mobile-web-app-title" content="监控">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/static/instance_theme.js?v=4"></script>
|
||||
<script src="/static/instance_theme.js?v=5"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<meta name="apple-mobile-web-app-title" content="监控">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/static/instance_theme.js?v=4"></script>
|
||||
<script src="/static/instance_theme.js?v=5"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<meta name="apple-mobile-web-app-title" content="监控">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/static/instance_theme.js?v=4"></script>
|
||||
<script src="/static/instance_theme.js?v=5"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=1">
|
||||
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<meta name="apple-mobile-web-app-title" content="监控">
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -250,6 +250,6 @@
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart.js?v=20260604-upnl-contracts"></script>
|
||||
<script src="/assets/app.js?v=20260604-risk-full-margin"></script>
|
||||
<script src="/assets/app.js?v=20260604-iframe-theme-flash"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user