126 lines
11 KiB
JavaScript
126 lines
11 KiB
JavaScript
'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();
|