/** * 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(); } })();