From 7af3a536c3ca6a41fa047dc29690967d57a27a68 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:11:02 -0400 Subject: [PATCH] Revamp watch history cards --- css/style.css | 268 ++++++++++++++++++++++++++++-- js/historyView.js | 412 +++++++++++++++++++++++++++++++--------------- 2 files changed, 538 insertions(+), 142 deletions(-) diff --git a/css/style.css b/css/style.css index 3d23f26b..cdabeb25 100644 --- a/css/style.css +++ b/css/style.css @@ -104,31 +104,28 @@ header img { padding: 1.5rem 0; } -/* Dedicated watch-history grids mirror the home feed layout */ +/* Dedicated watch-history layout stacks cards vertically */ .watch-history-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); - gap: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; width: 100%; padding: 1.5rem 0; } -/* Profile modal gets a tighter column and removes outer padding */ +/* Profile modal uses the same vertical stack but without outer padding */ .watch-history-grid.watch-history-grid--modal { - grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr)); - gap: 1.5rem; padding: 0; + gap: 1.25rem; } @media (max-width: 640px) { .watch-history-grid { - grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); - gap: 1.5rem; + gap: 1.25rem; } .watch-history-grid.watch-history-grid--modal { - grid-template-columns: repeat(auto-fill, minmax(14.5rem, 1fr)); - gap: 1.25rem; + gap: 1.125rem; } } @@ -159,6 +156,255 @@ header img { animation: video-card-fade-in 220ms ease-out forwards; } +/* Watch history cards */ +.watch-history-card { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + align-items: stretch; + background-color: var(--color-card); + border-radius: 0.75rem; + padding: 1rem 1.25rem; + border: 0.0625rem solid transparent; + box-shadow: var(--shadow-md); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.watch-history-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.watch-history-card--private { + border-color: rgba(234, 179, 8, 0.75); +} + +.watch-history-card__primary { + display: flex; + gap: 1rem; + flex: 1 1 18rem; + min-width: 0; +} + +.watch-history-card__thumbnail { + display: block; + flex: 0 0 14rem; + max-width: 14rem; + text-decoration: none; +} + +.watch-history-card__thumbnailInner { + width: 100%; + height: 100%; + border-radius: 0.5rem; + overflow: hidden; + background-color: #0f172a; +} + +.watch-history-card__thumbnailInner img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.watch-history-card__details { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.5rem; + min-width: 0; +} + +.watch-history-card__title { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + color: #ffffff; + line-height: 1.4; + cursor: pointer; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.watch-history-card__title:hover, +.watch-history-card__title:focus { + color: #60a5fa; +} + +.watch-history-card__created { + margin: 0; + font-size: 0.875rem; + color: #9ca3af; +} + +.watch-history-card__meta { + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 1rem; + flex: 0 0 16rem; + min-width: 14rem; +} + +.watch-history-card__watched { + margin: 0; + font-size: 0.875rem; + color: #d1d5db; +} + +.watch-history-card__creator { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.watch-history-card__creatorAvatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 9999px; + border: 0; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: #1f2937; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.watch-history-card__creatorAvatar:hover, +.watch-history-card__creatorAvatar:focus { + box-shadow: 0 0 0 0.125rem rgba(59, 130, 246, 0.5); + outline: none; +} + +.watch-history-card__creatorAvatar img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 9999px; +} + +.watch-history-card__creatorName { + background: none; + border: none; + padding: 0; + margin: 0; + font-size: 0.95rem; + font-weight: 500; + color: #e5e7eb; + text-align: left; + cursor: pointer; +} + +.watch-history-card__creatorName:hover, +.watch-history-card__creatorName:focus { + color: #60a5fa; + outline: none; +} + +.watch-history-card__actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; +} + +.watch-history-card__action { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem 1rem; + border-radius: 9999px; + font-size: 0.875rem; + font-weight: 500; + border: 0.0625rem solid transparent; + background-color: #1f2937; + color: #e5e7eb; + cursor: pointer; + text-decoration: none; + transition: background-color 0.2s ease, color 0.2s ease, + border-color 0.2s ease, box-shadow 0.2s ease; +} + +.watch-history-card__action:hover, +.watch-history-card__action:focus { + background-color: #111827; + color: #ffffff; + border-color: #374151; + outline: none; +} + +.watch-history-card__action--primary { + background-color: #2563eb; + color: #ffffff; +} + +.watch-history-card__action--primary:hover, +.watch-history-card__action--primary:focus { + background-color: #1d4ed8; + border-color: #1d4ed8; +} + +.watch-history-card__action--danger { + background-color: #7f1d1d; + color: #ffffff; +} + +.watch-history-card__action--danger:hover, +.watch-history-card__action--danger:focus { + background-color: #991b1b; + border-color: #991b1b; +} + +.watch-history-card__menus { + display: flex; + align-items: center; + gap: 0.5rem; +} + +@media (max-width: 1024px) { + .watch-history-card__meta { + flex: 1 1 18rem; + } +} + +@media (max-width: 768px) { + .watch-history-card { + gap: 1.25rem; + padding: 1rem; + } + + .watch-history-card__thumbnail { + flex-basis: 11rem; + max-width: 11rem; + } +} + +@media (max-width: 640px) { + .watch-history-card { + flex-direction: column; + } + + .watch-history-card__primary, + .watch-history-card__meta { + flex: 1 1 auto; + min-width: 0; + } + + .watch-history-card__thumbnail { + width: 100%; + max-width: 100%; + } + + .watch-history-card__meta { + gap: 0.75rem; + } +} + @keyframes video-card-fade-in { from { opacity: 0; diff --git a/js/historyView.js b/js/historyView.js index 1c422cb8..a55f3a73 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -3,8 +3,6 @@ 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; @@ -103,12 +101,31 @@ function renderWatchHistoryGrid(videos, containerOrElement) { const canEdit = window.app?.pubkey === video.pubkey; const highlightClass = - video.isPrivate && canEdit ? "border-2 border-yellow-500" : "border-none"; + video.isPrivate && canEdit ? "watch-history-card--private" : ""; const timeAgo = window.app?.formatTimeAgo ? window.app.formatTimeAgo(video.created_at) : new Date(video.created_at * 1000).toLocaleString(); + const watchedAtTimestamp = toNumber( + video?.watchHistory?.watchedAt, + null + ); + let watchedAtLabel = "Watch date unavailable"; + if (Number.isFinite(watchedAtTimestamp) && watchedAtTimestamp > 0) { + const watchedDate = new Date(watchedAtTimestamp); + if (!Number.isNaN(watchedDate.getTime())) { + try { + watchedAtLabel = `Watched on ${watchedDate.toLocaleString(undefined, { + dateStyle: "medium", + timeStyle: "short", + })}`; + } catch (err) { + watchedAtLabel = `Watched on ${watchedDate.toLocaleString()}`; + } + } + } + let hasOlder = false; if (canEdit && video.videoRootId && window.app?.hasOlderVersion) { hasOlder = window.app.hasOlderVersion(video, fullAllEventsArray); @@ -203,7 +220,7 @@ function renderWatchHistoryGrid(videos, containerOrElement) { `; const cardControls = ` -