diff --git a/scripts/patch_admin_timeline_ui.py b/scripts/patch_admin_timeline_ui.py index da18ffe..24e9882 100644 --- a/scripts/patch_admin_timeline_ui.py +++ b/scripts/patch_admin_timeline_ui.py @@ -58,27 +58,48 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: index = theme_dir / 'index.html' if not assets.exists() or not index.exists(): return False - js_path = assets / 'fcb-detail-collapse.js' + js_path = assets / 'fcb-lifecycle-collapse-v3.js' js = r'''(() => { - const EVENT_RE = /(第\s*\d+\s*次取件|取件\s*[##]\s*\d+|Retrieval\s*#\s*\d+)/i; const LIFE_RE = /(生命周期|Lifecycle)/i; const STATE = new WeakMap(); - const getRows = section => [...section.querySelectorAll('div.grid')].filter(row => EVENT_RE.test(row.textContent || '')); + const getSection = section => { + const title = section.querySelector('h1,h2,h3,h4,h5'); + if (!title || !LIFE_RE.test(title.textContent || '')) return null; + const box = title.nextElementSibling; + if (!box) return null; + return { title, box }; + }; + const getRows = section => { + const ctx = getSection(section); + if (!ctx) return []; + return [...ctx.box.children].filter(el => el instanceof HTMLElement); + }; const apply = section => { - const rows = getRows(section); + const ctx = getSection(section); + if (!ctx) return; + const rows = [...ctx.box.children].filter(el => el instanceof HTMLElement); if (rows.length <= 3) return; const expanded = STATE.get(section) === true; - rows.forEach((row, idx) => { row.hidden = !expanded && idx >= 3; }); + section.classList.toggle('fcbx-life-expanded', expanded); + rows.forEach((row, idx) => { + const shouldHide = idx >= 3 && !expanded; + row.classList.toggle('fcbx-life-row-extra', idx >= 3); + if (shouldHide) { + if (row.style.display !== 'none') row.style.setProperty('display', 'none', 'important'); + } else if (row.style.display) { + row.style.removeProperty('display'); + } + }); const btn = section.querySelector(':scope > .fcbx-life-toggle') || section.querySelector('.fcbx-life-toggle'); if (btn) { - btn.textContent = expanded ? '收起取件记录' : `展开全部取件记录(共 ${rows.length} 次)`; + btn.textContent = expanded ? '收起生命周期记录' : `展开全部生命周期记录(共 ${rows.length} 条)`; btn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); } }; const ensureSection = section => { - const title = section.querySelector('h1,h2,h3,h4,h5'); - if (!title || !LIFE_RE.test(title.textContent || '')) return; - const rows = getRows(section); + const ctx = getSection(section); + if (!ctx) return; + const rows = [...ctx.box.children].filter(el => el instanceof HTMLElement); if (rows.length <= 3) return; if (!STATE.has(section)) STATE.set(section, false); let btn = section.querySelector(':scope > .fcbx-life-toggle'); @@ -86,40 +107,44 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: 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;'; + btn.style.cssText = 'border-color:rgba(148,163,184,.55);display:inline-flex;align-items:center;gap:.35rem;touch-action:manipulation;user-select:none;cursor:pointer;'; + btn.onclick = event => { + event.preventDefault(); + event.stopPropagation(); + STATE.set(section, STATE.get(section) !== true); + apply(section); + }; 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 markRows = root => { + const scope = root?.querySelectorAll ? root : document; + scope.querySelectorAll('section').forEach(ensureSection); }; - 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 }); - schedule(); + const schedule = root => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => markRows(root)); }; + window.addEventListener('hashchange', () => schedule(document)); + document.addEventListener('DOMContentLoaded', () => schedule(document)); + new MutationObserver(records => { + for (const rec of records) { + if ([...rec.addedNodes].some(n => n.nodeType === 1 && ((n.matches?.('section')) || n.querySelector?.('section')))) { + schedule(document); + break; + } + } + }).observe(document.body || document.documentElement, { childList: true, subtree: true }); + schedule(document); })();''' rewrite(js_path, js) html = index.read_text(encoding='utf-8') - tag = '' - if 'fcb-detail-collapse.js' not in html: + # Remove old detail-collapse injection so browsers cannot keep loading the stale “取件记录” script. + html = html.replace('\n', '') + html = html.replace('', '') + html = html.replace('\n', '') + html = html.replace('', '') + tag = '' + if 'fcb-lifecycle-collapse-v3.js' not in html: html = html.replace('', tag + '\n') if '' in html else html + '\n' + tag + '\n' index.write_text(html, encoding='utf-8') gz = index.with_suffix(index.suffix + '.gz') @@ -140,7 +165,7 @@ def main() -> None: if patch_clipboard(path): changed.append(str(path.relative_to(ASSETS_ROOT))) if write_detail_collapse_script(theme): - changed.append(str((assets / 'fcb-detail-collapse.js').relative_to(ASSETS_ROOT))) + changed.append(str((assets / 'fcb-lifecycle-collapse-v3.js').relative_to(ASSETS_ROOT))) if not changed: raise SystemExit('admin timeline/clipboard patch markers not found or already patched') print('patched admin assets:', ', '.join(changed))