From c99f6834cde62a571a82f89fc83d870d051af3a9 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Wed, 8 Oct 2025 20:45:59 -0400
Subject: [PATCH] Show posted and edited times from root history
---
components/video-modal.html | 10 +-
js/app.js | 198 ++++++++++++++++++++++++++++++++-
js/ui/components/VideoModal.js | 46 ++++++--
3 files changed, 242 insertions(+), 12 deletions(-)
diff --git a/components/video-modal.html b/components/video-modal.html
index 3045c03f..741289ff 100644
--- a/components/video-modal.html
+++ b/components/video-modal.html
@@ -237,10 +237,14 @@
-
-
just now
+
+ Posted just now
+ Last edited just now
– views
diff --git a/js/app.js b/js/app.js
index 08d52500..17fc5fe2 100644
--- a/js/app.js
+++ b/js/app.js
@@ -6226,6 +6226,11 @@ class Application {
hasUrl: !!trimmedUrl,
});
+ const knownPostedAt = this.getKnownVideoPostedAt(video);
+ const normalizedEditedAt = Number.isFinite(video.created_at)
+ ? Math.floor(video.created_at)
+ : null;
+
this.currentVideo = {
...video,
url: trimmedUrl,
@@ -6235,8 +6240,15 @@ class Application {
torrentSupported: magnetSupported,
legacyInfoHash: video.legacyInfoHash || legacyInfoHash,
lightningAddress: null,
+ lastEditedAt: normalizedEditedAt,
};
+ if (Number.isFinite(knownPostedAt)) {
+ this.cacheVideoRootCreatedAt(this.currentVideo, knownPostedAt);
+ } else if (this.currentVideo.rootCreatedAt) {
+ delete this.currentVideo.rootCreatedAt;
+ }
+
const dTagValue = (this.extractDTagValue(video.tags) || "").trim();
const pointerInfo = resolveVideoPointer({
kind: video.kind,
@@ -6320,12 +6332,15 @@ class Application {
const creatorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
if (this.videoModal) {
- const formattedTimestamp = this.formatTimeAgo(video.created_at);
+ const timestampPayload = this.buildModalTimestampPayload({
+ postedAt: this.currentVideo?.rootCreatedAt ?? null,
+ editedAt: normalizedEditedAt,
+ });
const displayNpub = `${creatorNpub.slice(0, 8)}...${creatorNpub.slice(-4)}`;
this.videoModal.updateMetadata({
title: video.title || "Untitled",
description: video.description || "No description available.",
- timestamp: formattedTimestamp,
+ timestamps: timestampPayload,
creator: {
name: creatorProfile.name,
avatarUrl: creatorProfile.picture,
@@ -6334,6 +6349,8 @@ class Application {
});
}
+ this.ensureModalPostedTimestamp(this.currentVideo);
+
const playbackResult =
playbackPromise && typeof playbackPromise.then === "function"
? await playbackPromise
@@ -6342,6 +6359,183 @@ class Application {
return playbackResult;
}
+ buildModalTimestampPayload({ postedAt = null, editedAt = null } = {}) {
+ const normalizedPostedAt = Number.isFinite(postedAt)
+ ? Math.floor(postedAt)
+ : null;
+ const normalizedEditedAt = Number.isFinite(editedAt)
+ ? Math.floor(editedAt)
+ : null;
+
+ const payload = {
+ posted: "",
+ edited: "",
+ };
+
+ const effectivePostedAt =
+ normalizedPostedAt !== null ? normalizedPostedAt : normalizedEditedAt;
+
+ if (effectivePostedAt !== null) {
+ payload.posted = `Posted ${this.formatTimeAgo(effectivePostedAt)}`;
+ }
+
+ const shouldShowEdited =
+ normalizedEditedAt !== null &&
+ (normalizedPostedAt === null || normalizedEditedAt - normalizedPostedAt >= 60);
+
+ if (shouldShowEdited) {
+ payload.edited = `Last edited ${this.formatTimeAgo(normalizedEditedAt)}`;
+ }
+
+ return payload;
+ }
+
+ getKnownVideoPostedAt(video) {
+ if (!video || typeof video !== "object") {
+ return null;
+ }
+
+ const directValue = Number.isFinite(video.rootCreatedAt)
+ ? Math.floor(video.rootCreatedAt)
+ : null;
+ if (directValue !== null) {
+ return directValue;
+ }
+
+ if (video.id && this.videosMap instanceof Map) {
+ const stored = this.videosMap.get(video.id);
+ const storedValue = Number.isFinite(stored?.rootCreatedAt)
+ ? Math.floor(stored.rootCreatedAt)
+ : null;
+ if (storedValue !== null) {
+ video.rootCreatedAt = storedValue;
+ return storedValue;
+ }
+ }
+
+ const nip71Created = Number.isFinite(video?.nip71Source?.created_at)
+ ? Math.floor(video.nip71Source.created_at)
+ : null;
+
+ if (nip71Created !== null) {
+ return nip71Created;
+ }
+
+ return null;
+ }
+
+ cacheVideoRootCreatedAt(video, timestamp) {
+ if (!Number.isFinite(timestamp)) {
+ return;
+ }
+
+ const normalized = Math.floor(timestamp);
+
+ if (video && typeof video === "object") {
+ video.rootCreatedAt = normalized;
+ }
+
+ if (video?.id && this.videosMap instanceof Map) {
+ const existing = this.videosMap.get(video.id);
+ if (existing && typeof existing === "object") {
+ existing.rootCreatedAt = normalized;
+ }
+ }
+ }
+
+ async resolveVideoPostedAt(video) {
+ if (!video || typeof video !== "object") {
+ return null;
+ }
+
+ const cached = this.getKnownVideoPostedAt(video);
+ if (cached !== null) {
+ return cached;
+ }
+
+ if (!nostrClient || typeof nostrClient.hydrateVideoHistory !== "function") {
+ const fallback = Number.isFinite(video.created_at)
+ ? Math.floor(video.created_at)
+ : null;
+ if (fallback !== null) {
+ this.cacheVideoRootCreatedAt(video, fallback);
+ }
+ return fallback;
+ }
+
+ try {
+ const history = await nostrClient.hydrateVideoHistory(video);
+ if (Array.isArray(history) && history.length) {
+ let earliest = null;
+ for (const entry of history) {
+ if (!entry || entry.deleted) {
+ continue;
+ }
+ const created = Number.isFinite(entry.created_at)
+ ? Math.floor(entry.created_at)
+ : null;
+ if (created === null) {
+ continue;
+ }
+ if (earliest === null || created < earliest) {
+ earliest = created;
+ }
+ }
+
+ if (earliest === null) {
+ const lastEntry = history[history.length - 1];
+ if (Number.isFinite(lastEntry?.created_at)) {
+ earliest = Math.floor(lastEntry.created_at);
+ }
+ }
+
+ if (earliest !== null) {
+ this.cacheVideoRootCreatedAt(video, earliest);
+ return earliest;
+ }
+ }
+ } catch (error) {
+ if (isDevMode) {
+ console.warn(
+ "[Application] Failed to hydrate video history for timestamps:",
+ error
+ );
+ }
+ }
+
+ const fallback = Number.isFinite(video.created_at)
+ ? Math.floor(video.created_at)
+ : null;
+ if (fallback !== null) {
+ this.cacheVideoRootCreatedAt(video, fallback);
+ }
+ return fallback;
+ }
+
+ async ensureModalPostedTimestamp(video) {
+ if (!video || !this.videoModal) {
+ return;
+ }
+
+ const postedAt = await this.resolveVideoPostedAt(video);
+ if (!this.videoModal || this.currentVideo !== video) {
+ return;
+ }
+
+ const editedAt = Number.isFinite(video.lastEditedAt)
+ ? Math.floor(video.lastEditedAt)
+ : Number.isFinite(video.created_at)
+ ? Math.floor(video.created_at)
+ : null;
+
+ const payload = this.buildModalTimestampPayload({
+ postedAt,
+ editedAt,
+ });
+
+ this.videoModal.updateMetadata({ timestamps: payload });
+ }
+
async playVideoWithoutEvent({
url = "",
magnet = "",
diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js
index 81b8903a..5e7197f0 100644
--- a/js/ui/components/VideoModal.js
+++ b/js/ui/components/VideoModal.js
@@ -35,6 +35,7 @@ export class VideoModal {
this.videoTitle = null;
this.videoDescription = null;
this.videoTimestamp = null;
+ this.videoEditedTimestamp = null;
this.videoViewCountEl = null;
this.creatorAvatar = null;
this.creatorName = null;
@@ -185,6 +186,8 @@ export class VideoModal {
playerModal.querySelector("#videoDescription") || null;
this.videoTimestamp =
playerModal.querySelector("#videoTimestamp") || null;
+ this.videoEditedTimestamp =
+ playerModal.querySelector("#videoEditedTimestamp") || null;
this.videoViewCountEl =
playerModal.querySelector("#videoViewCount") || null;
this.creatorAvatar =
@@ -933,26 +936,55 @@ export class VideoModal {
title,
description,
timestamp,
+ timestamps,
viewCount,
creator,
} = {}) {
- if (this.videoTitle) {
+ if (this.videoTitle && title !== undefined) {
this.videoTitle.textContent = title || "Untitled";
}
- if (this.videoDescription) {
+ if (this.videoDescription && description !== undefined) {
this.videoDescription.textContent = description || "";
}
- if (this.videoTimestamp) {
- this.videoTimestamp.textContent = timestamp || "";
+ if (timestamps) {
+ this.updateTimestamps(timestamps);
+ } else if (timestamp !== undefined) {
+ this.updateTimestamps({ posted: timestamp });
}
- if (typeof viewCount === "string") {
- this.updateViewCountLabel(viewCount);
+ if (this.videoViewCountEl && viewCount !== undefined) {
+ if (typeof viewCount === "string") {
+ this.updateViewCountLabel(viewCount);
+ } else {
+ this.updateViewCountLabel("");
+ }
}
- if (creator) {
+ if (creator !== undefined) {
this.updateCreator(creator);
}
}
+ updateTimestamps({ posted, edited } = {}) {
+ if (this.videoTimestamp) {
+ if (posted) {
+ this.videoTimestamp.textContent = posted;
+ this.videoTimestamp.classList.remove("hidden");
+ } else {
+ this.videoTimestamp.textContent = "";
+ this.videoTimestamp.classList.add("hidden");
+ }
+ }
+
+ if (this.videoEditedTimestamp) {
+ if (edited) {
+ this.videoEditedTimestamp.textContent = edited;
+ this.videoEditedTimestamp.classList.remove("hidden");
+ } else {
+ this.videoEditedTimestamp.textContent = "";
+ this.videoEditedTimestamp.classList.add("hidden");
+ }
+ }
+ }
+
updateCreator({ name, avatarUrl, npub } = {}) {
if (this.creatorName) {
this.creatorName.textContent = name || "Unknown";