No feeds.
'; return; } @@ -534,6 +540,7 @@ function renderFeeds(){ if(kids) $('.chev',el).onclick=ev=>{ ev.stopPropagation(); toggleGroup(f.id); }; list.appendChild(el); } + list.scrollTop=top; } function selectFeed(id){ S.feed=id; S.offset=0; S.sel=null; S.q=''; @@ -664,12 +671,14 @@ async function loadEntries(append){ function renderEntries(){ const box=$('#eps'); if(!box) return; const c=$('#count'); if(c) c.textContent=`${S.total} episode${S.total===1?'':'s'}`; + const pane=$('#list'), top=pane?pane.scrollTop:0; box.innerHTML=''; if(!S.entries.length){ box.innerHTML=`${S.q?'Nothing matches that search.':'Nothing here yet — try Scan now.'}
`; return; } for(const e of S.entries) box.appendChild(epEl(e)); + if(pane) pane.scrollTop=top; if(S.entries.length < S.total){ const b=document.createElement('button'); b.className='btn'; b.id='more'; b.textContent=`Load more (${S.entries.length} of ${S.total})`; @@ -1309,6 +1318,15 @@ if(localStorage.getItem('ipx.theme')) document.documentElement.dataset.theme=loc let sse; function connect(){ sse=new EventSource('/api/events'); + const soon=(fn,ms=500)=>{ let t; return ()=>{ clearTimeout(t); t=setTimeout(fn,ms); }; }; + const refreshFeeds=soon(()=>loadFeeds(true)); + const refreshEntries=soon(()=>{ if(S.feed) loadEntries(); }); + let fresh={}; + const tellNew=soon(()=>{ + const feeds=Object.keys(fresh), n=feeds.reduce((a,k)=>a+fresh[k],0); + if(n) toast(feeds.length===1 ? `${feeds[0]}: ${n} new` : `${n} new in ${feeds.length} feeds`); + fresh={}; + },900); sse.onmessage=m=>{ let ev; try{ ev=JSON.parse(m.data) }catch{ return } if(ev.ev==='progress'){ @@ -1322,16 +1340,19 @@ function connect(){ else if(ev.ev==='download_done'){ const bar=document.querySelector(`.dlbar[data-bar="${ev.enclosure}"]`); if(bar) bar.classList.remove('live'); - toast('Downloaded '+ev.path.split('/').pop()); loadEntries(); loadFeeds(true); + toast('Downloaded '+ev.path.split('/').pop()); refreshEntries(); refreshFeeds(); } else if(ev.ev==='download_error'){ const bar=document.querySelector(`.dlbar[data-bar="${ev.enclosure}"]`); if(bar) bar.classList.remove('live'); - toast('Download failed: '+ev.msg,true); loadEntries(); + toast('Download failed: '+ev.msg,true); refreshEntries(); } - else if(ev.ev==='feed_done'){ if(ev.new) toast(`${ev.feed}: ${ev.new} new`); loadFeeds(true); if(ev.feed===S.feed) loadEntries(); } - else if(ev.ev==='feed_error'){ toast(ev.feed+': '+ev.msg,true); loadFeeds(true); } - else if(ev.ev==='scan_done'){ loadFeeds(true); if(S.feed) loadEntries(); } + else if(ev.ev==='feed_done'){ + if(ev.new){ fresh[ev.feed]=(fresh[ev.feed]||0)+ev.new; tellNew(); } + refreshFeeds(); if(ev.feed===S.feed) refreshEntries(); + } + else if(ev.ev==='feed_error'){ toast(ev.feed+': '+ev.msg,true); refreshFeeds(); } + else if(ev.ev==='scan_done'){ refreshFeeds(); refreshEntries(); } }; sse.onerror=()=>{ sse.close(); setTimeout(connect,4000); }; }