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">
|
<html lang="zh-CN" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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="theme-color" content="#0b0d14">
|
||||||
<meta name="apple-mobile-web-app-title" content="监控">
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
|||||||
@@ -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=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="theme-color" content="#0b0d14">
|
||||||
<meta name="apple-mobile-web-app-title" content="监控">
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
|||||||
@@ -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=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="theme-color" content="#0b0d14">
|
||||||
<meta name="apple-mobile-web-app-title" content="监控">
|
<meta name="apple-mobile-web-app-title" content="监控">
|
||||||
|
|||||||
@@ -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=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="theme-color" content="#0b0d14">
|
||||||
<meta name="apple-mobile-web-app-title" content="监控">
|
<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")
|
repo_static = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
|
||||||
assets = {
|
assets = {
|
||||||
"instance_theme.js": "application/javascript; charset=utf-8",
|
"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_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.js": "application/javascript; charset=utf-8",
|
||||||
"focus_chart_page.css": "text/css; charset=utf-8",
|
"focus_chart_page.css": "text/css; charset=utf-8",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,16 +138,19 @@
|
|||||||
shell.classList.remove("hidden");
|
shell.classList.remove("hidden");
|
||||||
shell.setAttribute("aria-hidden", "false");
|
shell.setAttribute("aria-hidden", "false");
|
||||||
document.body.classList.add("hub-instance-frame-open");
|
document.body.classList.add("hub-instance-frame-open");
|
||||||
frame.addEventListener("load", function syncInstanceFrameTheme() {
|
if (frame.dataset.themeSyncBound !== "1") {
|
||||||
try {
|
frame.dataset.themeSyncBound = "1";
|
||||||
if (globalThis.HubTheme && typeof HubTheme.get === "function" && frame.contentWindow) {
|
frame.addEventListener("load", function syncInstanceFrameTheme() {
|
||||||
frame.contentWindow.postMessage(
|
try {
|
||||||
{ type: "hub-theme-sync", theme: HubTheme.get() },
|
if (globalThis.HubTheme && typeof HubTheme.get === "function" && frame.contentWindow) {
|
||||||
"*"
|
frame.contentWindow.postMessage(
|
||||||
);
|
{ type: "hub-theme-sync", theme: HubTheme.get() },
|
||||||
}
|
"*"
|
||||||
} catch (_) {}
|
);
|
||||||
});
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeInstanceFrame() {
|
function closeInstanceFrame() {
|
||||||
|
|||||||
@@ -250,6 +250,6 @@
|
|||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
<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/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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
(function (global) {
|
(function (global) {
|
||||||
const STANDALONE_KEY = "instance-theme";
|
const STANDALONE_KEY = "instance-theme";
|
||||||
|
const HUB_LINKED_THEME_KEY = "hub-linked-theme";
|
||||||
const META = { dark: "#0b0d14", light: "#d8e2ec" };
|
const META = { dark: "#0b0d14", light: "#d8e2ec" };
|
||||||
|
|
||||||
function normalize(theme) {
|
function normalize(theme) {
|
||||||
@@ -32,6 +33,21 @@
|
|||||||
return null;
|
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() {
|
function getStandalone() {
|
||||||
try {
|
try {
|
||||||
return normalize(localStorage.getItem(STANDALONE_KEY));
|
return normalize(localStorage.getItem(STANDALONE_KEY));
|
||||||
@@ -46,15 +62,15 @@
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _linkedTheme = null;
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
if (isHubLinked()) {
|
if (isHubLinked()) {
|
||||||
return themeFromUrl() || _linkedTheme || "dark";
|
return themeFromUrl() || _linkedTheme || readLinkedThemeStorage() || "dark";
|
||||||
}
|
}
|
||||||
return getStandalone();
|
return getStandalone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let _linkedTheme = null;
|
|
||||||
|
|
||||||
/** 模板内联暗色 → 亮色(切换时重写 style 属性) */
|
/** 模板内联暗色 → 亮色(切换时重写 style 属性) */
|
||||||
const INLINE_HEX_LIGHT = {
|
const INLINE_HEX_LIGHT = {
|
||||||
"#cfd3ef": "#1a2838",
|
"#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) {
|
function apply(theme, opts) {
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
const linked = isHubLinked();
|
const linked = isHubLinked();
|
||||||
const t = normalize(theme);
|
const t = normalize(theme);
|
||||||
if (linked) {
|
if (linked) {
|
||||||
_linkedTheme = t;
|
_linkedTheme = t;
|
||||||
|
writeLinkedThemeStorage(t);
|
||||||
} else if (!options.skipStore) {
|
} else if (!options.skipStore) {
|
||||||
setStandalone(t);
|
setStandalone(t);
|
||||||
}
|
}
|
||||||
@@ -155,7 +202,19 @@
|
|||||||
const meta = document.querySelector('meta[name="theme-color"]');
|
const meta = document.querySelector('meta[name="theme-color"]');
|
||||||
if (meta) meta.setAttribute("content", META[t]);
|
if (meta) meta.setAttribute("content", META[t]);
|
||||||
root.style.colorScheme = 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();
|
syncToggleUI();
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent("instance-theme-change", { detail: { theme: t, hubLinked: linked } })
|
new CustomEvent("instance-theme-change", { detail: { theme: t, hubLinked: linked } })
|
||||||
@@ -200,7 +259,7 @@
|
|||||||
|
|
||||||
function boot() {
|
function boot() {
|
||||||
if (isHubLinked()) {
|
if (isHubLinked()) {
|
||||||
apply(themeFromUrl() || "dark", { skipStore: true });
|
apply(get(), { skipStore: true });
|
||||||
window.addEventListener("message", (ev) => initFromHubMessage(ev.data));
|
window.addEventListener("message", (ev) => initFromHubMessage(ev.data));
|
||||||
try {
|
try {
|
||||||
window.parent.postMessage({ type: "instance-theme-ready" }, "*");
|
window.parent.postMessage({ type: "instance-theme-ready" }, "*");
|
||||||
@@ -208,12 +267,16 @@
|
|||||||
} else {
|
} else {
|
||||||
apply(getStandalone());
|
apply(getStandalone());
|
||||||
}
|
}
|
||||||
|
|
||||||
function observeDynamicLists() {
|
function observeDynamicLists() {
|
||||||
["journal-list", "review-list"].forEach((id) => {
|
["journal-list", "review-list"].forEach((id) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (!el || el.dataset.instThemeObserved === "1") return;
|
if (!el || el.dataset.instThemeObserved === "1") return;
|
||||||
el.dataset.instThemeObserved = "1";
|
el.dataset.instThemeObserved = "1";
|
||||||
new MutationObserver(() => syncInlineStyles(get())).observe(el, {
|
new MutationObserver(() => {
|
||||||
|
syncInlineStyles(get());
|
||||||
|
patchHubNavLinks(get());
|
||||||
|
}).observe(el, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
@@ -223,6 +286,7 @@
|
|||||||
const onReady = () => {
|
const onReady = () => {
|
||||||
initToggleUI();
|
initToggleUI();
|
||||||
syncInlineStyles(get());
|
syncInlineStyles(get());
|
||||||
|
patchHubNavLinks(get());
|
||||||
observeDynamicLists();
|
observeDynamicLists();
|
||||||
};
|
};
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
@@ -232,7 +296,10 @@
|
|||||||
}
|
}
|
||||||
document.addEventListener("instance-theme-change", (ev) => {
|
document.addEventListener("instance-theme-change", (ev) => {
|
||||||
const t = ev.detail && ev.detail.theme;
|
const t = ev.detail && ev.detail.theme;
|
||||||
if (t) syncInlineStyles(t);
|
if (t) {
|
||||||
|
syncInlineStyles(t);
|
||||||
|
patchHubNavLinks(t);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,11 +307,14 @@
|
|||||||
|
|
||||||
global.InstanceTheme = {
|
global.InstanceTheme = {
|
||||||
STANDALONE_KEY,
|
STANDALONE_KEY,
|
||||||
|
HUB_LINKED_THEME_KEY,
|
||||||
isHubLinked,
|
isHubLinked,
|
||||||
get,
|
get,
|
||||||
apply,
|
apply,
|
||||||
initToggleUI,
|
initToggleUI,
|
||||||
syncToggleUI,
|
syncToggleUI,
|
||||||
syncInlineStyles,
|
syncInlineStyles,
|
||||||
|
patchHubNavLinks,
|
||||||
|
mergeHubQueryIntoHref,
|
||||||
};
|
};
|
||||||
})(typeof window !== "undefined" ? window : globalThis);
|
})(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