mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
The per-video Popularity modal now plots a second, orange zaps-over-time line beside the red views line, with a legend and — new — VISIBLE x-axis date ticks (the old chart only put the range in the aria-label). - buildZapSatsTimeSeries: buckets cumulative SATS from 9735 receipts + verified bitvid tallies, deduped by payment_hash (same rule as the badge store) so the chart and badge agree. - buildPopularityChartSvg: two token-colored cumulative lines on a shared time x-axis, each with its OWN y scale (independent per-series — D5), plus date-tick <text> labels. Views = .text-accent, zaps = .text-zap (new --color-zap utility). Legend is plain DOM in the modal; headline gains "· N sats zapped". - js/zapEventsFacade.js: lists a pointer's [9735, ZAP_TALLY_KIND] events via the L1 SubscriptionManager for the chart; modal re-renders on onZapTotalsChanged. - All flag-gated (FEATURE_ZAP_TALLY); the existing views-only chart + its test are untouched (buildViewCountChartSvg kept). Verified live earlier that imports resolve (app builds/runs). Tests: tests/zap-time-series-chart.test.mjs (5) + existing view-count-chart green. Lint + build green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
72 lines
2.2 KiB
JavaScript
72 lines
2.2 KiB
JavaScript
// js/zapEventsFacade.js
|
|
//
|
|
// Lists the zap events for a video/profile pointer so the Popularity chart can
|
|
// draw a zaps-over-time series (docs/zap-tally-plan.md §5.9). Fetches BOTH real
|
|
// NIP-57 receipts (kind 9735) and bitvid tallies (ZAP_TALLY_KIND); the chart's
|
|
// buildZapSatsTimeSeries verifies tallies + dedups by payment_hash, so this just
|
|
// needs to return the raw events. Routed through the L1 SubscriptionManager
|
|
// (relay caps + in-flight dedupe), matching the zap-totals store.
|
|
|
|
import { nostrClient } from "./nostrClientFacade.js";
|
|
import { ZAP_RECEIPT_KIND } from "./zapTotals.js";
|
|
import { ZAP_TALLY_KIND } from "./nostrEventSchemas.js";
|
|
import { devLogger } from "./utils/logger.js";
|
|
|
|
const LIMIT = 500;
|
|
|
|
// pointer: the tag-style ["a"|"e", value, relay?] array resolveVideoPointer
|
|
// produces (or an { type, value } object).
|
|
function pointerFilterKey(pointer) {
|
|
const type = Array.isArray(pointer)
|
|
? pointer[0]
|
|
: pointer && typeof pointer === "object"
|
|
? pointer.type
|
|
: "";
|
|
const value = Array.isArray(pointer)
|
|
? pointer[1]
|
|
: pointer && typeof pointer === "object"
|
|
? pointer.value
|
|
: "";
|
|
if ((type === "a" || type === "e") && typeof value === "string" && value.trim()) {
|
|
return { tag: `#${type}`, value: value.trim() };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function listVideoZapEventsWithDefaultClient(
|
|
pointer,
|
|
{ since, client = nostrClient } = {},
|
|
) {
|
|
const pf = pointerFilterKey(pointer);
|
|
if (!pf) {
|
|
return [];
|
|
}
|
|
const manager =
|
|
typeof client?.getSubscriptionManager === "function"
|
|
? client.getSubscriptionManager()
|
|
: null;
|
|
const relays = Array.isArray(client?.relays) ? client.relays : [];
|
|
if (!manager || typeof manager.list !== "function" || !relays.length) {
|
|
return [];
|
|
}
|
|
|
|
const filter = {
|
|
kinds: [ZAP_RECEIPT_KIND, ZAP_TALLY_KIND],
|
|
[pf.tag]: [pf.value],
|
|
limit: LIMIT,
|
|
};
|
|
if (Number.isFinite(since) && since > 0) {
|
|
filter.since = Math.floor(since);
|
|
}
|
|
|
|
try {
|
|
const events = await manager.list({ relays, filters: [filter] });
|
|
return Array.isArray(events) ? events : [];
|
|
} catch (error) {
|
|
devLogger.warn("[zapEvents] Failed to list zap events:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export default listVideoZapEventsWithDefaultClient;
|