Disable turbo client navigation to fix broken page layouts.
Turbo swapping broke page CSS/JS across stats, settings, market, and trade. Restore full page loads; keep external base.css and link prefetch. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,10 +42,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prefetchNav(href) {
|
function prefetchNav(href) {
|
||||||
if (window.qihuoPrefetchPage) {
|
|
||||||
window.qihuoPrefetchPage(href);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!href || href.indexOf(window.location.origin) !== 0) return;
|
if (!href || href.indexOf(window.location.origin) !== 0) return;
|
||||||
if (href === window.location.href) return;
|
if (href === window.location.href) return;
|
||||||
var links = document.head.querySelectorAll('link[rel="prefetch"]');
|
var links = document.head.querySelectorAll('link[rel="prefetch"]');
|
||||||
|
|||||||
@@ -122,5 +122,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window.qihuoPageBoot) window.qihuoPageBoot(bootSettingsPage, '.settings-page');
|
if (window.qihuoPageBoot) window.qihuoPageBoot(bootSettingsPage, '.settings-page');
|
||||||
else document.addEventListener('DOMContentLoaded', bootSettingsPage);
|
else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootSettingsPage);
|
||||||
|
else bootSettingsPage();
|
||||||
})();
|
})();
|
||||||
|
|||||||
+2
-2
@@ -163,6 +163,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window.qihuoPageBoot) window.qihuoPageBoot(bootStatsPage, '#stats-summary');
|
if (window.qihuoPageBoot) window.qihuoPageBoot(bootStatsPage, '#stats-summary');
|
||||||
else if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootStatsPage);
|
else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootStatsPage);
|
||||||
else document.addEventListener('DOMContentLoaded', bootStatsPage);
|
else bootStatsPage();
|
||||||
})();
|
})();
|
||||||
|
|||||||
+8
-4
@@ -1664,9 +1664,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.qihuoPageBoot) window.qihuoPageBoot(bootTradePage, '.trade-page');
|
function startTradePage() {
|
||||||
else if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootTradePage);
|
if (!document.querySelector('.trade-page')) return;
|
||||||
else bootTradePage();
|
bootTradePage();
|
||||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupTradePage);
|
}
|
||||||
|
|
||||||
|
if (window.qihuoPageBoot) window.qihuoPageBoot(startTradePage, '.trade-page');
|
||||||
|
else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', startTradePage);
|
||||||
|
else startTradePage();
|
||||||
window.addEventListener('pagehide', cleanupTradePage);
|
window.addEventListener('pagehide', cleanupTradePage);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,276 +0,0 @@
|
|||||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
|
||||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
|
||||||
* 详见 LICENSE.zh-CN.txt
|
|
||||||
*/
|
|
||||||
(function () {
|
|
||||||
var PERMANENT_CSS = ['/static/css/base.css', '/static/css/tech.css', '/static/css/responsive.css'];
|
|
||||||
var CORE_SCRIPT_RE = /theme\.js|symbol\.js|page\.js|nav\.js|pwa\.js|turbonav\.js/;
|
|
||||||
var pageCache = new Map();
|
|
||||||
var MAX_CACHE = 10;
|
|
||||||
var inflight = null;
|
|
||||||
var currentUrl = normalizeUrl(window.location.href);
|
|
||||||
|
|
||||||
function normalizeUrl(href) {
|
|
||||||
var u = new URL(href, window.location.origin);
|
|
||||||
u.hash = '';
|
|
||||||
return u.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPermanentStylesheet(el) {
|
|
||||||
var href = el.getAttribute('href') || '';
|
|
||||||
return PERMANENT_CSS.some(function (p) { return href.indexOf(p) !== -1; });
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldTurboClick(e, link) {
|
|
||||||
if (!link || !link.href) return false;
|
|
||||||
if (e.defaultPrevented) return false;
|
|
||||||
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return false;
|
|
||||||
if (link.target && link.target !== '_self') return false;
|
|
||||||
if (link.hasAttribute('download')) return false;
|
|
||||||
if (link.origin !== window.location.origin) return false;
|
|
||||||
if (normalizeUrl(link.href) === currentUrl) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseHtml(html) {
|
|
||||||
return new DOMParser().parseFromString(html, 'text/html');
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchPage(url, signal) {
|
|
||||||
var key = normalizeUrl(url);
|
|
||||||
if (pageCache.has(key)) {
|
|
||||||
return Promise.resolve(pageCache.get(key));
|
|
||||||
}
|
|
||||||
return fetch(key, {
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: { Accept: 'text/html' },
|
|
||||||
signal: signal
|
|
||||||
}).then(function (res) {
|
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
||||||
return res.text();
|
|
||||||
}).then(function (html) {
|
|
||||||
var pack = { doc: parseHtml(html), html: html };
|
|
||||||
pageCache.set(key, pack);
|
|
||||||
if (pageCache.size > MAX_CACHE) {
|
|
||||||
var first = pageCache.keys().next().value;
|
|
||||||
pageCache.delete(first);
|
|
||||||
}
|
|
||||||
return pack;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectPageCss(doc) {
|
|
||||||
var out = [];
|
|
||||||
doc.querySelectorAll('head link[rel="stylesheet"], head style').forEach(function (el) {
|
|
||||||
if (el.tagName === 'LINK' && isPermanentStylesheet(el)) return;
|
|
||||||
out.push(el);
|
|
||||||
});
|
|
||||||
doc.querySelectorAll('.main style').forEach(function (el) {
|
|
||||||
out.push(el);
|
|
||||||
});
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareMainHtml(doc) {
|
|
||||||
var fetchedMain = doc.querySelector('.main');
|
|
||||||
if (!fetchedMain) return '';
|
|
||||||
fetchedMain.querySelectorAll('style').forEach(function (el) { el.remove(); });
|
|
||||||
return fetchedMain.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** DOMParser strips inline script bodies — parse from raw HTML instead. */
|
|
||||||
function collectPageScripts(rawHtml) {
|
|
||||||
var scripts = [];
|
|
||||||
var bodyMatch = /<body[\s\S]*?>([\s\S]*)<\/body>/i.exec(rawHtml);
|
|
||||||
if (!bodyMatch) return scripts;
|
|
||||||
var body = bodyMatch[1];
|
|
||||||
var re = /<script(\s[^>]*)?>([\s\S]*?)<\/script>/gi;
|
|
||||||
var pastPwa = false;
|
|
||||||
var m;
|
|
||||||
while ((m = re.exec(body)) !== null) {
|
|
||||||
var attrs = m[1] || '';
|
|
||||||
var text = m[2];
|
|
||||||
var srcMatch = /\ssrc=["']([^"']+)["']/i.exec(attrs);
|
|
||||||
var typeMatch = /\stype=["']([^"']+)["']/i.exec(attrs);
|
|
||||||
var idMatch = /\sid=["']([^"']+)["']/i.exec(attrs);
|
|
||||||
var src = srcMatch ? srcMatch[1] : '';
|
|
||||||
var type = typeMatch ? typeMatch[1].toLowerCase() : 'text/javascript';
|
|
||||||
var id = idMatch ? idMatch[1] : '';
|
|
||||||
if (src) {
|
|
||||||
if (/pwa\.js/.test(src)) pastPwa = true;
|
|
||||||
if (CORE_SCRIPT_RE.test(src)) continue;
|
|
||||||
if (!pastPwa) continue;
|
|
||||||
scripts.push({ src: src, text: '', type: type, id: id });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!pastPwa) continue;
|
|
||||||
scripts.push({ src: '', text: text, type: type, id: id });
|
|
||||||
}
|
|
||||||
return scripts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePageAssets() {
|
|
||||||
document.querySelectorAll('[data-page-css]').forEach(function (el) { el.remove(); });
|
|
||||||
document.querySelectorAll('body script[data-page-js], body script[data-page-data]').forEach(function (el) {
|
|
||||||
el.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPageCss(items) {
|
|
||||||
return items.reduce(function (chain, srcEl) {
|
|
||||||
return chain.then(function () {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
var el = srcEl.cloneNode(true);
|
|
||||||
el.setAttribute('data-page-css', '');
|
|
||||||
if (el.tagName === 'LINK') {
|
|
||||||
el.onload = function () { resolve(); };
|
|
||||||
el.onerror = function () { resolve(); };
|
|
||||||
document.head.appendChild(el);
|
|
||||||
} else {
|
|
||||||
document.head.appendChild(el);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, Promise.resolve());
|
|
||||||
}
|
|
||||||
|
|
||||||
function runPageScripts(items) {
|
|
||||||
return items.reduce(function (chain, item) {
|
|
||||||
return chain.then(function () {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
var s = document.createElement('script');
|
|
||||||
if (item.src) {
|
|
||||||
s.setAttribute('data-page-js', '');
|
|
||||||
var bust = (item.src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now();
|
|
||||||
s.src = item.src + bust;
|
|
||||||
s.async = false;
|
|
||||||
s.onload = function () { resolve(); };
|
|
||||||
s.onerror = function () { resolve(); };
|
|
||||||
document.body.appendChild(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item.type === 'application/json') {
|
|
||||||
s.type = 'application/json';
|
|
||||||
if (item.id) s.id = item.id;
|
|
||||||
s.setAttribute('data-page-data', '');
|
|
||||||
s.textContent = item.text;
|
|
||||||
document.body.appendChild(s);
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item.type && item.type !== 'text/javascript' && item.type !== 'module') {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
s.setAttribute('data-page-js', '');
|
|
||||||
s.textContent = item.text;
|
|
||||||
document.body.appendChild(s);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, Promise.resolve());
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncNavActive(doc) {
|
|
||||||
var nav = document.getElementById('site-nav');
|
|
||||||
var fetchedNav = doc.getElementById('site-nav');
|
|
||||||
if (!nav || !fetchedNav) return;
|
|
||||||
nav.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
|
|
||||||
fetchedNav.querySelectorAll('a[href].active').forEach(function (fa) {
|
|
||||||
var href = fa.getAttribute('href');
|
|
||||||
if (!href) return;
|
|
||||||
var local = nav.querySelector('a[href="' + href + '"]')
|
|
||||||
|| nav.querySelector('a[href="' + new URL(href, window.location.origin).pathname + '"]');
|
|
||||||
if (local) local.classList.add('active');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLoading(on) {
|
|
||||||
var main = document.querySelector('.main');
|
|
||||||
if (main) main.classList.toggle('nav-loading', on);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyDocument(pack, url, fromPopstate) {
|
|
||||||
var doc = pack.doc;
|
|
||||||
var rawHtml = pack.html;
|
|
||||||
var main = document.querySelector('.main');
|
|
||||||
if (!main || !doc.querySelector('.main')) {
|
|
||||||
window.location.href = url;
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.dispatchEvent(new Event('qihuo:page-leave'));
|
|
||||||
removePageAssets();
|
|
||||||
|
|
||||||
var cssItems = collectPageCss(doc);
|
|
||||||
var mainHtml = prepareMainHtml(doc);
|
|
||||||
var scriptItems = collectPageScripts(rawHtml);
|
|
||||||
|
|
||||||
return applyPageCss(cssItems).then(function () {
|
|
||||||
main.innerHTML = mainHtml;
|
|
||||||
document.title = doc.title || document.title;
|
|
||||||
syncNavActive(doc, url);
|
|
||||||
return runPageScripts(scriptItems);
|
|
||||||
}).then(function () {
|
|
||||||
currentUrl = normalizeUrl(url);
|
|
||||||
if (!fromPopstate) {
|
|
||||||
history.pushState({ turbo: true }, '', currentUrl);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo(url, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
var target = normalizeUrl(url);
|
|
||||||
if (target === currentUrl && !opts.force) return Promise.resolve();
|
|
||||||
|
|
||||||
if (inflight) inflight.abort();
|
|
||||||
var ctrl = new AbortController();
|
|
||||||
inflight = ctrl;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
return fetchPage(target, ctrl.signal).then(function (pack) {
|
|
||||||
if (ctrl.signal.aborted) return;
|
|
||||||
inflight = null;
|
|
||||||
return applyDocument(pack, target, !!opts.fromPopstate);
|
|
||||||
}).catch(function () {
|
|
||||||
if (ctrl.signal.aborted) return;
|
|
||||||
inflight = null;
|
|
||||||
setLoading(false);
|
|
||||||
window.location.href = target;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function prefetchPage(href) {
|
|
||||||
var target = normalizeUrl(href);
|
|
||||||
if (target === currentUrl || pageCache.has(target)) return;
|
|
||||||
fetchPage(target).catch(function () { /* ignore */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
window.qihuoNavigate = navigateTo;
|
|
||||||
window.qihuoPrefetchPage = prefetchPage;
|
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
|
||||||
var link = e.target.closest('#site-nav a[href]');
|
|
||||||
if (!link || !shouldTurboClick(e, link)) return;
|
|
||||||
e.preventDefault();
|
|
||||||
var navEl = document.getElementById('site-nav');
|
|
||||||
if (navEl) {
|
|
||||||
navEl.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
|
|
||||||
link.classList.add('active');
|
|
||||||
}
|
|
||||||
navigateTo(link.href);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
window.addEventListener('popstate', function () {
|
|
||||||
var target = normalizeUrl(window.location.href);
|
|
||||||
if (target === currentUrl) return;
|
|
||||||
navigateTo(target, { fromPopstate: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
|
|
||||||
})();
|
|
||||||
+3
-3
@@ -2,7 +2,7 @@
|
|||||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||||
* 详见 LICENSE.zh-CN.txt
|
* 详见 LICENSE.zh-CN.txt
|
||||||
*/
|
*/
|
||||||
var CACHE_VERSION = 'qihuo-v8';
|
var CACHE_VERSION = 'qihuo-v9';
|
||||||
var STATIC_CACHE = CACHE_VERSION + '-static';
|
var STATIC_CACHE = CACHE_VERSION + '-static';
|
||||||
var STATIC_ASSETS = [
|
var STATIC_ASSETS = [
|
||||||
'/static/css/base.css',
|
'/static/css/base.css',
|
||||||
@@ -10,12 +10,12 @@ var STATIC_ASSETS = [
|
|||||||
'/static/css/responsive.css',
|
'/static/css/responsive.css',
|
||||||
'/static/css/trade.css',
|
'/static/css/trade.css',
|
||||||
'/static/js/theme.js',
|
'/static/js/theme.js',
|
||||||
'/static/js/page.js',
|
|
||||||
'/static/js/nav.js',
|
'/static/js/nav.js',
|
||||||
'/static/js/turbonav.js',
|
|
||||||
'/static/js/pwa.js',
|
'/static/js/pwa.js',
|
||||||
'/static/js/symbol.js',
|
'/static/js/symbol.js',
|
||||||
'/static/js/trade.js',
|
'/static/js/trade.js',
|
||||||
|
'/static/js/stats.js',
|
||||||
|
'/static/js/settings.js',
|
||||||
'/static/icons/icon-192.png',
|
'/static/icons/icon-192.png',
|
||||||
'/static/icons/icon-512.png',
|
'/static/icons/icon-512.png',
|
||||||
'/static/icons/icon.svg',
|
'/static/icons/icon.svg',
|
||||||
|
|||||||
@@ -73,10 +73,8 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='js/symbol.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/symbol.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/page.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/nav.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/nav.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/pwa.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/pwa.js') }}"></script>
|
||||||
{% block extra_js %}{% endblock %}
|
{% block extra_js %}{% endblock %}
|
||||||
<script src="{{ url_for('static', filename='js/turbonav.js') }}"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user