Test/custom admin ui #6

Merged
orion merged 2 commits from test/custom-admin-ui into master 2026-07-10 09:18:51 +08:00
+60 -35
View File
@@ -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 = '<script src="assets/fcb-detail-collapse.js?v=20260709"></script>'
if 'fcb-detail-collapse.js' not in html:
# Remove old detail-collapse injection so browsers cannot keep loading the stale “取件记录” script.
html = html.replace('<script src="assets/fcb-detail-collapse.js?v=20260709"></script>\n', '')
html = html.replace('<script src="assets/fcb-detail-collapse.js?v=20260709"></script>', '')
html = html.replace('<script src="assets/fcb-lifecycle-collapse.js?v=20260709-2"></script>\n', '')
html = html.replace('<script src="assets/fcb-lifecycle-collapse.js?v=20260709-2"></script>', '')
tag = '<script src="assets/fcb-lifecycle-collapse-v3.js"></script>'
if 'fcb-lifecycle-collapse-v3.js' not in html:
html = html.replace('</body>', tag + '\n</body>') if '</body>' 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))