mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 00:42:30 +00:00
Sort watch history by most recent view
This commit is contained in:
1 parent
b44373a4b6
commit
bea08e3b3c
3 files changed
+90
-24
No files matched your search
+8
-24
@@ -5,6 +5,11 @@ import { WATCH_HISTORY_BATCH_RESOLVE } from "./config.js";
|
||||
import { subscriptions } from "./subscriptions.js";
|
||||
import { accessControl } from "./accessControl.js";
|
||||
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
||||
import {
|
||||
toNumber,
|
||||
getWatchedAtScore,
|
||||
compareByWatchedAtDesc,
|
||||
} from "./watchHistorySort.js";
|
||||
|
||||
const DEFAULT_BATCH_SIZE = 20;
|
||||
const BATCH_SIZE = WATCH_HISTORY_BATCH_RESOLVE ? DEFAULT_BATCH_SIZE : 1;
|
||||
@@ -15,11 +20,6 @@ export const WATCH_HISTORY_EMPTY_COPY =
|
||||
const WATCH_HISTORY_LOADING_STATUS = "Fetching watch history from relays…";
|
||||
const WATCH_HISTORY_EMPTY_STATUS = "No watch history yet.";
|
||||
|
||||
function toNumber(value, fallback) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
function getVideoAuthorIdentifiers(video) {
|
||||
if (!video || typeof video !== "object") {
|
||||
return { pubkey: "", npub: "" };
|
||||
@@ -119,24 +119,6 @@ function filterAccessibleVideos(videos) {
|
||||
});
|
||||
}
|
||||
|
||||
function getWatchedAtScore(video) {
|
||||
if (!video || typeof video !== "object") {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
const watchedAt = toNumber(video?.watchHistory?.watchedAt, Number.NaN);
|
||||
if (Number.isFinite(watchedAt)) {
|
||||
return watchedAt;
|
||||
}
|
||||
|
||||
const createdAtSeconds = toNumber(video?.created_at, Number.NaN);
|
||||
if (Number.isFinite(createdAtSeconds)) {
|
||||
return createdAtSeconds * 1000;
|
||||
}
|
||||
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
function getAbsoluteShareUrl(nevent) {
|
||||
if (!nevent) {
|
||||
return "";
|
||||
@@ -822,7 +804,9 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
state.resolvedVideos.forEach((video) => ingest(dedupeMap, video));
|
||||
nextVideos.forEach((video) => ingest(dedupeMap, video));
|
||||
|
||||
state.resolvedVideos = Array.from(dedupeMap.values());
|
||||
state.resolvedVideos = Array.from(dedupeMap.values()).sort(
|
||||
compareByWatchedAtDesc
|
||||
);
|
||||
applyAccessFilters();
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// js/watchHistorySort.js
|
||||
|
||||
export function toNumber(value, fallback) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
export function getWatchedAtScore(video) {
|
||||
if (!video || typeof video !== "object") {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
const watchedAt = toNumber(video?.watchHistory?.watchedAt, Number.NaN);
|
||||
if (Number.isFinite(watchedAt)) {
|
||||
return watchedAt;
|
||||
}
|
||||
|
||||
const createdAtSeconds = toNumber(video?.created_at, Number.NaN);
|
||||
if (Number.isFinite(createdAtSeconds)) {
|
||||
return createdAtSeconds * 1000;
|
||||
}
|
||||
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
export function compareByWatchedAtDesc(a, b) {
|
||||
const aScore = getWatchedAtScore(a);
|
||||
const bScore = getWatchedAtScore(b);
|
||||
|
||||
if (aScore !== bScore) {
|
||||
return bScore - aScore;
|
||||
}
|
||||
|
||||
const aCreated = toNumber(a?.created_at, Number.NEGATIVE_INFINITY);
|
||||
const bCreated = toNumber(b?.created_at, Number.NEGATIVE_INFINITY);
|
||||
|
||||
if (aCreated !== bCreated) {
|
||||
return bCreated - aCreated;
|
||||
}
|
||||
|
||||
const aId = typeof a?.id === "string" ? a.id : "";
|
||||
const bId = typeof b?.id === "string" ? b.id : "";
|
||||
|
||||
return aId.localeCompare(bId);
|
||||
}
|
||||
@@ -12,6 +12,9 @@ const {
|
||||
buildWatchHistoryIndexEvent,
|
||||
buildWatchHistoryChunkEvent,
|
||||
} = await import("../js/nostrEventSchemas.js");
|
||||
const { compareByWatchedAtDesc, getWatchedAtScore } = await import(
|
||||
"../js/watchHistorySort.js"
|
||||
);
|
||||
|
||||
if (typeof globalThis.window === "undefined") {
|
||||
globalThis.window = {};
|
||||
@@ -1063,4 +1066,38 @@ try {
|
||||
Date.now = originalDateNow;
|
||||
}
|
||||
|
||||
const unsortedVideos = [
|
||||
{ id: "alpha", watchHistory: { watchedAt: 1_700_000_000_500 } },
|
||||
{ id: "bravo", watchHistory: { watchedAt: 1_700_000_010_000 } },
|
||||
{ id: "charlie", created_at: 1_699_999_000 },
|
||||
];
|
||||
|
||||
const sortedVideos = unsortedVideos.slice().sort(compareByWatchedAtDesc);
|
||||
|
||||
assert.deepEqual(
|
||||
sortedVideos.map((video) => video.id),
|
||||
["bravo", "alpha", "charlie"],
|
||||
"videos should be ordered by watchedAt descending with created_at fallback",
|
||||
);
|
||||
|
||||
const tieVideos = [
|
||||
{ id: "echo", watchHistory: { watchedAt: 1_700_100_000_000 }, created_at: 1_700_100_100 },
|
||||
{ id: "delta", watchHistory: { watchedAt: 1_700_100_000_000 }, created_at: 1_700_100_200 },
|
||||
{ id: "foxtrot", watchHistory: { watchedAt: 1_700_100_000_000 }, created_at: 1_700_100_200 },
|
||||
];
|
||||
|
||||
tieVideos.sort(compareByWatchedAtDesc);
|
||||
|
||||
assert.equal(
|
||||
getWatchedAtScore(tieVideos[0]),
|
||||
getWatchedAtScore(tieVideos[1]),
|
||||
"ties should preserve identical watchedAt scores",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
tieVideos.map((video) => video.id),
|
||||
["delta", "foxtrot", "echo"],
|
||||
"created_at should break watchedAt ties with stable fallback",
|
||||
);
|
||||
|
||||
console.log("watch history tests passed");
|
||||
Reference in new issue
Block a user