/* 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 = /([\s\S]*)<\/body>/i.exec(rawHtml); if (!bodyMatch) return scripts; var body = bodyMatch[1]; var re = /]*)?>([\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(); })();