diff --git a/css/style.css b/css/style.css index 208e5552..07aa6a01 100644 --- a/css/style.css +++ b/css/style.css @@ -74,6 +74,10 @@ header img { box-shadow: var(--shadow-md); } +.video-card[data-owner-is-viewer="false"][data-url-health-state="offline"][data-stream-health-state="unhealthy"] { + display: none; +} + .video-card--enter { opacity: 0; animation: video-card-fade-in 220ms ease-out forwards; diff --git a/js/app.js b/js/app.js index 7d693751..98b97656 100644 --- a/js/app.js +++ b/js/app.js @@ -3243,6 +3243,10 @@ class bitvidApp { const hadMargin = badgeEl.classList.contains("mt-3"); badgeEl.dataset.urlHealthState = status; + const cardEl = badgeEl.closest(".video-card"); + if (cardEl) { + cardEl.dataset.urlHealthState = status; + } badgeEl.setAttribute("aria-live", "polite"); badgeEl.setAttribute("role", status === "offline" ? "alert" : "status"); badgeEl.textContent = message; @@ -3677,6 +3681,22 @@ class bitvidApp { template.innerHTML = cardHtml.trim(); const cardEl = template.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; + } + + cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (magnetProvided && magnetSupported) { + cardEl.dataset.streamHealthState = "checking"; + } else if (magnetProvided) { + cardEl.dataset.streamHealthState = "unsupported"; + } else { + cardEl.dataset.streamHealthState = "absent"; + } + const thumbnailEl = cardEl.querySelector("[data-video-thumbnail]"); if (thumbnailEl) { const markThumbnailAsLoaded = () => { diff --git a/js/channelProfile.js b/js/channelProfile.js index 531d5ad4..abeb30bd 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -360,6 +360,13 @@ async function loadUserVideos(pubkey) { "duration-300" ); + 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; + } + const rawMagnet = typeof video.magnet === "string" ? video.magnet : ""; const trimmedMagnet = rawMagnet ? rawMagnet.trim() : ""; @@ -433,6 +440,15 @@ async function loadUserVideos(pubkey) { delete cardEl.dataset.torrentSupported; } + cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (magnetProvided && magnetSupported) { + cardEl.dataset.streamHealthState = "checking"; + } else if (magnetProvided) { + cardEl.dataset.streamHealthState = "unsupported"; + } else { + cardEl.dataset.streamHealthState = "absent"; + } + if (magnetProvided) { cardEl.dataset.magnet = playbackMagnet; } else if (cardEl.dataset.magnet) { diff --git a/js/gridHealth.js b/js/gridHealth.js index b2cf13ca..a0394eed 100644 --- a/js/gridHealth.js +++ b/js/gridHealth.js @@ -433,11 +433,23 @@ function setBadge(card, state, details) { badge.setAttribute("aria-live", entry.role === "alert" ? "assertive" : "polite"); badge.setAttribute("role", entry.role); badge.dataset.streamHealthState = state; - if (Number.isFinite(peers)) { - badge.dataset.streamHealthPeers = String(peers); + const hasPeerCount = Number.isFinite(peers); + const peersTextValue = hasPeerCount ? String(peers) : ""; + if (hasPeerCount) { + badge.dataset.streamHealthPeers = peersTextValue; } else if (badge.dataset.streamHealthPeers) { delete badge.dataset.streamHealthPeers; } + + const card = badge.closest(".video-card"); + if (card) { + card.dataset.streamHealthState = state; + if (hasPeerCount) { + card.dataset.streamHealthPeers = peersTextValue; + } else if (card.dataset.streamHealthPeers) { + delete card.dataset.streamHealthPeers; + } + } } function handleCardVisible({ card, pendingByCard, priority = 0 }) { diff --git a/js/subscriptions.js b/js/subscriptions.js index 68b42102..216d1ab3 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -467,6 +467,22 @@ class SubscriptionsManager { 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; + } + + cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (magnetProvided && magnetSupported) { + cardEl.dataset.streamHealthState = "checking"; + } else if (magnetProvided) { + cardEl.dataset.streamHealthState = "unsupported"; + } else { + cardEl.dataset.streamHealthState = "absent"; + } + // Leave the data-play-* attributes empty in the literal markup so we can // assign the raw URL/magnet strings post-parsing without HTML entity // escaping, mirroring the approach in app.js. The URL is encoded so that