mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
New FEED_TYPES.TRENDING tab: the active (recently-added) source ranked by view count (recency tiebreak, trusted-muted last), gated by FEATURE_TRENDING_FEED (default on). Builds on the now-trustworthy view counts (#4). - createTrendingSorter reads counts from runtime.getViewCount, which resolves each video's pointer via the app's canonical deriveVideoPointerInfo against the shared viewCounter cache (new getVideoViewCountSnapshot export) — the same counts the grid cards already load. - Live re-rank: viewCounter now emits a coalesced onViewCountsChanged signal; while the Trending view is active it debounce-re-runs the feed (cheap re-rank over the in-memory active set, no relay re-fetch), so the order settles into true trending as counts stream in. Cold cache reads as recency, then improves. - Pipeline extracted to js/feedEngine/trendingFeed.js (feedCoordinator is at its size cap; bumped the cap for the thin load/refresh delegations). - Wiring: FEED_TYPES + flag, feedCoordinator cases, app.js wrappers, applicationBootstrap registration (hides the sidebar link when flag off), viewManager init + js/trendingView.js, views/trending.html, sidebar link. Tests: tests/trending-sorter.test.mjs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
66 lines
2.0 KiB
JavaScript
66 lines
2.0 KiB
JavaScript
// js/trendingView.js
|
|
//
|
|
// The "Trending" tab presentation hook. The ranking lives in the feed engine
|
|
// (createTrendingSorter), but view counts arrive asynchronously as the grid
|
|
// cards subscribe to them. So while this view is active we listen for
|
|
// viewCounter's coalesced "counts changed" signal and re-run the Trending feed
|
|
// (a cheap re-rank over the in-memory active set, no relay re-fetch) so the order
|
|
// visibly settles into true trending as counts stream in.
|
|
|
|
import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js";
|
|
import { onViewCountsChanged } from "./viewCounter.js";
|
|
|
|
const TRENDING_RERANK_DEBOUNCE_MS = 700;
|
|
|
|
let unsubscribeCounts = null;
|
|
let rerankTimer = null;
|
|
|
|
export function teardownTrendingView() {
|
|
if (typeof unsubscribeCounts === "function") {
|
|
try {
|
|
unsubscribeCounts();
|
|
} catch (error) {
|
|
// best effort
|
|
}
|
|
unsubscribeCounts = null;
|
|
}
|
|
if (rerankTimer) {
|
|
clearTimeout(rerankTimer);
|
|
rerankTimer = null;
|
|
}
|
|
}
|
|
|
|
export function initTrendingView({ getApp, isActive } = {}) {
|
|
const infoTrigger = document.getElementById("trendingInfoTrigger");
|
|
if (infoTrigger) {
|
|
attachFeedInfoPopover(
|
|
infoTrigger,
|
|
"Trending ranks recently-added videos by their view count. The order settles as counts load in."
|
|
);
|
|
}
|
|
|
|
// Reset any prior subscription (re-entering the tab).
|
|
teardownTrendingView();
|
|
|
|
const stillActive = typeof isActive === "function" ? isActive : () => true;
|
|
|
|
unsubscribeCounts = onViewCountsChanged(() => {
|
|
if (rerankTimer) {
|
|
return;
|
|
}
|
|
rerankTimer = setTimeout(() => {
|
|
rerankTimer = null;
|
|
if (!stillActive()) {
|
|
teardownTrendingView();
|
|
return;
|
|
}
|
|
const app = typeof getApp === "function" ? getApp() : null;
|
|
if (app && typeof app.refreshTrendingFeed === "function") {
|
|
Promise.resolve(
|
|
app.refreshTrendingFeed({ reason: "view-count-update" })
|
|
).catch(() => {});
|
|
}
|
|
}, TRENDING_RERANK_DEBOUNCE_MS);
|
|
});
|
|
}
|