diff --git a/scripts/patch_admin_timeline_ui.py b/scripts/patch_admin_timeline_ui.py index eb32154..da18ffe 100644 --- a/scripts/patch_admin_timeline_ui.py +++ b/scripts/patch_admin_timeline_ui.py @@ -62,39 +62,55 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: js = r'''(() => { const EVENT_RE = /(第\s*\d+\s*次取件|取件\s*[##]\s*\d+|Retrieval\s*#\s*\d+)/i; const LIFE_RE = /(生命周期|Lifecycle)/i; - const markRows = () => { - document.querySelectorAll('section').forEach(section => { - const title = section.querySelector('h1,h2,h3,h4,h5'); - if (!title || !LIFE_RE.test(title.textContent || '')) return; - const rows = [...section.querySelectorAll('div.grid')].filter(row => EVENT_RE.test(row.textContent || '')); - if (rows.length <= 3) return; - if (section.dataset.fcbxLifecycleReady !== '1') { - section.dataset.fcbxLifecycleReady = '1'; - section.dataset.fcbxLifecycleCollapsed = '1'; - const btn = document.createElement('button'); - btn.type = 'button'; - btn.className = 'fcbx-life-toggle mt-3 rounded-lg border px-3 py-2 text-sm font-medium transition'; - btn.style.cssText = 'border-color:rgba(148,163,184,.55);display:inline-flex;align-items:center;gap:.35rem;'; - btn.addEventListener('click', event => { - event.preventDefault(); - event.stopPropagation(); - section.dataset.fcbxLifecycleCollapsed = section.dataset.fcbxLifecycleCollapsed === '1' ? '0' : '1'; - apply(section); - }); - (section.lastElementChild || section).appendChild(btn); - } - apply(section); - }); - }; + const STATE = new WeakMap(); + const getRows = section => [...section.querySelectorAll('div.grid')].filter(row => EVENT_RE.test(row.textContent || '')); const apply = section => { - const rows = [...section.querySelectorAll('div.grid')].filter(row => EVENT_RE.test(row.textContent || '')); - const collapsed = section.dataset.fcbxLifecycleCollapsed !== '0'; - rows.forEach((row, idx) => { row.style.display = collapsed && idx >= 3 ? 'none' : ''; }); - const btn = section.querySelector('.fcbx-life-toggle'); - if (btn) btn.textContent = collapsed ? `展开全部取件记录(共 ${rows.length} 次)` : '收起取件记录'; + const rows = getRows(section); + if (rows.length <= 3) return; + const expanded = STATE.get(section) === true; + rows.forEach((row, idx) => { row.hidden = !expanded && idx >= 3; }); + const btn = section.querySelector(':scope > .fcbx-life-toggle') || section.querySelector('.fcbx-life-toggle'); + if (btn) { + btn.textContent = expanded ? '收起取件记录' : `展开全部取件记录(共 ${rows.length} 次)`; + btn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); + } }; - let timer = 0; - const schedule = () => { clearTimeout(timer); timer = setTimeout(markRows, 80); }; + const ensureSection = section => { + const title = section.querySelector('h1,h2,h3,h4,h5'); + if (!title || !LIFE_RE.test(title.textContent || '')) return; + const rows = getRows(section); + if (rows.length <= 3) return; + if (!STATE.has(section)) STATE.set(section, false); + let btn = section.querySelector(':scope > .fcbx-life-toggle'); + if (!btn) { + btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'fcbx-life-toggle mt-3 rounded-lg border px-3 py-2 text-sm font-medium transition'; + btn.style.cssText = 'border-color:rgba(148,163,184,.55);display:inline-flex;align-items:center;gap:.35rem;touch-action:manipulation;user-select:none;'; + section.appendChild(btn); + } + apply(section); + }; + const markRows = () => document.querySelectorAll('section').forEach(ensureSection); + const toggle = target => { + const btn = target.closest?.('.fcbx-life-toggle'); + if (!btn) return false; + const section = btn.closest('section'); + if (!section) return false; + STATE.set(section, STATE.get(section) !== true); + apply(section); + return true; + }; + const onToggle = event => { + if (!toggle(event.target)) return; + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation?.(); + }; + document.addEventListener('pointerdown', onToggle, true); + document.addEventListener('click', onToggle, true); + let raf = 0; + const schedule = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(markRows); }; window.addEventListener('hashchange', schedule); document.addEventListener('DOMContentLoaded', schedule); new MutationObserver(schedule).observe(document.documentElement, { childList: true, subtree: true });