From ed98cbe8268bbe6e04bf58161d4fe0480d176cae Mon Sep 17 00:00:00 2001 From: Orion Date: Thu, 9 Jul 2026 17:41:48 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20=E4=BC=98=E5=8C=96=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E8=AE=B0=E5=BD=95=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E6=94=B6=E8=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_admin_timeline_ui.py | 34 ++++++++++++------------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/scripts/patch_admin_timeline_ui.py b/scripts/patch_admin_timeline_ui.py index da18ffe..bf28182 100644 --- a/scripts/patch_admin_timeline_ui.py +++ b/scripts/patch_admin_timeline_ui.py @@ -60,10 +60,13 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: return False js_path = assets / 'fcb-detail-collapse.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 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 apply = section => { const rows = getRows(section); if (rows.length <= 3) return; @@ -71,7 +74,7 @@ def write_detail_collapse_script(theme_dir: Path) -> bool: 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.textContent = expanded ? '收起生命周期记录' : `展开全部生命周期记录(共 ${rows.length} 条)`; btn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); } }; @@ -86,29 +89,18 @@ 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 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); From 49d2dc9c2c9a61ea07257de71d1ac60094036197 Mon Sep 17 00:00:00 2001 From: Orion Date: Fri, 10 Jul 2026 09:15:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20=E4=BC=98=E5=8C=96=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E6=8A=98=E5=8F=A0=E4=B8=8E=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_admin_timeline_ui.py | 73 ++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 20 deletions(-) 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))