Popular button in the sidebar; the popular list counts everyone

- A Popular button beside + Feed opens the popular list directly; the
  Add feed dialog keeps it too.
- The list counts every subscriber, you included. Your own feeds stay on
  it, marked Subscribed, and clicking one opens it. Private feeds and
  feeds inside an OPML are still never listed, for anyone.
- GET /api/popular rows carry `subscribed`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HdTEWQNrzyFULijigkmMn
This commit is contained in:
2026-09-11 14:05:24 +00:00
parent 8b8b48302b
commit c0f4b0bcb2
7 changed files with 54 additions and 16 deletions

View File

@@ -561,10 +561,13 @@ struct PopularRow {
title: Option<String>,
image: Option<String>,
subscribers: i64,
/// Yours already. Everyone counts, you included, so your own feeds are listed too.
subscribed: bool,
}
/// What other people here subscribe to that you don't, most subscribers first. What the
/// Add feed screen offers, and all that `subscribe_popular` will subscribe you to.
/// What everyone here subscribes to, you included, most subscribers first. What the Popular
/// button and the Add feed screen show, and all that `subscribe_popular` will subscribe
/// you to.
fn popular(state: &WebState, user_id: i64) -> Result<Vec<PopularRow>> {
let db = &state.ctx.db;
let mine: std::collections::HashSet<String> =
@@ -576,11 +579,12 @@ fn popular(state: &WebState, user_id: i64) -> Result<Vec<PopularRow>> {
// A feed from an OPML rides on the OPML: everyone subscribed to it counts every feed
// inside, which would bury everything anyone chose on purpose.
let from_opml = s.managed || s.cfg.group.is_some();
if n == 0 || from_opml || mine.contains(&s.id) || looks_private(&s.cfg) {
if n == 0 || from_opml || looks_private(&s.cfg) {
continue;
}
let sum = db.feed_summary(&s.id)?;
out.push(PopularRow { id: s.id, title: sum.title, image: sum.image, subscribers: n });
let subscribed = mine.contains(&s.id);
out.push(PopularRow { id: s.id, title: sum.title, image: sum.image, subscribers: n, subscribed });
}
let name = |p: &PopularRow| p.title.clone().unwrap_or_else(|| p.id.clone()).to_lowercase();
out.sort_by(|a, b| b.subscribers.cmp(&a.subscribers).then_with(|| name(a).cmp(&name(b))));