Browse the Directory by category, then subcategory, as Apple does (#118)

The Directory had one row of chips holding whatever each feed's category was, a category
(Technology) or a subcategory (Tech News, Video Games) side by side: a podcast's own
<itunes:category> is stored as its subcategory, and Jev's answers (#117) are often
subcategories too, so after the first forced scan the row held 22 chips. /api/directory now
gives each feed's Apple category and subcategory, worked out from Apple's list, and the
page shows the categories, then a picked one's subcategories on a line of their own. A
category that is not Apple's, one an admin typed, stands as a category of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 22:33:26 +00:00
parent 3e384f34f9
commit 02a46bb500
6 changed files with 58 additions and 11 deletions

View File

@@ -861,8 +861,10 @@ 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.
/// Apple's category, from the feed's own iTunes category or else the catalogue's, and the
/// subcategory within it where there is one: the Directory browses them in that order.
category: Option<String>,
subcategory: 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,
@@ -902,14 +904,16 @@ async fn popular(state: &WebState, user_id: i64, everything: bool) -> Result<Vec
}
let sum = listed.remove(&s.id).map(|l| l.summary).unwrap_or_default();
let subscribed = mine.contains(&s.id);
// The feed's own wins; an admin's is for the feeds, mostly blogs, that name none.
let path = sum.category.or_else(|| s.cfg.category.clone()).map(|c| crate::feed::category_path(&c));
out.push(PopularRow {
id: s.id.clone(),
title: sum.title,
image: sum.image,
subscribers: n,
subscribed,
// The feed's own wins; an admin's is for the feeds, mostly blogs, that name none.
category: sum.category.or_else(|| s.cfg.category.clone()),
category: path.as_ref().map(|p| p.0.clone()),
subcategory: path.and_then(|p| p.1),
podcast: media.contains(&s.id),
});
}