From b3274c9e682f0705b2f444f9fefcb54dcd03122e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:02:55 -0400 Subject: [PATCH] Preserve watch history ordering --- js/historyView.js | 466 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 463 insertions(+), 3 deletions(-) diff --git a/js/historyView.js b/js/historyView.js index 0b508d7f..6fd013d7 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -3,6 +3,8 @@ import { nostrClient } from "./nostr.js"; import { WATCH_HISTORY_BATCH_RESOLVE } from "./config.js"; import { subscriptions } from "./subscriptions.js"; +import { attachHealthBadges } from "./gridHealth.js"; +import { attachUrlHealthBadges } from "./urlHealthObserver.js"; const DEFAULT_BATCH_SIZE = 20; const BATCH_SIZE = WATCH_HISTORY_BATCH_RESOLVE ? DEFAULT_BATCH_SIZE : 1; @@ -15,6 +17,466 @@ function toNumber(value, fallback) { return Number.isFinite(parsed) ? parsed : fallback; } +function getAbsoluteShareUrl(nevent) { + if (!nevent) { + return ""; + } + + if (window.app?.buildShareUrlFromNevent) { + const candidate = window.app.buildShareUrlFromNevent(nevent); + if (candidate) { + return candidate; + } + } + + const origin = window.location?.origin || ""; + const pathname = window.location?.pathname || ""; + let base = origin || pathname ? `${origin}${pathname}` : ""; + if (!base) { + const href = window.location?.href || ""; + base = href ? href.split(/[?#]/)[0] : ""; + } + + if (!base) { + return `?v=${encodeURIComponent(nevent)}`; + } + + return `${base}?v=${encodeURIComponent(nevent)}`; +} + +function renderWatchHistoryGrid(videos, containerOrElement) { + let container = null; + if (typeof containerOrElement === "string") { + container = document.getElementById(containerOrElement); + } else if (containerOrElement instanceof Element) { + container = containerOrElement; + } + + if (!container) { + return; + } + + const safeVideos = Array.isArray(videos) ? videos : []; + const dedupedVideos = + window.app?.dedupeVideosByRoot?.(safeVideos) ?? + subscriptions.dedupeToNewestByRoot(safeVideos); + + const filteredVideos = dedupedVideos.filter((video) => { + if (!video || typeof video !== "object") { + return false; + } + + if (window.app?.isAuthorBlocked && window.app.isAuthorBlocked(video.pubkey)) { + return false; + } + + return true; + }); + + if (!filteredVideos.length) { + container.innerHTML = ` +

+ No videos available yet. +

`; + return; + } + + const fullAllEventsArray = Array.from(nostrClient.allEvents.values()); + const fragment = document.createDocumentFragment(); + const localAuthorSet = new Set(); + + filteredVideos.forEach((video, index) => { + if (!video.id || !video.title) { + console.error("Missing ID or title:", video); + return; + } + + window.app?.videosMap?.set(video.id, video); + + localAuthorSet.add(video.pubkey); + + const nevent = window.NostrTools.nip19.neventEncode({ id: video.id }); + const shareUrl = getAbsoluteShareUrl(nevent); + const canEdit = window.app?.pubkey === video.pubkey; + + const highlightClass = + video.isPrivate && canEdit ? "border-2 border-yellow-500" : "border-none"; + + const timeAgo = window.app?.formatTimeAgo + ? window.app.formatTimeAgo(video.created_at) + : new Date(video.created_at * 1000).toLocaleString(); + + let hasOlder = false; + if (canEdit && video.videoRootId && window.app?.hasOlderVersion) { + hasOlder = window.app.hasOlderVersion(video, fullAllEventsArray); + } + + const revertButton = hasOlder + ? ` + + ` + : ""; + + const gearMenu = canEdit + ? ` +
+ + +
+ ` + : ""; + + const moreMenu = ` +
+ + +
+ `; + + const cardControls = ` +
+ ${moreMenu}${gearMenu} +
+ `; + + const safeTitle = window.app?.escapeHTML(video.title) || "Untitled"; + const safeThumb = window.app?.escapeHTML(video.thumbnail) || ""; + const playbackUrl = typeof video.url === "string" ? video.url : ""; + const trimmedUrl = playbackUrl ? playbackUrl.trim() : ""; + const trimmedMagnet = + typeof video.magnet === "string" ? video.magnet.trim() : ""; + const legacyInfoHash = + typeof video.infoHash === "string" ? video.infoHash.trim() : ""; + const magnetCandidate = trimmedMagnet || legacyInfoHash; + const playbackMagnet = magnetCandidate; + const magnetProvided = magnetCandidate.length > 0; + const magnetSupported = + window.app?.isMagnetUriSupported?.(magnetCandidate) ?? false; + const urlBadgeHtml = trimmedUrl + ? window.app?.getUrlHealthPlaceholderMarkup?.({ includeMargin: false }) ?? "" + : ""; + const torrentHealthBadgeHtml = + magnetProvided && magnetSupported + ? window.app?.getTorrentHealthBadgeMarkup?.({ + includeMargin: false, + }) ?? "" + : ""; + const connectionBadgesHtml = urlBadgeHtml || torrentHealthBadgeHtml + ? ` +
+ ${urlBadgeHtml}${torrentHealthBadgeHtml} +
+ ` + : ""; + const cardHtml = ` +
+ +
+ ${safeTitle} +
+
+
+

+ ${safeTitle} +

+
+
+
+ Placeholder +
+
+

+ Loading name... +

+
+ ${timeAgo} +
+
+
+ ${cardControls} +
+ ${connectionBadgesHtml} +
+
+ `; + + const t = document.createElement("template"); + t.innerHTML = cardHtml.trim(); + const cardEl = t.content.firstElementChild; + if (cardEl) { + cardEl.dataset.ownerIsViewer = canEdit ? "true" : "false"; + if (typeof video.pubkey === "string" && video.pubkey) { + cardEl.dataset.ownerPubkey = video.pubkey; + } else if (cardEl.dataset.ownerPubkey) { + delete cardEl.dataset.ownerPubkey; + } + + if (trimmedUrl) { + cardEl.dataset.urlHealthState = "checking"; + if (cardEl.dataset.urlHealthReason) { + delete cardEl.dataset.urlHealthReason; + } + cardEl.dataset.urlHealthEventId = video.id || ""; + cardEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl); + } else { + cardEl.dataset.urlHealthState = "offline"; + cardEl.dataset.urlHealthReason = "missing-source"; + if (cardEl.dataset.urlHealthEventId) { + delete cardEl.dataset.urlHealthEventId; + } + if (cardEl.dataset.urlHealthUrl) { + delete cardEl.dataset.urlHealthUrl; + } + } + if (magnetProvided && magnetSupported) { + cardEl.dataset.streamHealthState = "checking"; + if (cardEl.dataset.streamHealthReason) { + delete cardEl.dataset.streamHealthReason; + } + } else { + cardEl.dataset.streamHealthState = "unhealthy"; + cardEl.dataset.streamHealthReason = magnetProvided + ? "unsupported" + : "missing-source"; + } + + const interactiveEls = cardEl.querySelectorAll("[data-video-id]"); + interactiveEls.forEach((el) => { + if (!el.dataset) return; + + if (trimmedUrl) { + el.dataset.playUrl = encodeURIComponent(trimmedUrl); + } else { + delete el.dataset.playUrl; + } + + el.dataset.playMagnet = playbackMagnet || ""; + }); + + if (magnetProvided) { + cardEl.dataset.magnet = playbackMagnet; + } else if (cardEl.dataset.magnet) { + delete cardEl.dataset.magnet; + } + + const badgeEl = cardEl.querySelector("[data-url-health-state]"); + if (badgeEl) { + if (trimmedUrl) { + badgeEl.dataset.urlHealthEventId = video.id || ""; + badgeEl.dataset.urlHealthUrl = encodeURIComponent(trimmedUrl); + } else { + if (badgeEl.dataset.urlHealthEventId) { + delete badgeEl.dataset.urlHealthEventId; + } + if (badgeEl.dataset.urlHealthUrl) { + delete badgeEl.dataset.urlHealthUrl; + } + } + } + } + fragment.appendChild(cardEl); + }); + + container.innerHTML = ""; + container.appendChild(fragment); + attachHealthBadges(container); + attachUrlHealthBadges(container, ({ badgeEl, url, eventId }) => { + if (!window.app?.handleUrlHealthBadge) { + return; + } + const video = window.app.videosMap?.get?.(eventId) || { id: eventId }; + window.app.handleUrlHealthBadge({ video, url, badgeEl }); + }); + + window.app?.attachVideoListHandler?.(); + + const lazyEls = container.querySelectorAll("[data-lazy]"); + lazyEls.forEach((el) => window.app?.mediaLoader.observe(el)); + + window.app?.attachMoreMenuHandlers?.(container); + + const gearButtons = container.querySelectorAll("[data-settings-dropdown]"); + gearButtons.forEach((btn) => { + btn.addEventListener("click", (ev) => { + ev.stopPropagation(); + const idx = btn.getAttribute("data-settings-dropdown"); + const dropdown = document.getElementById(`settingsDropdown-${idx}`); + if (dropdown) dropdown.classList.toggle("hidden"); + }); + }); + + const editButtons = container.querySelectorAll("[data-edit-index]"); + editButtons.forEach((btn) => { + btn.addEventListener("click", (ev) => { + ev.stopPropagation(); + const idxAttr = btn.getAttribute("data-edit-index"); + const idx = Number.parseInt(idxAttr, 10); + const dropdown = document.getElementById(`settingsDropdown-${idxAttr}`); + if (dropdown) dropdown.classList.add("hidden"); + const eventId = btn.getAttribute("data-edit-event-id") || ""; + window.app?.handleEditVideo({ + eventId, + index: Number.isNaN(idx) ? null : idx, + }); + }); + }); + + const revertButtons = container.querySelectorAll("[data-revert-index]"); + revertButtons.forEach((btn) => { + btn.addEventListener("click", (ev) => { + ev.stopPropagation(); + const idxAttr = btn.getAttribute("data-revert-index"); + const idx = Number.parseInt(idxAttr, 10); + const dropdown = document.getElementById(`settingsDropdown-${idxAttr}`); + if (dropdown) dropdown.classList.add("hidden"); + const eventId = btn.getAttribute("data-revert-event-id") || ""; + window.app?.handleRevertVideo({ + eventId, + index: Number.isNaN(idx) ? null : idx, + }); + }); + }); + + const deleteAllButtons = container.querySelectorAll("[data-delete-all-index]"); + deleteAllButtons.forEach((btn) => { + btn.addEventListener("click", (ev) => { + ev.stopPropagation(); + const idxAttr = btn.getAttribute("data-delete-all-index"); + const idx = Number.parseInt(idxAttr, 10); + const dd = document.getElementById(`settingsDropdown-${idxAttr}`); + if (dd) dd.classList.add("hidden"); + const eventId = btn.getAttribute("data-delete-all-event-id") || ""; + window.app?.handleFullDeleteVideo({ + eventId, + index: Number.isNaN(idx) ? null : idx, + }); + }); + }); + + const authorPics = container.querySelectorAll(".author-pic"); + const authorNames = container.querySelectorAll(".author-name"); + + authorPics.forEach((pic) => { + localAuthorSet.add(pic.getAttribute("data-pubkey")); + }); + authorNames.forEach((nameEl) => { + localAuthorSet.add(nameEl.getAttribute("data-pubkey")); + }); + + if (window.app?.batchFetchProfiles && localAuthorSet.size > 0) { + window.app.batchFetchProfiles(localAuthorSet); + } + + authorPics.forEach((pic) => { + pic.style.cursor = "pointer"; + pic.addEventListener("click", (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + const pubkey = pic.getAttribute("data-pubkey"); + window.app?.goToProfile(pubkey); + }); + }); + + authorNames.forEach((nameEl) => { + nameEl.style.cursor = "pointer"; + nameEl.addEventListener("click", (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + const pubkey = nameEl.getAttribute("data-pubkey"); + window.app?.goToProfile(pubkey); + }); + }); +} + export function createWatchHistoryRenderer(config = {}) { const fallbackGetActor = async () => { const candidate = @@ -268,10 +730,8 @@ export function createWatchHistoryRenderer(config = {}) { if (typeof renderGrid === "function") { renderGrid(state.resolvedVideos, grid); - } else if (grid.id) { - subscriptions.renderSameGridStyle(state.resolvedVideos, grid.id); } else { - subscriptions.renderSameGridStyle(state.resolvedVideos, "watchHistoryGrid"); + renderWatchHistoryGrid(state.resolvedVideos, grid); } grid.classList.remove("hidden");