diff --git a/scripts/patch_admin_timeline_ui.py b/scripts/patch_admin_timeline_ui.py index bf28182..24e9882 100644 --- a/scripts/patch_admin_timeline_ui.py +++ b/scripts/patch_admin_timeline_ui.py @@ -58,20 +58,38 @@ 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 LIFE_RE = /(生命周期|Lifecycle)/i; const STATE = new WeakMap(); - const getRows = section => [...section.querySelectorAll('div.grid')].filter(row => { - if (row.classList.contains('fcbx-life-row-skip')) return false; - const text = row.textContent || ''; - return !!row.querySelector('span.rounded-full') && !LIFE_RE.test(text); - }); + 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} 条)`; @@ -79,9 +97,9 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: } }; 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'); @@ -100,18 +118,33 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: } apply(section); }; - const markRows = () => document.querySelectorAll('section').forEach(ensureSection); + const markRows = root => { + const scope = root?.querySelectorAll ? root : document; + scope.querySelectorAll('section').forEach(ensureSection); + }; 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') @@ -132,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))