Sort watch history by most recent view

This commit is contained in:
thePR0M3TH3AN committed 2025-10-01 19:00:54 -04:00
1 parent b44373a4b6
commit bea08e3b3c
3 files changed
+90 -24

No files matched your search

+8 -24
View File
@@ -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();
};
+45
View File
@@ -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);
}
+37
View File
@@ -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");