An OPML's page lists its shows' items together, not the shows (#283)
GET /api/feeds/{id}/entries for an OPML or a Patreon creator now lists the
items of the shows in it the person subscribes to, as All Subscriptions does
every feed's. The shows are looked up first and queried by list: an OR with a
subquery, or an IN over one, made a single feed's page a sequential scan of
entries (clarkesworld 10 ms to 26 ms or a second, EXPLAIN on production), so a
single feed keeps its `feed_id = $1`.
The page draws the OPML with its own header and the item list, tabs, search
and sort of any feed, with the feed column showing which show each is from.
A show's scan or change refreshes its OPML's list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,11 +24,9 @@ function keepList(key: string, r: ListPage){
|
||||
/// Fetches a list's first page ahead of a click: when the page has loaded, and when the pointer
|
||||
/// reaches a feed or a filter. Not one kept already or on its way.
|
||||
function prefetch(feed: string, filter=S.filter){
|
||||
// Currently Listening and the Directory draw from their own address; a folder's page lists its
|
||||
// feeds, not items.
|
||||
// Currently Listening and the Directory draw from their own address.
|
||||
const url=VIEWS[feed]?.url;
|
||||
if(url) return prefetchPage(url);
|
||||
if(S.feeds.some(c=>c.group===feed)) return;
|
||||
const key=listKey(feed,filter);
|
||||
if(lists.has(key)||fetching.has(key)) return;
|
||||
const got: Promise<ListPage>=api(listUrl(feed,filter));
|
||||
@@ -51,8 +49,9 @@ const prefetchPage=(url: string)=>{ if(!pages.has(url)) fetchPage(url).catch(()=
|
||||
/// A feed changed (a scan, a read elsewhere): its kept lists, and every one of All Subscriptions,
|
||||
/// are fetched again when next wanted.
|
||||
function forgetLists(feed: string){
|
||||
// A Signal holds items of any feed.
|
||||
for(const k of [...lists.keys()]) if(k.startsWith(feed+'\n')||k.startsWith(':all\n')||k.startsWith(':signal:')) lists.delete(k);
|
||||
// A Signal holds items of any feed, and an OPML's list its shows' (#283).
|
||||
const group=S.feeds.find(f=>f.id===feed)?.group;
|
||||
for(const k of [...lists.keys()]) if(k.startsWith(feed+'\n')||k.startsWith(':all\n')||k.startsWith(':signal:')||(group&&k.startsWith(group+'\n'))) lists.delete(k);
|
||||
}
|
||||
/// What is likely to be opened next, fetched two at a time once the page is idle: All Subscriptions
|
||||
/// and the feeds with something unread, most first.
|
||||
@@ -125,7 +124,9 @@ function listLoading(box=$('#eps')){
|
||||
function renderEntries(){
|
||||
const box=$('#eps'); if(!box) return;
|
||||
const f=S.feeds.find(x=>x.id===S.feed), v=VIEWS[S.feed ?? ''], sg=signalOf(S.feed);
|
||||
const unread=f?f.unread:sg?sg.unread:S.feeds.reduce((n,x)=>n+(x.unread||0),0);
|
||||
// An OPML's own row counts nothing: its shows do.
|
||||
const kids=f?S.feeds.filter(c=>c.group===f.id):[];
|
||||
const unread=kids.length?kids.reduce((n,x)=>n+x.unread,0):f?f.unread:sg?sg.unread:S.feeds.reduce((n,x)=>n+(x.unread||0),0);
|
||||
$('#count').textContent=
|
||||
`${f?(f.title||f.id):sg?sg.name:v?v.title:''}: ${S.total} item${S.total===1?'':'s'}, ${unread} unread`;
|
||||
const pane=$('#list'), top=pane?pane.scrollTop:0;
|
||||
|
||||
Reference in New Issue
Block a user