A spinner on the feed being checked, not toasts; check only your own feeds
The scan's events reach everyone, so every browser showed "<feed>: N new" and "Scanning…" toasts, and refreshed, for everyone's feeds. Now a feed's row, and its folder's, carries a spinner between feed_start and its done, skip or error; the list refreshes only for the reader's own feeds; the scan toasts are gone, and "Downloaded" is said only for a file on screen. "Check every feed" from the web UI sent a scan of every feed on the server. Command::Fetch takes an optional `feeds` list -- those feeds and the feeds inside any OPML among them -- and the web fills it with the asker's subscriptions. The schedule and the CLI send none, meaning every feed. Closes #37. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -371,7 +371,8 @@ function opmlModal(){
|
||||
};
|
||||
}
|
||||
|
||||
async function scanAll(){ toast('Scanning all feeds…'); await api('/api/fetch',{method:'POST',body:JSON.stringify({force:true})}); }
|
||||
// No toast: the spinners on the rows being checked say it (issue #37).
|
||||
async function scanAll(){ await api('/api/fetch',{method:'POST',body:JSON.stringify({force:true})}); }
|
||||
$('#scanAll').onclick=scanAll;
|
||||
$('#prefs').onclick=prefsModal;
|
||||
// Someone the proxy signed in is signed out by the proxy: ipx's own sign-out cannot stick while
|
||||
|
||||
@@ -5,12 +5,8 @@ function connect(){
|
||||
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);
|
||||
// Every scan's events reach everyone; only this person's feeds are theirs to show or refresh.
|
||||
const mine=id=>S.feeds.some(f=>f.id===id);
|
||||
sse.onmessage=m=>{
|
||||
let ev; try{ ev=JSON.parse(m.data) }catch{ return }
|
||||
if(ev.ev==='progress'){
|
||||
@@ -23,22 +19,28 @@ 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()); refreshEntries(); refreshFeeds();
|
||||
// Said only for a file on screen, as one downloaded by hand is: the scheduled downloads of
|
||||
// everyone's feeds used to announce themselves to everyone.
|
||||
if(bar){ bar.classList.remove('live'); 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); refreshEntries();
|
||||
}
|
||||
// A spinner on the feed's row while it is checked, in place of a toast per feed (issue #37).
|
||||
else if(ev.ev==='feed_start') setScanning(ev.feed,true);
|
||||
else if(ev.ev==='feed_skip') setScanning(ev.feed,false);
|
||||
else if(ev.ev==='feed_done'){
|
||||
if(ev.new){ fresh[ev.feed]=(fresh[ev.feed]||0)+ev.new; tellNew(); }
|
||||
setScanning(ev.feed,false);
|
||||
if(!mine(ev.feed)) return;
|
||||
refreshFeeds(); if(ev.feed===S.feed||S.feed===':all') refreshEntries();
|
||||
}
|
||||
// No toast: a scan of every feed raised one per failure, to everyone. The feed list's
|
||||
// red ! marks the feed instead, and its page says why.
|
||||
else if(ev.ev==='feed_error') refreshFeeds();
|
||||
else if(ev.ev==='scan_done'){ refreshFeeds(); refreshEntries(); }
|
||||
else if(ev.ev==='feed_error'){ setScanning(ev.feed,false); if(mine(ev.feed)) refreshFeeds(); }
|
||||
else if(ev.ev==='scan_done'){ scanning.clear(); paintScanning(); refreshFeeds(); refreshEntries(); }
|
||||
};
|
||||
sse.onerror=()=>{ sse.close(); setTimeout(connect,4000); };
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ function renderGroup(f,kids){
|
||||
}
|
||||
|
||||
async function feedAction(a,f){
|
||||
if(a==='scan'){ toast('Scanning '+(f.title||f.id)+'…'); await api('/api/fetch',{method:'POST',body:JSON.stringify({feed:f.id,force:true})}); }
|
||||
if(a==='scan'){ await api('/api/fetch',{method:'POST',body:JSON.stringify({feed:f.id,force:true})}); }
|
||||
if(a==='read'){ const r=await api(`/api/feeds/${encodeURIComponent(f.id)}/read-all`,{method:'POST'}); toast(`Marked ${r.marked} read`); await loadFeeds(true); renderFeed(); loadEntries(); }
|
||||
if(a==='rm') removeFeed(f);
|
||||
if(a==='pin'){
|
||||
|
||||
@@ -25,6 +25,22 @@ const VIEWS={
|
||||
blurb:'Episodes you started and have not finished, across every feed you subscribe to. Pick one up where you left off.'},
|
||||
':all':{title:'All Subscriptions',icon:ICON.all},
|
||||
};
|
||||
/// Feeds being checked right now, from the event stream: their rows, and the row of a folder
|
||||
/// holding one, carry a spinner.
|
||||
const scanning=new Set<string>();
|
||||
function setScanning(id: string, on: boolean){
|
||||
if(on) scanning.add(id); else scanning.delete(id);
|
||||
paintScanning();
|
||||
}
|
||||
function paintScanning(){
|
||||
for(const row of $$('#feedlist .feed')){
|
||||
const id=row.dataset.id;
|
||||
const on=scanning.has(id)||S.feeds.some(c=>c.group===id&&scanning.has(c.id));
|
||||
row.classList.toggle('scanning',on);
|
||||
if(on) row.title='Checking for new items…'; else row.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
|
||||
function renderFeeds(){
|
||||
const q=$('#feedFilter').value.trim().toLowerCase();
|
||||
const list=$('#feedlist'); const top=list.scrollTop;
|
||||
@@ -104,6 +120,7 @@ function renderFeeds(){
|
||||
if(kids) $('.chev',el).onclick=ev=>{ ev.stopPropagation(); toggleGroup(f.id); };
|
||||
list.appendChild(el);
|
||||
}
|
||||
paintScanning();
|
||||
done();
|
||||
}
|
||||
function selectFeed(id){
|
||||
|
||||
@@ -34,7 +34,6 @@ function pullShow(dy: number){
|
||||
function refreshFeed(){
|
||||
const f = S.feeds.find(x => x.id === S.feed);
|
||||
if(!f && S.feed !== ':all') return;
|
||||
toast(f ? `Checking ${f.title || f.id} for new items…` : 'Checking every feed for new items…');
|
||||
// New items arrive by the event stream when the scan finishes, as they do for a button press.
|
||||
api('/api/fetch', {method: 'POST', body: JSON.stringify(f ? {feed: f.id, force: true} : {force: true})})
|
||||
.catch(e => toast(e.message, true));
|
||||
|
||||
Reference in New Issue
Block a user