Spaces:
Sleeping
Sleeping
| /** | |
| * session_state.js | |
| * 事件驅動版,不做定時掃描,只在互動時儲存。 | |
| */ | |
| (function () { | |
| "use strict"; | |
| var STORE_KEY = "dash_math_page_state"; | |
| function loadState() { | |
| try { return JSON.parse(sessionStorage.getItem(STORE_KEY) || "{}"); } | |
| catch (e) { return {}; } | |
| } | |
| function saveState(s) { | |
| try { sessionStorage.setItem(STORE_KEY, JSON.stringify(s)); } | |
| catch (e) {} | |
| } | |
| function getPathname() { return window.location.pathname || "/"; } | |
| function saveProp(key, value) { | |
| var s = loadState(), p = getPathname(); | |
| if (!s[p]) s[p] = {}; | |
| s[p][key] = value; | |
| saveState(s); | |
| } | |
| function saveNested(group, key, value) { | |
| var s = loadState(), p = getPathname(); | |
| if (!s[p]) s[p] = {}; | |
| if (!s[p][group]) s[p][group] = {}; | |
| s[p][group][key] = value; | |
| saveState(s); | |
| } | |
| function getPs() { | |
| var s = loadState(), p = getPathname(); | |
| return s[p] || {}; | |
| } | |
| // ── 恢復 ────────────────────────────────────────────────────────────── | |
| var _restoring = false; | |
| function restoreState() { | |
| var ps = getPs(); | |
| _restoring = true; | |
| // Section | |
| if (ps.section) { | |
| var b = document.querySelector('[data-sid="' + ps.section + '"]'); | |
| if (b) b.click(); | |
| } | |
| // 填空題 | |
| var inputs = ps.quiz_inputs || {}; | |
| Object.keys(inputs).forEach(function (qid) { | |
| var val = inputs[qid]; | |
| if (!val) return; | |
| // 用 querySelectorAll 找包含此 qid 的 input | |
| document.querySelectorAll("input").forEach(function (el) { | |
| var w = el.closest("[id]"); | |
| if (!w || w.id.indexOf(qid) === -1 || w.id.indexOf("quiz-input") === -1) return; | |
| var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; | |
| setter.call(el, val); | |
| el.dispatchEvent(new Event("input", { bubbles: true })); | |
| }); | |
| }); | |
| // 解法展開 | |
| var solutions = ps.solutions || {}; | |
| Object.keys(solutions).forEach(function (key) { | |
| if (!solutions[key]) return; | |
| document.querySelectorAll("button").forEach(function (btn) { | |
| if (btn.id && btn.id.indexOf(key) > -1 && btn.id.indexOf("solution-toggle") > -1) { | |
| btn.click(); | |
| } | |
| }); | |
| }); | |
| setTimeout(function () { _restoring = false; }, 800); | |
| } | |
| // ── 掛載監聽 ───────────────────────────────────────────────────────── | |
| var _attached = new Set(); | |
| function attachListeners() { | |
| // Section 按鈕 | |
| document.querySelectorAll("[data-sid]").forEach(function (btn) { | |
| var sid = btn.getAttribute("data-sid"); | |
| if (_attached.has("s:" + sid)) return; | |
| _attached.add("s:" + sid); | |
| btn.addEventListener("click", function () { | |
| if (!_restoring) saveProp("section", sid); | |
| }); | |
| }); | |
| // 填空題 input | |
| document.querySelectorAll("input").forEach(function (el) { | |
| if (_attached.has(el)) return; | |
| var w = el.closest("[id]"); | |
| if (!w || !w.id || w.id.indexOf("quiz-input") === -1) return; | |
| var m = w.id.match(/"qid":"([^"]+)"/); | |
| if (!m) return; | |
| var qid = m[1]; | |
| _attached.add(el); | |
| el.addEventListener("change", function () { | |
| if (!_restoring) saveNested("quiz_inputs", qid, el.value); | |
| }); | |
| }); | |
| // 解法 toggle 按鈕 | |
| document.querySelectorAll("button").forEach(function (btn) { | |
| if (_attached.has(btn)) return; | |
| if (!btn.id || btn.id.indexOf("solution-toggle") === -1) return; | |
| var m = btn.id.match(/"key":"([^"]+)"/); | |
| if (!m) return; | |
| var key = m[1]; | |
| _attached.add(btn); | |
| btn.addEventListener("click", function () { | |
| if (_restoring) return; | |
| // 延遲判斷是否展開 | |
| setTimeout(function () { | |
| var col = document.querySelector('[id*="' + key + '"][id*="solution-collapse"]'); | |
| var opened = col ? col.offsetHeight > 10 : false; | |
| saveNested("solutions", key, opened); | |
| }, 400); | |
| }); | |
| }); | |
| } | |
| // ── 換頁偵測 ───────────────────────────────────────────────────────── | |
| var _lastPath = null, _timer = null; | |
| function checkPath() { | |
| var cur = getPathname(); | |
| if (cur !== _lastPath) { | |
| _lastPath = cur; | |
| _attached.clear(); | |
| clearTimeout(_timer); | |
| _timer = setTimeout(function () { | |
| attachListeners(); | |
| restoreState(); | |
| }, 900); | |
| } else { | |
| attachListeners(); | |
| } | |
| } | |
| // MutationObserver:只做 debounce checkPath,不掃描整個 DOM | |
| var _mutTimer = null; | |
| var obs = new MutationObserver(function () { | |
| clearTimeout(_mutTimer); | |
| _mutTimer = setTimeout(checkPath, 200); | |
| }); | |
| function init() { | |
| _lastPath = getPathname(); | |
| obs.observe(document.body, { childList: true, subtree: false }); // subtree:false 減少觸發 | |
| setTimeout(function () { | |
| attachListeners(); | |
| restoreState(); | |
| }, 1000); | |
| } | |
| if (document.readyState === "loading") { | |
| document.addEventListener("DOMContentLoaded", init); | |
| } else { | |
| init(); | |
| } | |
| })(); | |