Directory: chips by kind and category over a grid of cover art

Feeds take their channel's first <itunes:category> into a new feeds.category
column; the migration drops ETag and Last-Modified once so every feed re-reads
on its normal schedule and picks one up. /api/popular and /api/directory carry
category and podcast (any audio or video enclosure). Directory becomes a grid of
cover-art tiles under a chip rail: All, Podcasts, Blogs, and a podcast's
categories once Podcasts is picked. Popular and Add a feed keep their rows.

Closes #4, closes #5, closes #6, closes #7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 16:09:47 +00:00
parent 97934d641a
commit 954173cacf
11 changed files with 207 additions and 38 deletions

View File

@@ -627,6 +627,11 @@ struct PopularRow {
subscribers: i64,
/// Yours already. Everyone counts, you included, so your own feeds are listed too.
subscribed: bool,
/// The feed's own iTunes category, if it names one; most blogs do not.
category: Option<String>,
/// Any audio or video enclosure. Unlike category, every feed has an answer, so the
/// Directory's Podcasts and Blogs between them hold everything.
podcast: bool,
}
/// Every feed that may be listed, with everyone counted, you included, most subscribers
@@ -638,6 +643,7 @@ fn popular(state: &WebState, user_id: i64) -> Result<Vec<PopularRow>> {
let mine: std::collections::HashSet<String> =
db.subscriptions_for(user_id)?.into_iter().map(|s| s.feed_id).collect();
let counts = db.subscriber_counts()?;
let media = db.media_feeds()?;
let catalogue = crate::subscriptions(&state.ctx)?;
let by_id: std::collections::HashMap<&str, &crate::config::Feed> =
catalogue.iter().map(|s| (s.id.as_str(), &s.cfg)).collect();
@@ -657,7 +663,15 @@ fn popular(state: &WebState, user_id: i64) -> Result<Vec<PopularRow>> {
}
let sum = db.feed_summary(&s.id)?;
let subscribed = mine.contains(&s.id);
out.push(PopularRow { id: s.id.clone(), title: sum.title, image: sum.image, subscribers: n, subscribed });
out.push(PopularRow {
id: s.id.clone(),
title: sum.title,
image: sum.image,
subscribers: n,
subscribed,
category: sum.category,
podcast: media.contains(&s.id),
});
}
out.sort_by(|a, b| b.subscribers.cmp(&a.subscribers).then_with(|| sort_name(a).cmp(&sort_name(b))));
Ok(out)