From e3559531d95cb7f28cb49502119e93e599295e72 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 23 Jun 2026 23:59:27 +0800 Subject: [PATCH] Revert instance soft nav cache to fix navigation flicker Co-authored-by: Cursor --- crypto_monitor_binance/templates/index.html | 2 +- crypto_monitor_gate/templates/index.html | 2 +- crypto_monitor_gate_bot/templates/index.html | 2 +- crypto_monitor_okx/templates/index.html | 2 +- static/instance_theme.js | 366 ++----------------- static/instance_theme_early.css | 7 - 6 files changed, 41 insertions(+), 340 deletions(-) diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index a5c4773..35d2164 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 7a1ed85..f115bca 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 7a1ed85..f115bca 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index a657736..e815816 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/static/instance_theme.js b/static/instance_theme.js index 8c64bb6..1992aac 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -281,7 +281,6 @@ function apply() { if (!mq.matches) return; - if (consumeSoftNavPending()) return; document.querySelectorAll(".top-nav").forEach(scrollActiveTab); } @@ -330,331 +329,40 @@ } } - function remapInlineStylesInHtml(html) { - if (!html) return html; - return html.replace(/\sstyle=(["'])([\s\S]*?)\1/gi, (full, q, style) => { - return ` style=${q}${remapInlineStyle(style, "light")}${q}`; - }); - } + /** 仅中控 iframe 内:fetch + document.write 换页,避免 iframe 整页卸载白屏。单独打开实例仍走浏览器正常跳转。 */ + function initHubEmbedInFrameNav() { + if (!isHubLinked()) return; - function prepareSoftNavHtml(html, theme) { - const t = normalize(theme || get()); - const bg = META[t]; - let out = html || ""; - if (t === "light") { - out = remapInlineStylesInHtml(out); - } - const guard = ``; - if (out.includes("")) { - out = out.replace("", `${guard}`); - } else { - out = guard + out; - } - out = out.replace(/]*)>/i, (m, attrs) => { - if (/data-theme=/i.test(attrs)) { - return m.replace(/data-theme="[^"]*"/i, `data-theme="${t}"`); - } - return ``; - }); - const overlay = ``; - if (/]*>/i.test(out)) { - out = out.replace(/]*)>/i, `${overlay}`); - } - return out; - } - - function ensureNavOverlay(theme) { - const t = normalize(theme || get()); - const bg = META[t]; - let el = document.getElementById("inst-nav-overlay"); - if (!el) { - el = document.createElement("div"); - el.id = "inst-nav-overlay"; - el.setAttribute("aria-hidden", "true"); - (document.body || document.documentElement).appendChild(el); - } - el.style.cssText = `position:fixed;inset:0;z-index:2147483647;background:${bg};pointer-events:none;opacity:1;`; - return el; - } - - function dismissNavOverlay() { - const el = document.getElementById("inst-nav-overlay"); - if (!el) return; - requestAnimationFrame(() => { - requestAnimationFrame(() => { - el.style.transition = "opacity 90ms ease"; - el.style.opacity = "0"; - window.setTimeout(() => { - try { - el.remove(); - } catch (_) {} - }, 120); - }); - }); - } - - function markSoftNavPending() { - try { - sessionStorage.setItem("inst-soft-nav", "1"); - } catch (_) {} - } - - function consumeSoftNavPending() { - try { - if (sessionStorage.getItem("inst-soft-nav") === "1") { - sessionStorage.removeItem("inst-soft-nav"); - return true; - } - } catch (_) {} - return false; - } - - const PAGE_CACHE_PREFIX = "inst-pc:v1:"; - const PAGE_CACHE_DAYS_KEY = "inst-page-cache-days"; - const PAGE_CACHE_INDEX_KEY = "inst-page-cache-index"; - const DEFAULT_PAGE_CACHE_DAYS = 7; - const MAX_PAGE_CACHE_ENTRIES = 16; - const CACHE_REVALIDATE_KEY = "inst-cache-revalidate"; - - function getPageCacheMaxAgeMs() { - try { - const days = parseInt(localStorage.getItem(PAGE_CACHE_DAYS_KEY) || "", 10); - if (Number.isFinite(days) && days > 0 && days <= 30) { - return days * 86400000; - } - } catch (_) {} - return DEFAULT_PAGE_CACHE_DAYS * 86400000; - } - - function setPageCacheDays(days) { - const d = parseInt(days, 10); - if (!Number.isFinite(d) || d <= 0 || d > 30) return DEFAULT_PAGE_CACHE_DAYS; - try { - localStorage.setItem(PAGE_CACHE_DAYS_KEY, String(d)); - } catch (_) {} - return d; - } - - function pageCacheStorageKey(cacheKey) { - return PAGE_CACHE_PREFIX + cacheKey; - } - - function pageCacheKey(href) { - try { - const u = new URL(href, location.href); - return `${u.pathname}${u.search}|t=${get()}`; - } catch (_) { - return `${href}|t=${get()}`; - } - } - - function readPageCacheIndex() { - try { - const raw = localStorage.getItem(PAGE_CACHE_INDEX_KEY); - const data = raw ? JSON.parse(raw) : null; - return Array.isArray(data) ? data : []; - } catch (_) { - return []; - } - } - - function writePageCacheIndex(rows) { - try { - localStorage.setItem(PAGE_CACHE_INDEX_KEY, JSON.stringify(rows.slice(0, MAX_PAGE_CACHE_ENTRIES))); - } catch (_) {} - } - - function prunePageCache(aggressive) { - const maxAge = getPageCacheMaxAgeMs(); - const now = Date.now(); - let index = readPageCacheIndex().filter((row) => { - if (!row || !row.key || !row.savedAt) return false; - if (now - row.savedAt > maxAge) { - try { - localStorage.removeItem(pageCacheStorageKey(row.key)); - } catch (_) {} - return false; - } - return true; - }); - index.sort((a, b) => (b.savedAt || 0) - (a.savedAt || 0)); - if (aggressive || index.length > MAX_PAGE_CACHE_ENTRIES) { - index.slice(MAX_PAGE_CACHE_ENTRIES).forEach((row) => { - try { - localStorage.removeItem(pageCacheStorageKey(row.key)); - } catch (_) {} - }); - index = index.slice(0, MAX_PAGE_CACHE_ENTRIES); - } - writePageCacheIndex(index); - } - - function readPageCache(cacheKey) { - prunePageCache(false); - try { - const raw = localStorage.getItem(pageCacheStorageKey(cacheKey)); - if (!raw) return null; - const entry = JSON.parse(raw); - if (!entry || typeof entry.html !== "string" || !entry.savedAt) return null; - if (Date.now() - entry.savedAt > getPageCacheMaxAgeMs()) { - localStorage.removeItem(pageCacheStorageKey(cacheKey)); - return null; - } - return entry; - } catch (_) { - return null; - } - } - - function savePageCache(cacheKey, html) { - if (!html) return; - const savedAt = Date.now(); - const payload = JSON.stringify({ html, savedAt, key: cacheKey }); - try { - localStorage.setItem(pageCacheStorageKey(cacheKey), payload); - } catch (_) { - prunePageCache(true); - try { - localStorage.setItem(pageCacheStorageKey(cacheKey), payload); - } catch (e2) { - return; - } - } - let index = readPageCacheIndex().filter((row) => row && row.key !== cacheKey); - index.unshift({ key: cacheKey, savedAt }); - writePageCacheIndex(index); - } - - function clearPageCache() { - readPageCacheIndex().forEach((row) => { - if (!row || !row.key) return; - try { - localStorage.removeItem(pageCacheStorageKey(row.key)); - } catch (_) {} - }); - try { - localStorage.removeItem(PAGE_CACHE_INDEX_KEY); - } catch (_) {} - } - - function markCacheRevalidatePending() { - try { - sessionStorage.setItem(CACHE_REVALIDATE_KEY, "1"); - } catch (_) {} - } - - function consumeCacheRevalidatePending() { - try { - if (sessionStorage.getItem(CACHE_REVALIDATE_KEY) === "1") { - sessionStorage.removeItem(CACHE_REVALIDATE_KEY); - return true; - } - } catch (_) {} - return false; - } - - function refreshInstanceLiveData() { - const fns = [ - "refreshAccountSnapshot", - "refreshPriceSnapshotConditional", - "refreshPriceSnapshot", - "refreshOrderDefaults", - "loadJournals", - "loadReviews", - ]; - fns.forEach((name) => { - const fn = global[name]; - if (typeof fn === "function") { - try { - fn(); - } catch (_) {} - } - }); - try { - document.dispatchEvent(new CustomEvent("instance-page-revalidate")); - } catch (_) {} - } - - function scheduleLiveDataRefresh() { - window.setTimeout(refreshInstanceLiveData, 0); - window.setTimeout(refreshInstanceLiveData, 350); - } - - function updateSoftNavHistory(href, opts) { - let path = href; - try { - const u = new URL(href, location.href); - path = u.pathname + u.search + u.hash; - } catch (_) {} - if (opts && opts.replace) history.replaceState(null, "", path); - else history.pushState(null, "", path); - } - - function paintSoftNavDocument(html, href, opts) { - updateSoftNavHistory(href, opts); - document.open(); - document.write(html); - document.close(); - } - - function seedCurrentPageCache() { - if (global.__instPageCacheSeeded) return; - global.__instPageCacheSeeded = true; - const href = location.pathname + location.search; - const cacheKey = pageCacheKey(href); - if (readPageCache(cacheKey)) return; - window.setTimeout(() => { - fetch(href, { credentials: "same-origin", cache: "no-store" }) - .then((r) => (r.ok ? r.text() : null)) - .then((html) => { - if (html) savePageCache(cacheKey, html); - }) - .catch(() => {}); - }, 1500); - } - - function initInstanceTopNavSoft() { - prunePageCache(false); let navToken = 0; function isSoftNavLink(a) { if (!a || !a.getAttribute) return false; - if (a.hasAttribute("download") || a.target === "_blank") return false; return !!a.closest(".top-nav, .strategy-subnav"); } async function navigateInFrame(href, opts) { const token = ++navToken; - const themeNow = get(); - const cacheKey = pageCacheKey(href); - ensureNavOverlay(themeNow); - markSoftNavPending(); - - const cached = readPageCache(cacheKey); - let paintedFromCache = false; - if (cached && cached.html) { - markCacheRevalidatePending(); - paintSoftNavDocument(prepareSoftNavHtml(cached.html, themeNow), href, opts); - paintedFromCache = true; - } - try { - const r = await fetch(href, { credentials: "same-origin", cache: "no-store" }); + const r = await fetch(href, { credentials: "same-origin" }); if (token !== navToken) return; if (!r.ok) { - if (!paintedFromCache) location.href = href; + location.href = href; return; } - const rawHtml = await r.text(); - savePageCache(cacheKey, rawHtml); + const html = await r.text(); if (token !== navToken) return; - if (!paintedFromCache) { - paintSoftNavDocument(prepareSoftNavHtml(rawHtml, themeNow), href, opts); - } else { - scheduleLiveDataRefresh(); - } + let path = href; + try { + const u = new URL(href, location.href); + path = u.pathname + u.search + u.hash; + } catch (_) {} + if (opts && opts.replace) history.replaceState(null, "", path); + else history.pushState(null, "", path); + document.open(); + document.write(html); + document.close(); } catch (_) { - if (!paintedFromCache && token === navToken) location.href = href; + if (token === navToken) location.href = href; } } @@ -686,18 +394,36 @@ }); } + function purgeLegacySoftNavCache() { + try { + for (let i = localStorage.length - 1; i >= 0; i -= 1) { + const key = localStorage.key(i); + if (!key) continue; + if ( + key.startsWith("inst-pc:") || + key === "inst-page-cache-index" || + key === "inst-page-cache-days" + ) { + localStorage.removeItem(key); + } + } + sessionStorage.removeItem("inst-soft-nav"); + sessionStorage.removeItem("inst-cache-revalidate"); + } catch (_) {} + } + function boot() { + purgeLegacySoftNavCache(); if (isHubLinked()) { apply(get(), { skipStore: true }); window.addEventListener("message", (ev) => initFromHubMessage(ev.data)); + initHubEmbedInFrameNav(); try { window.parent.postMessage({ type: "instance-theme-ready" }, "*"); } catch (_) {} } else { apply(getStandalone()); } - initInstanceTopNavSoft(); - seedCurrentPageCache(); function observeDynamicLists() { ["journal-list", "review-list"].forEach((id) => { @@ -721,20 +447,6 @@ syncInlineStyles(get()); patchHubNavLinks(get()); observeDynamicLists(); - if (consumeCacheRevalidatePending()) { - scheduleLiveDataRefresh(); - } - if (document.getElementById("inst-nav-overlay")) { - dismissNavOverlay(); - window.setTimeout(() => { - const el = document.getElementById("inst-nav-overlay"); - if (el) { - try { - el.remove(); - } catch (_) {} - } - }, 800); - } }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", onReady); @@ -765,9 +477,5 @@ mergeHubQueryIntoHref, syncReviewEditButtons, initReviewEditModeSync, - setPageCacheDays, - clearPageCache, - getPageCacheMaxAgeMs, - refreshInstanceLiveData, }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/static/instance_theme_early.css b/static/instance_theme_early.css index 1713bea..f67270b 100644 --- a/static/instance_theme_early.css +++ b/static/instance_theme_early.css @@ -41,10 +41,3 @@ html[data-theme="light"] .stat-item { background: #fff !important; border-color: #b8c8d8 !important; } - -#inst-nav-overlay { - position: fixed; - inset: 0; - z-index: 2147483647; - pointer-events: none; -}