mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Trending-pattern clone ranked by sats from kind-9735 receipts: - js/zapTotals.js: per-pointer receipt aggregation. The feed's metric getter returns the cached total and schedules a batched fetch (#a/#e chunks via the L1 SubscriptionManager) for unknown/stale pointers; amounts parse via nip57 bolt11 with the zap request's amount tag as fallback; receipts are deduped by id so re-fetches never double-count; zero-receipt answers are cached (2-min TTL). - sorters.js: shared count-ranked sorter (metric desc, recency tie-break, author spread, trusted-muted sink); Trending delegates unchanged, Most Zapped ranks on runtime.getZapTotal. - Full wiring: feed definition, view + info popover, change-signal re-rank (order settles as receipts stream in), sidebar link, viewManager registry, feedCoordinator case/loader, FEED_TYPES.MOST_ZAPPED, and a FEATURE_MOST_ZAPPED_FEED flag (default on; off hides the tab). Co-Authored-By: Claude Fable 5 <[email protected]>
66 lines
2.0 KiB
JavaScript
66 lines
2.0 KiB
JavaScript
// js/mostZappedView.js
|
|
//
|
|
// The "Most Zapped" tab presentation hook (#47). The ranking lives in the feed
|
|
// engine (createMostZappedSorter); zap totals arrive asynchronously as the
|
|
// feed's metric getter schedules batched receipt fetches. While this view is
|
|
// active we listen for zapTotals' change signal and re-run the feed (a cheap
|
|
// re-rank over the in-memory active set, no relay re-fetch) so the order
|
|
// visibly settles as totals stream in — same pattern as trendingView.
|
|
|
|
import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js";
|
|
import { onZapTotalsChanged } from "./zapTotals.js";
|
|
|
|
const MOST_ZAPPED_RERANK_DEBOUNCE_MS = 700;
|
|
|
|
let unsubscribeTotals = null;
|
|
let rerankTimer = null;
|
|
|
|
export function teardownMostZappedView() {
|
|
if (typeof unsubscribeTotals === "function") {
|
|
try {
|
|
unsubscribeTotals();
|
|
} catch (error) {
|
|
// best effort
|
|
}
|
|
unsubscribeTotals = null;
|
|
}
|
|
if (rerankTimer) {
|
|
clearTimeout(rerankTimer);
|
|
rerankTimer = null;
|
|
}
|
|
}
|
|
|
|
export function initMostZappedView({ getApp, isActive } = {}) {
|
|
const infoTrigger = document.getElementById("mostZappedInfoTrigger");
|
|
if (infoTrigger) {
|
|
attachFeedInfoPopover(
|
|
infoTrigger,
|
|
"Most Zapped ranks recently-added videos by their zap total (sats). The order settles as zap receipts load in."
|
|
);
|
|
}
|
|
|
|
// Reset any prior subscription (re-entering the tab).
|
|
teardownMostZappedView();
|
|
|
|
const stillActive = typeof isActive === "function" ? isActive : () => true;
|
|
|
|
unsubscribeTotals = onZapTotalsChanged(() => {
|
|
if (rerankTimer) {
|
|
return;
|
|
}
|
|
rerankTimer = setTimeout(() => {
|
|
rerankTimer = null;
|
|
if (!stillActive()) {
|
|
teardownMostZappedView();
|
|
return;
|
|
}
|
|
const app = typeof getApp === "function" ? getApp() : null;
|
|
if (app && typeof app.refreshMostZappedFeed === "function") {
|
|
Promise.resolve(
|
|
app.refreshMostZappedFeed({ reason: "zap-total-update" })
|
|
).catch(() => {});
|
|
}
|
|
}, MOST_ZAPPED_RERANK_DEBOUNCE_MS);
|
|
});
|
|
}
|