'use strict'; function openModal(id){ const m=$(id); if(!m) return; m.classList.remove('closing'); m.classList.add('show'); } function closeModal(id){ const m=$(id); if(!m || !m.classList.contains('show')) return; if(id==='settingsModal' && authReady && typeof restoreAppearance==='function') restoreAppearance(); m.classList.add('closing'); const ms=(state.settings.motion==='off')?0:200; setTimeout(()=>m.classList.remove('show','closing'), ms); } function closeAllModals(){ closeDrawer(); document.querySelectorAll('.modal-mask.show').forEach(m=>closeModal(m.id)); } function setupDropdown(rootId, hiddenId, labelId, onChange){ const root=$(rootId), hidden=$(hiddenId), label=$(labelId); if(!root || !hidden || !label) return; const trigger=root.querySelector('.select-trigger'); const menu=root.querySelector('.select-menu'); function close(){ root.classList.remove('open'); } function setValue(value, silent=false){ hidden.value = String(value); const opt=[...menu.querySelectorAll('button')].find(b=>String(b.dataset.value)===String(value)) || menu.querySelector('button'); if(opt) label.textContent = opt.textContent; menu.querySelectorAll('button').forEach(b=>b.classList.toggle('active', String(b.dataset.value)===String(value))); if(!silent) onChange?.(String(value)); } trigger.onclick=(e)=>{ e.stopPropagation(); document.querySelectorAll('.selectish.open').forEach(x=>{ if(x!==root) x.classList.remove('open'); }); root.classList.toggle('open'); }; menu.querySelectorAll('button').forEach(btn=>btn.onclick=(e)=>{ e.stopPropagation(); setValue(btn.dataset.value); close(); }); document.addEventListener('click', close); setValue(hidden.value || menu.querySelector('button')?.dataset.value || '', true); return {setValue}; } let typeDropdownCtl = null; function syncCustomControls(){ typeDropdownCtl = setupDropdown('typeDropdown','typeFilter','typeDropdownLabel',(value)=>{ resetRenderLimit(); filters.type=Number(value); filters.tag='全部'; render(); }); setupDropdown('sortDropdown','sortSelect','sortDropdownLabel',(value)=>{ resetRenderLimit(); filters.sort=value; renderCards(); }); } // Reset every filter (sort is ordering, not a filter, so it's kept). Also resets the related UI controls. function clearFilters(){ filters.status=0; filters.type=0; filters.tag='全部'; filters.q=''; filters.nsfw='all'; filters.hasComment=false; if($('searchInput')) $('searchInput').value=''; typeDropdownCtl?.setValue('0', true); refreshNsfwBtn(); resetRenderLimit(); render(); } function bindEvents(){ syncCustomControls(); bindSegmentedControls(); $('settingsBtn').onclick=()=>{ fillSettingsUI(); if(typeof snapshotAppearance==='function') snapshotAppearance(); if(!authReady && isPublicMode){ document.querySelectorAll('.settings-tab').forEach(t=>t.classList.remove('active')); document.querySelectorAll('.settings-pane').forEach(p=>p.classList.remove('active')); const at=document.querySelector('[data-tab="stab-appearance"]'), ap=$('stab-appearance'); if(at) at.classList.add('active'); if(ap) ap.classList.add('active'); } openModal('settingsModal'); }; document.querySelectorAll('.settings-tab').forEach(tab=>{ tab.onclick=()=>{ document.querySelectorAll('.settings-tab').forEach(t=>t.classList.remove('active')); document.querySelectorAll('.settings-pane').forEach(p=>p.classList.remove('active')); tab.classList.add('active'); const pane=$( tab.dataset.tab); if(pane) pane.classList.add('active'); }; }); $('exportBtn').onclick=()=> openModal('exportModal'); document.querySelectorAll('[data-close]').forEach(b=>b.onclick=()=>closeModal(b.dataset.close)); document.querySelectorAll('.modal-mask').forEach(m=>{ let downOnMask=false; m.addEventListener('pointerdown', e=>{ downOnMask=(e.target===m); }); m.addEventListener('click', e=>{ if(e.target===m && downOnMask){ closeModal(m.id); if(m.id==='syncModal') showSyncMini(); } }); }); $('syncMiniBar').onclick=()=>{ hideSyncMini(); openModal('syncModal'); }; $('openTokenBtn').onclick=()=> window.open(TOKEN_URL,'_blank','noopener'); $('testLoginBtn').onclick=testLogin; if($('tokenInput')){ const emptyClip=e=>{ e.preventDefault(); try{ e.clipboardData&&e.clipboardData.setData('text/plain',''); }catch{} }; $('tokenInput').addEventListener('copy',emptyClip); $('tokenInput').addEventListener('cut',emptyClip); } $('saveSettingsBtn').onclick=saveSettingsFromUI; $('changePasswordBtn').onclick=changeAdminPassword; $('logoutBtn').onclick=logout; $('syncBtn').onclick=()=>openSyncConfirm('recent'); $('fullSyncBtn').onclick=()=>openSyncConfirm('full'); $('syncConfirmStartBtn').onclick=confirmStartSync; $('cancelSyncBtn').onclick=()=>{ closeModal('syncModal'); showSyncMini(); }; // Appearance number inputs apply live (preview). Admin: preview only, persisted via 保存设置, reverted on close-without-save // (these keys are in APPEARANCE_KEYS). Guest: no save button, so persist to guestSettings now. if($('commentLinesInput')) $('commentLinesInput').addEventListener('input',()=>{ state.settings.commentLines=clampNum($('commentLinesInput').value,0,6,0); applyAppearance(false); if(!authReady) saveSettings(); }); if($('borderWidthInput')) $('borderWidthInput').addEventListener('input',()=>{ state.settings.borderWidth=clampNum($('borderWidthInput').value,0,4,1); applyAppearance(false); if(!authReady) saveSettings(); }); if($('pageSizeInput')) $('pageSizeInput').addEventListener('change',()=>{ state.settings.pageSize=clampNum($('pageSizeInput').value,40,360,120); resetRenderLimit(); renderCards(); if(!authReady) saveSettings(); }); $('searchInput').oninput=e=>{ resetRenderLimit(); filters.q=e.target.value; renderCards(); }; if($('tagFilterInput')) $('tagFilterInput').oninput=()=>renderTags(); // Mobile: off-canvas sidebar drawer const openSide=(v)=>{ document.querySelector('.side')?.classList.toggle('open',v); $('sideBackdrop')?.classList.toggle('open',v); $('menuBtn')?.classList.toggle('open',v); }; if($('menuBtn')) $('menuBtn').onclick=()=>openSide(true); if($('sideBackdrop')) $('sideBackdrop').onclick=()=>openSide(false); window.addEventListener('keydown',e=>{ if(e.key==='Escape') openSide(false); }); // Filter-summary pill doubles as a one-click "clear all filters" button (shows ✕ when active — see CSS) if($('filterSummary')) $('filterSummary').onclick=clearFilters; // "有吐槽" stat: toggle a filter that shows only items with a comment const toggleHasComment=()=>{ filters.hasComment=!filters.hasComment; resetRenderLimit(); render(); openSide(false); }; if($('statCommentsCard')){ $('statCommentsCard').onclick=toggleHasComment; $('statCommentsCard').onkeydown=e=>{ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); toggleHasComment(); } }; } $('statusNav')?.addEventListener('click',e=>{ if(e.target.closest('button')) openSide(false); }); $('tagList')?.addEventListener('click',e=>{ if(e.target.closest('button')) openSide(false); }); // Drawer "操作" buttons delegate to the real (hidden-on-mobile) toolbar buttons [['mSettings','settingsBtn'],['mSyncRecent','syncBtn'],['mFullSync','fullSyncBtn'],['mExport','exportBtn'],['mImport','importBtn'],['mCacheCovers','cacheCoversBtn'],['mEnrichTags','enrichTagsBtn']].forEach(([m,real])=>{ const b=$(m); if(b) b.onclick=()=>{ $(real)?.click(); openSide(false); }; }); if($('tagListMore')) $('tagListMore').onclick=()=>{ const tl=$('tagList'); const exp=tl.classList.toggle('tags-expanded'); $('tagListMore').textContent=exp?'收起':'展开更多'; }; if($('tagSearchToggle')) $('tagSearchToggle').onclick=()=>{ const f=$('tagFilter'); const inp=$('tagFilterInput'); const willOpen=!f.classList.contains('open'); $('tagSearchToggle').classList.toggle('active',willOpen); if(willOpen){ f.classList.add('open'); const ms=document.documentElement.dataset.motion==='off'?0:300; setTimeout(()=>{ if(f.classList.contains('open')){ f.style.overflow='visible'; inp?.focus(); } }, ms); } else { f.style.overflow=''; f.classList.remove('open'); if(inp){ inp.value=''; renderTags(); } } }; $('typeFilter').onchange=e=>{ resetRenderLimit(); filters.type=Number(e.target.value); filters.tag='全部'; render(); }; $('sortSelect').onchange=e=>{ resetRenderLimit(); filters.sort=e.target.value; renderCards(); }; document.querySelectorAll('.theme-toggle').forEach(b=>b.onclick=(e)=>toggleTheme(e)); $('cacheCoversBtn').onclick=cacheCoversCurrent; if($('enrichTagsBtn')) $('enrichTagsBtn').onclick=enrichPublicTagsCurrent; $('nsfwBtn').onclick=()=>{ const next={all:'hide',hide:'only',only:'all'}; filters.nsfw=next[filters.nsfw]||'all'; resetRenderLimit(); refreshNsfwBtn(); render(); }; bindCronEditor(); const plsb=$('publicLoginSubmitBtn'); if(plsb) plsb.onclick=async()=>{ const pw=$('publicLoginPasswordInput')?.value||''; const st=$('publicLoginStatus'); if(st) st.textContent='验证中…'; try{ await apiJSON('/api/login',{method:'POST',body:JSON.stringify({password:pw})}); location.reload(); }catch(err){ if(st) st.textContent='密码不正确,请重试'; } }; $('exportJsonBtn').onclick=exportJson; $('exportCsvBtn').onclick=exportCsv; $('exportHtmlBtn').onclick=exportHtml; $('exportDocxBtn').onclick=exportDocx; $('exportXlsxBtn').onclick=exportXlsx; $('exportZipBtn').onclick=exportZip; $('importBtn').onclick=()=> $('importFile').click(); $('importFile').onchange=e=>{ const f=e.target.files[0]; if(f) importBackup(f).catch(err=>toast('导入失败:'+err.message)); e.target.value=''; }; window.addEventListener('keydown',e=>{ if(e.key==='Escape') closeAllModals(); }); } function hideLoadingScreen(){ const el=document.getElementById('loadingScreen'); if(el) el.classList.add('done'); } async function init(){ try{ // Offline single-file export: no server — render the embedded state directly. if(window.BANGUMI_VAULT_OFFLINE_STATE){ await loadState(); bindEvents(); refreshSegmentedControls(); render(); applyAuthVisibility(); return; } if(!(await ensureSession())){ hideLoadingScreen(); return; } await loadState(); bindEvents(); refreshSegmentedControls(); window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener?.('change',()=>{ if((state.settings.themeMode||'dark')==='system') applyAppearance(true); }); render(); applyAuthVisibility(); // Scheduled sync now runs in the container (server cron). If a sync is already // running server-side when the page opens, attach the progress UI to it. resumeSyncIfRunning(); } finally { hideLoadingScreen(); } } init();