Spaces:
Sleeping
Sleeping
| /** | |
| * session_state.js | |
| * 放在 assets/ 目錄,Dash 自動載入。 | |
| */ | |
| (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(state) { | |
| try { sessionStorage.setItem(STORE_KEY, JSON.stringify(state)); } | |
| catch (e) {} | |
| } | |
| function getPathname() { return window.location.pathname || "/"; } | |
| function getPageState() { | |
| var s = loadState(); var p = getPathname(); | |
| if (!s[p]) s[p] = {}; | |
| return { state: s, page: p, ps: s[p] }; | |
| } | |
| function save(key, value) { | |
| var r = getPageState(); | |
| r.ps[key] = value; | |
| r.state[r.page] = r.ps; | |
| saveState(r.state); | |
| } | |
| // ── 從 id 字串取得 key ──────────────────────────────────────────────── | |
| function parseId(idStr) { | |
| try { return JSON.parse(idStr); } | |
| catch (e) { return null; } | |
| } | |
| // ── 快照目前頁面所有狀態 ────────────────────────────────────────────── | |
| function snapshotState() { | |
| var r = getPageState(); | |
| var ps = r.ps; | |
| // 1. 填空題輸入值 | |
| if (!ps.quiz_inputs) ps.quiz_inputs = {}; | |
| document.querySelectorAll("[id]").forEach(function (el) { | |
| var parsed = parseId(el.id); | |
| if (!parsed || parsed.type !== "quiz-input") return; | |
| var inp = el.querySelector("input"); | |
| if (inp) ps.quiz_inputs[parsed.qid] = inp.value || ""; | |
| }); | |
| // 2. 選擇題已選中的按鈕(找 data-active 或 aria-pressed 的按鈕) | |
| if (!ps.quiz_choice) ps.quiz_choice = {}; | |
| document.querySelectorAll("[id]").forEach(function (el) { | |
| var parsed = parseId(el.id); | |
| if (!parsed || parsed.type !== "quiz-feedback-choice") return; | |
| // feedback 有內容代表已作答 | |
| if (el.textContent && el.textContent.trim()) { | |
| ps.quiz_choice[parsed.qid] = { | |
| children: el.textContent.trim(), | |
| color: el.getAttribute("data-color") || "" | |
| }; | |
| } | |
| }); | |
| // 3. 解法展開狀態 | |
| if (!ps.solutions) ps.solutions = {}; | |
| document.querySelectorAll("button[id]").forEach(function (btn) { | |
| var parsed = parseId(btn.id); | |
| if (!parsed || parsed.type !== "solution-toggle") return; | |
| var key = parsed.key; | |
| // 找對應的 collapse 元素,判斷是否展開 | |
| var collapseEl = document.querySelector( | |
| '[id=\'{"key":"' + key + '","type":"solution-collapse"}\'],' + | |
| '[id=\'{"type":"solution-collapse","key":"' + key + '"}\']' | |
| ); | |
| if (collapseEl) { | |
| ps.solutions[key] = collapseEl.offsetHeight > 10; | |
| } | |
| }); | |
| r.state[r.page] = ps; | |
| saveState(r.state); | |
| } | |
| // ── 恢復狀態 ────────────────────────────────────────────────────────── | |
| var _restoring = false; | |
| function restoreState() { | |
| var r = getPageState(); | |
| var ps = r.ps; | |
| _restoring = true; | |
| // 1. 恢復 section | |
| if (ps.section) { | |
| var secBtn = document.querySelector('[data-sid="' + ps.section + '"]'); | |
| if (secBtn) secBtn.click(); | |
| } | |
| // 2. 恢復填空題輸入值 | |
| var inputs = ps.quiz_inputs || {}; | |
| Object.keys(inputs).forEach(function (qid) { | |
| var val = inputs[qid]; | |
| if (!val) return; | |
| var el = document.querySelector('[id=\'{"qid":"' + qid + '","type":"quiz-input"}\'],' + | |
| '[id=\'{"type":"quiz-input","qid":"' + qid + '"}\']'); | |
| if (!el) return; | |
| var inp = el.querySelector("input"); | |
| if (!inp) return; | |
| var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; | |
| setter.call(inp, val); | |
| inp.dispatchEvent(new Event("input", { bubbles: true })); | |
| }); | |
| // 3. 恢復解法展開(點擊 toggle 按鈕) | |
| var solutions = ps.solutions || {}; | |
| Object.keys(solutions).forEach(function (key) { | |
| if (!solutions[key]) return; | |
| var btn = document.querySelector( | |
| '[id=\'{"key":"' + key + '","type":"solution-toggle"}\'],' + | |
| '[id=\'{"type":"solution-toggle","key":"' + key + '"}\']' | |
| ); | |
| if (btn) btn.click(); | |
| }); | |
| setTimeout(function () { _restoring = false; }, 600); | |
| } | |
| // ── 監聽 section 按鈕點擊 ──────────────────────────────────────────── | |
| var _secListened = new Set(); | |
| function attachSectionListeners() { | |
| document.querySelectorAll("[data-sid]").forEach(function (btn) { | |
| var sid = btn.getAttribute("data-sid"); | |
| if (_secListened.has(sid)) return; | |
| _secListened.add(sid); | |
| btn.addEventListener("click", function () { | |
| if (_restoring) return; | |
| save("section", sid); | |
| }); | |
| }); | |
| } | |
| // ── 定時快照(每 2 秒存一次目前狀態)──────────────────────────────── | |
| function startAutoSnapshot() { | |
| setInterval(function () { | |
| if (!_restoring) snapshotState(); | |
| }, 2000); | |
| } | |
| // ── 監聽 URL 變化 ───────────────────────────────────────────────────── | |
| var _lastPathname = null; | |
| var _restoreTimer = null; | |
| function checkPathname() { | |
| var cur = getPathname(); | |
| if (cur !== _lastPathname) { | |
| _lastPathname = cur; | |
| _secListened.clear(); | |
| clearTimeout(_restoreTimer); | |
| _restoreTimer = setTimeout(function () { | |
| attachSectionListeners(); | |
| restoreState(); | |
| }, 1000); | |
| } else { | |
| attachSectionListeners(); | |
| } | |
| } | |
| // ── 初始化 ──────────────────────────────────────────────────────────── | |
| var _mutTimer = null; | |
| var observer = new MutationObserver(function () { | |
| clearTimeout(_mutTimer); | |
| _mutTimer = setTimeout(checkPathname, 150); | |
| }); | |
| function init() { | |
| _lastPathname = getPathname(); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| startAutoSnapshot(); | |
| setTimeout(function () { | |
| attachSectionListeners(); | |
| restoreState(); | |
| }, 1200); | |
| } | |
| if (document.readyState === "loading") { | |
| document.addEventListener("DOMContentLoaded", init); | |
| } else { | |
| init(); | |
| } | |
| })(); | |