- IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
+ IPNS:
+
+ k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
+
diff --git a/src/ipns.html b/src/ipns.html
new file mode 100644
index 0000000..d378797
--- /dev/null
+++ b/src/ipns.html
@@ -0,0 +1,192 @@
+
+
+
+
+
+
bitvid | About
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading Content...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/js/app.js b/src/js/app.js
index e719f1a..0ac86bc 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -1,467 +1,481 @@
// js/app.js
-import { nostrClient } from './nostr.js';
-import { torrentClient } from './webtorrent.js';
-import { isDevMode } from './config.js';
+import { nostrClient } from "./nostr.js";
+import { torrentClient } from "./webtorrent.js";
+import { isDevMode } from "./config.js";
+import { disclaimerModal } from "./disclaimer.js";
class bitvidApp {
- constructor() {
- // Authentication Elements
- this.loginButton = document.getElementById('loginButton');
- this.logoutButton = document.getElementById('logoutButton');
- this.userStatus = document.getElementById('userStatus');
- this.userPubKey = document.getElementById('userPubKey');
+ constructor() {
+ // Authentication Elements
+ this.loginButton = document.getElementById("loginButton");
+ this.logoutButton = document.getElementById("logoutButton");
+ this.userStatus = document.getElementById("userStatus");
+ this.userPubKey = document.getElementById("userPubKey");
- // Form Elements
- this.submitForm = document.getElementById('submitForm');
- this.videoFormContainer = document.getElementById('videoFormContainer');
+ // Form Elements
+ this.submitForm = document.getElementById("submitForm");
+ this.videoFormContainer = document.getElementById("videoFormContainer");
- // Video List Element
- this.videoList = document.getElementById('videoList');
+ // Video List Element
+ this.videoList = document.getElementById("videoList");
- // Video Player Elements
- this.playerSection = document.getElementById('playerSection');
- this.videoElement = document.getElementById('video');
- this.status = document.getElementById('status');
- this.progressBar = document.getElementById('progress');
- this.peers = document.getElementById('peers');
- this.speed = document.getElementById('speed');
- this.downloaded = document.getElementById('downloaded');
+ // Video Player Elements
+ this.playerSection = document.getElementById("playerSection");
+ this.videoElement = document.getElementById("video");
+ this.status = document.getElementById("status");
+ this.progressBar = document.getElementById("progress");
+ this.peers = document.getElementById("peers");
+ this.speed = document.getElementById("speed");
+ this.downloaded = document.getElementById("downloaded");
- // Modal Elements
- this.playerModal = document.getElementById('playerModal');
- this.modalVideo = document.getElementById('modalVideo');
- this.modalStatus = document.getElementById('modalStatus');
- this.modalProgress = document.getElementById('modalProgress');
- this.modalPeers = document.getElementById('modalPeers');
- this.modalSpeed = document.getElementById('modalSpeed');
- this.modalDownloaded = document.getElementById('modalDownloaded');
- this.closePlayerBtn = document.getElementById('closePlayer');
+ // Modal Elements
+ this.playerModal = document.getElementById("playerModal");
+ this.modalVideo = document.getElementById("modalVideo");
+ this.modalStatus = document.getElementById("modalStatus");
+ this.modalProgress = document.getElementById("modalProgress");
+ this.modalPeers = document.getElementById("modalPeers");
+ this.modalSpeed = document.getElementById("modalSpeed");
+ this.modalDownloaded = document.getElementById("modalDownloaded");
+ this.closePlayerBtn = document.getElementById("closePlayer");
- // Video Info Elements
- this.videoTitle = document.getElementById('videoTitle');
- this.videoDescription = document.getElementById('videoDescription');
- this.videoTimestamp = document.getElementById('videoTimestamp');
+ // Video Info Elements
+ this.videoTitle = document.getElementById("videoTitle");
+ this.videoDescription = document.getElementById("videoDescription");
+ this.videoTimestamp = document.getElementById("videoTimestamp");
- // Creator Info Elements
- this.creatorAvatar = document.getElementById('creatorAvatar').querySelector('img');
- this.creatorName = document.getElementById('creatorName');
- this.creatorNpub = document.getElementById('creatorNpub');
+ // Creator Info Elements
+ this.creatorAvatar = document
+ .getElementById("creatorAvatar")
+ .querySelector("img");
+ this.creatorName = document.getElementById("creatorName");
+ this.creatorNpub = document.getElementById("creatorNpub");
- // Notification Containers
- this.errorContainer = document.getElementById('errorContainer');
- this.successContainer = document.getElementById('successContainer');
+ // Notification Containers
+ this.errorContainer = document.getElementById("errorContainer");
+ this.successContainer = document.getElementById("successContainer");
- this.pubkey = null;
- this.currentMagnetUri = null;
+ this.pubkey = null;
+ this.currentMagnetUri = null;
- // ADDED FOR VERSIONING/PRIVATE/DELETE:
- // If you created an
in your HTML form:
- this.isPrivateCheckbox = document.getElementById('isPrivate');
+ // ADDED FOR VERSIONING/PRIVATE/DELETE:
+ // If you created an
in your HTML form:
+ this.isPrivateCheckbox = document.getElementById("isPrivate");
+ }
+
+ /**
+ * Initializes the application by setting up the Nostr client and loading videos.
+ */
+ // app.js
+ async init() {
+ try {
+ // Hide and reset player states
+ this.playerSection.style.display = "none";
+ this.playerModal.style.display = "none";
+ this.currentMagnetUri = null;
+
+ // Initialize Nostr and check login
+ await nostrClient.init();
+ const savedPubKey = localStorage.getItem("userPubKey");
+ if (savedPubKey) {
+ this.login(savedPubKey, false);
+ }
+
+ this.setupEventListeners();
+ disclaimerModal.show(); // Add this line here
+ await this.loadVideos();
+ } catch (error) {
+ console.error("Init failed:", error);
+ this.showError("Failed to connect to Nostr relay");
+ }
+ }
+
+ /**
+ * Formats a timestamp into a "time ago" format.
+ */
+ formatTimeAgo(timestamp) {
+ const seconds = Math.floor(Date.now() / 1000 - timestamp);
+ const intervals = {
+ year: 31536000,
+ month: 2592000,
+ week: 604800,
+ day: 86400,
+ hour: 3600,
+ minute: 60,
+ };
+
+ for (const [unit, secondsInUnit] of Object.entries(intervals)) {
+ const interval = Math.floor(seconds / secondsInUnit);
+ if (interval >= 1) {
+ return `${interval} ${unit}${interval === 1 ? "" : "s"} ago`;
+ }
}
- /**
- * Initializes the application by setting up the Nostr client and loading videos.
- */
- // app.js
- async init() {
- try {
- // Hide and reset player states
- this.playerSection.style.display = 'none';
- this.playerModal.style.display = 'none';
- this.currentMagnetUri = null;
-
- // Initialize Nostr and check login
- await nostrClient.init();
- const savedPubKey = localStorage.getItem('userPubKey');
- if (savedPubKey) {
- this.login(savedPubKey, false);
- }
+ return "just now";
+ }
- this.setupEventListeners();
- await this.loadVideos();
- } catch (error) {
- console.error('Init failed:', error);
- this.showError('Failed to connect to Nostr relay');
- }
+ /**
+ * Sets up event listeners for various UI interactions.
+ */
+ setupEventListeners() {
+ // Login Button
+ this.loginButton.addEventListener("click", async () => {
+ try {
+ const pubkey = await nostrClient.login();
+ this.login(pubkey, true);
+ } catch (error) {
+ this.log("Login failed:", error);
+ this.showError("Failed to login. Please try again.");
+ }
+ });
+
+ // Logout Button
+ this.logoutButton.addEventListener("click", () => {
+ this.logout();
+ });
+
+ // Form submission
+ this.submitForm.addEventListener("submit", (e) => this.handleSubmit(e));
+
+ // Close Modal Button
+ if (this.closePlayerBtn) {
+ this.closePlayerBtn.addEventListener("click", async () => {
+ await this.hideModal();
+ });
}
- /**
- * Formats a timestamp into a "time ago" format.
- */
- formatTimeAgo(timestamp) {
- const seconds = Math.floor((Date.now() / 1000) - timestamp);
- const intervals = {
- year: 31536000,
- month: 2592000,
- week: 604800,
- day: 86400,
- hour: 3600,
- minute: 60
- };
-
- for (const [unit, secondsInUnit] of Object.entries(intervals)) {
- const interval = Math.floor(seconds / secondsInUnit);
- if (interval >= 1) {
- return `${interval} ${unit}${interval === 1 ? '' : 's'} ago`;
- }
+ // Close Modal by clicking outside content
+ if (this.playerModal) {
+ this.playerModal.addEventListener("click", async (e) => {
+ if (e.target === this.playerModal) {
+ await this.hideModal();
}
-
- return 'just now';
+ });
}
- /**
- * Sets up event listeners for various UI interactions.
- */
- setupEventListeners() {
- // Login Button
- this.loginButton.addEventListener('click', async () => {
- try {
- const pubkey = await nostrClient.login();
- this.login(pubkey, true);
- } catch (error) {
- this.log('Login failed:', error);
- this.showError('Failed to login. Please try again.');
- }
- });
+ // Video error handling
+ this.videoElement.addEventListener("error", (e) => {
+ const error = e.target.error;
+ this.log("Video error:", error);
+ if (error) {
+ this.showError(
+ `Video playback error: ${error.message || "Unknown error"}`
+ );
+ }
+ });
- // Logout Button
- this.logoutButton.addEventListener('click', () => {
- this.logout();
- });
-
- // Form submission
- this.submitForm.addEventListener('submit', (e) => this.handleSubmit(e));
-
- // Close Modal Button
- if (this.closePlayerBtn) {
- this.closePlayerBtn.addEventListener('click', async () => {
- await this.hideModal();
- });
+ // Detailed Modal Video Event Listeners
+ if (this.modalVideo) {
+ this.modalVideo.addEventListener("error", (e) => {
+ const error = e.target.error;
+ this.log("Modal video error:", error);
+ if (error) {
+ this.log("Error code:", error.code);
+ this.log("Error message:", error.message);
+ this.showError(
+ `Video playback error: ${error.message || "Unknown error"}`
+ );
}
+ });
- // Close Modal by clicking outside content
- if (this.playerModal) {
- this.playerModal.addEventListener('click', async (e) => {
- if (e.target === this.playerModal) {
- await this.hideModal();
- }
- });
- }
+ this.modalVideo.addEventListener("loadstart", () => {
+ this.log("Video loadstart event fired");
+ });
- // Video error handling
- this.videoElement.addEventListener('error', (e) => {
- const error = e.target.error;
- this.log('Video error:', error);
- if (error) {
- this.showError(`Video playback error: ${error.message || 'Unknown error'}`);
- }
- });
+ this.modalVideo.addEventListener("loadedmetadata", () => {
+ this.log("Video loadedmetadata event fired");
+ });
- // Detailed Modal Video Event Listeners
- if (this.modalVideo) {
- this.modalVideo.addEventListener('error', (e) => {
- const error = e.target.error;
- this.log('Modal video error:', error);
- if (error) {
- this.log('Error code:', error.code);
- this.log('Error message:', error.message);
- this.showError(`Video playback error: ${error.message || 'Unknown error'}`);
- }
- });
-
- this.modalVideo.addEventListener('loadstart', () => {
- this.log('Video loadstart event fired');
- });
-
- this.modalVideo.addEventListener('loadedmetadata', () => {
- this.log('Video loadedmetadata event fired');
- });
-
- this.modalVideo.addEventListener('canplay', () => {
- this.log('Video canplay event fired');
- });
- }
-
- // Cleanup on page unload
- window.addEventListener('beforeunload', async () => {
- await this.cleanup();
- });
+ this.modalVideo.addEventListener("canplay", () => {
+ this.log("Video canplay event fired");
+ });
}
- /**
- * Handles user login.
- */
- login(pubkey, saveToStorage = true) {
- this.pubkey = pubkey;
- this.loginButton.classList.add('hidden');
- this.logoutButton.classList.remove('hidden');
- this.userStatus.classList.remove('hidden');
- this.userPubKey.textContent = pubkey;
- this.videoFormContainer.classList.remove('hidden');
- this.log(`User logged in as: ${pubkey}`);
+ // Cleanup on page unload
+ window.addEventListener("beforeunload", async () => {
+ await this.cleanup();
+ });
+ }
- if (saveToStorage) {
- localStorage.setItem('userPubKey', pubkey);
+ /**
+ * Handles user login.
+ */
+ login(pubkey, saveToStorage = true) {
+ this.pubkey = pubkey;
+ this.loginButton.classList.add("hidden");
+ this.logoutButton.classList.remove("hidden");
+ this.userStatus.classList.remove("hidden");
+ this.userPubKey.textContent = pubkey;
+ this.videoFormContainer.classList.remove("hidden");
+ this.log(`User logged in as: ${pubkey}`);
+
+ if (saveToStorage) {
+ localStorage.setItem("userPubKey", pubkey);
+ }
+ }
+
+ /**
+ * Handles user logout.
+ */
+ logout() {
+ nostrClient.logout();
+ this.pubkey = null;
+ this.loginButton.classList.remove("hidden");
+ this.logoutButton.classList.add("hidden");
+ this.userStatus.classList.add("hidden");
+ this.userPubKey.textContent = "";
+ this.videoFormContainer.classList.add("hidden");
+ localStorage.removeItem("userPubKey");
+ this.log("User logged out.");
+ }
+
+ /**
+ * Cleans up video player and torrents.
+ */
+ async cleanup() {
+ try {
+ if (this.videoElement) {
+ this.videoElement.pause();
+ this.videoElement.src = "";
+ this.videoElement.load();
+ }
+ if (this.modalVideo) {
+ this.modalVideo.pause();
+ this.modalVideo.src = "";
+ this.modalVideo.load();
+ }
+ await torrentClient.cleanup();
+ } catch (error) {
+ this.log("Cleanup error:", error);
+ }
+ }
+
+ /**
+ * Hides the video player section.
+ */
+ async hideVideoPlayer() {
+ await this.cleanup();
+ this.playerSection.classList.add("hidden");
+ }
+
+ /**
+ * Hides the video modal.
+ */
+ async hideModal() {
+ await this.cleanup();
+ this.playerModal.style.display = "none";
+ this.playerModal.classList.add("hidden");
+ }
+
+ /**
+ * Handles video submission (with version, private listing).
+ */
+ async handleSubmit(e) {
+ e.preventDefault();
+
+ if (!this.pubkey) {
+ this.showError("Please login to post a video.");
+ return;
+ }
+
+ const descriptionElement = document.getElementById("description");
+
+ // ADDED FOR VERSIONING/PRIVATE/DELETE:
+ // If you have a checkbox with id="isPrivate" in HTML
+ const isPrivate = this.isPrivateCheckbox
+ ? this.isPrivateCheckbox.checked
+ : false;
+
+ const formData = {
+ version: 2, // We set the version to 2 for new posts
+ title: document.getElementById("title")?.value.trim() || "",
+ magnet: document.getElementById("magnet")?.value.trim() || "",
+ thumbnail: document.getElementById("thumbnail")?.value.trim() || "",
+ description: descriptionElement?.value.trim() || "",
+ mode: isDevMode ? "dev" : "live",
+ isPrivate, // new field to handle private listings
+ };
+
+ this.log("Form Data Collected:", formData);
+
+ if (!formData.title || !formData.magnet) {
+ this.showError("Title and Magnet URI are required.");
+ return;
+ }
+
+ try {
+ await nostrClient.publishVideo(formData, this.pubkey);
+ this.submitForm.reset();
+
+ // If the private checkbox was checked, reset it
+ if (this.isPrivateCheckbox) {
+ this.isPrivateCheckbox.checked = false;
+ }
+
+ await this.loadVideos();
+ this.showSuccess("Video shared successfully!");
+ } catch (error) {
+ this.log("Failed to publish video:", error.message);
+ this.showError("Failed to share video. Please try again later.");
+ }
+ }
+
+ /**
+ * Loads and displays videos from Nostr.
+ */
+ async loadVideos() {
+ try {
+ const videos = await nostrClient.fetchVideos();
+ this.log("Fetched videos (raw):", videos);
+
+ if (!videos) {
+ this.log("No videos received");
+ throw new Error("No videos received from relays");
+ }
+
+ // Convert to array if not already
+ const videosArray = Array.isArray(videos) ? videos : [videos];
+
+ // **Filter** so we only show:
+ // - isPrivate === false (public videos)
+ // - or isPrivate === true but pubkey === this.pubkey
+ const displayedVideos = videosArray.filter((video) => {
+ if (!video.isPrivate) {
+ // Public video => show it
+ return true;
}
- }
+ // Else it's private; only show if it's owned by the logged-in user
+ return this.pubkey && video.pubkey === this.pubkey;
+ });
- /**
- * Handles user logout.
- */
- logout() {
- nostrClient.logout();
- this.pubkey = null;
- this.loginButton.classList.remove('hidden');
- this.logoutButton.classList.add('hidden');
- this.userStatus.classList.add('hidden');
- this.userPubKey.textContent = '';
- this.videoFormContainer.classList.add('hidden');
- localStorage.removeItem('userPubKey');
- this.log('User logged out.');
- }
-
- /**
- * Cleans up video player and torrents.
- */
- async cleanup() {
- try {
- if (this.videoElement) {
- this.videoElement.pause();
- this.videoElement.src = '';
- this.videoElement.load();
- }
- if (this.modalVideo) {
- this.modalVideo.pause();
- this.modalVideo.src = '';
- this.modalVideo.load();
- }
- await torrentClient.cleanup();
- } catch (error) {
- this.log('Cleanup error:', error);
- }
- }
-
- /**
- * Hides the video player section.
- */
- async hideVideoPlayer() {
- await this.cleanup();
- this.playerSection.classList.add('hidden');
- }
-
- /**
- * Hides the video modal.
- */
- async hideModal() {
- await this.cleanup();
- this.playerModal.style.display = 'none';
- this.playerModal.classList.add('hidden');
- }
-
- /**
- * Handles video submission (with version, private listing).
- */
- async handleSubmit(e) {
- e.preventDefault();
-
- if (!this.pubkey) {
- this.showError('Please login to post a video.');
- return;
- }
-
- const descriptionElement = document.getElementById('description');
-
- // ADDED FOR VERSIONING/PRIVATE/DELETE:
- // If you have a checkbox with id="isPrivate" in HTML
- const isPrivate = this.isPrivateCheckbox
- ? this.isPrivateCheckbox.checked
- : false;
-
- const formData = {
- version: 2, // We set the version to 2 for new posts
- title: document.getElementById('title')?.value.trim() || '',
- magnet: document.getElementById('magnet')?.value.trim() || '',
- thumbnail: document.getElementById('thumbnail')?.value.trim() || '',
- description: descriptionElement?.value.trim() || '',
- mode: isDevMode ? 'dev' : 'live',
- isPrivate // new field to handle private listings
- };
-
- this.log('Form Data Collected:', formData);
-
- if (!formData.title || !formData.magnet) {
- this.showError('Title and Magnet URI are required.');
- return;
- }
-
- try {
- await nostrClient.publishVideo(formData, this.pubkey);
- this.submitForm.reset();
-
- // If the private checkbox was checked, reset it
- if (this.isPrivateCheckbox) {
- this.isPrivateCheckbox.checked = false;
- }
-
- await this.loadVideos();
- this.showSuccess('Video shared successfully!');
- } catch (error) {
- this.log('Failed to publish video:', error.message);
- this.showError('Failed to share video. Please try again later.');
- }
- }
-
- /**
- * Loads and displays videos from Nostr.
- */
- async loadVideos() {
- try {
- const videos = await nostrClient.fetchVideos();
- this.log('Fetched videos (raw):', videos);
-
- if (!videos) {
- this.log('No videos received');
- throw new Error('No videos received from relays');
- }
-
- // Convert to array if not already
- const videosArray = Array.isArray(videos) ? videos : [videos];
-
- // **Filter** so we only show:
- // - isPrivate === false (public videos)
- // - or isPrivate === true but pubkey === this.pubkey
- const displayedVideos = videosArray.filter(video => {
- if (!video.isPrivate) {
- // Public video => show it
- return true;
- }
- // Else it's private; only show if it's owned by the logged-in user
- return (this.pubkey && video.pubkey === this.pubkey);
- });
-
- if (displayedVideos.length === 0) {
- this.log('No valid videos found after filtering.');
- this.videoList.innerHTML = `
+ if (displayedVideos.length === 0) {
+ this.log("No valid videos found after filtering.");
+ this.videoList.innerHTML = `
No public videos available yet. Be the first to upload one!
`;
- return;
- }
+ return;
+ }
- this.log('Processing filtered videos:', displayedVideos);
+ this.log("Processing filtered videos:", displayedVideos);
- displayedVideos.forEach((video, index) => {
- this.log(`Video ${index} details:`, {
- id: video.id,
- title: video.title,
- magnet: video.magnet,
- isPrivate: video.isPrivate,
- pubkey: video.pubkey,
- created_at: video.created_at
- });
- });
+ displayedVideos.forEach((video, index) => {
+ this.log(`Video ${index} details:`, {
+ id: video.id,
+ title: video.title,
+ magnet: video.magnet,
+ isPrivate: video.isPrivate,
+ pubkey: video.pubkey,
+ created_at: video.created_at,
+ });
+ });
- // Now render only the displayedVideos
- this.renderVideoList(displayedVideos);
- this.log(`Rendered ${displayedVideos.length} videos successfully`);
- } catch (error) {
- this.log('Failed to fetch videos:', error);
- this.showError('An error occurred while loading videos. Please try again later.');
- this.videoList.innerHTML = `
+ // Now render only the displayedVideos
+ this.renderVideoList(displayedVideos);
+ this.log(`Rendered ${displayedVideos.length} videos successfully`);
+ } catch (error) {
+ this.log("Failed to fetch videos:", error);
+ this.showError(
+ "An error occurred while loading videos. Please try again later."
+ );
+ this.videoList.innerHTML = `
No videos available at the moment. Please try again later.
`;
- }
}
+ }
- /**
- * Renders the given list of videos. If a video is private and belongs to the user,
- * highlight with a special border (e.g. border-yellow-500).
- */
- async renderVideoList(videos) {
+ /**
+ * Renders the given list of videos. If a video is private and belongs to the user,
+ * highlight with a special border (e.g. border-yellow-500).
+ */
+ async renderVideoList(videos) {
+ try {
+ console.log("RENDER VIDEO LIST - Start", {
+ videosReceived: videos,
+ videosCount: videos ? videos.length : "N/A",
+ videosType: typeof videos,
+ });
+
+ if (!videos) {
+ console.error("NO VIDEOS RECEIVED");
+ this.videoList.innerHTML = `
No videos found.
`;
+ return;
+ }
+
+ const videoArray = Array.isArray(videos) ? videos : [videos];
+
+ if (videoArray.length === 0) {
+ console.error("VIDEO ARRAY IS EMPTY");
+ this.videoList.innerHTML = `
No videos available.
`;
+ return;
+ }
+
+ // Sort by creation date
+ videoArray.sort((a, b) => b.created_at - a.created_at);
+
+ // Prepare to fetch user profiles
+ const userProfiles = new Map();
+ const uniquePubkeys = [...new Set(videoArray.map((v) => v.pubkey))];
+
+ for (const pubkey of uniquePubkeys) {
try {
- console.log('RENDER VIDEO LIST - Start', {
- videosReceived: videos,
- videosCount: videos ? videos.length : 'N/A',
- videosType: typeof videos
+ const userEvents = await nostrClient.pool.list(nostrClient.relays, [
+ {
+ kinds: [0],
+ authors: [pubkey],
+ limit: 1,
+ },
+ ]);
+
+ if (userEvents[0]?.content) {
+ const profile = JSON.parse(userEvents[0].content);
+ userProfiles.set(pubkey, {
+ name: profile.name || profile.display_name || "Unknown",
+ picture: profile.picture || `https://robohash.org/${pubkey}`,
});
-
- if (!videos) {
- console.error('NO VIDEOS RECEIVED');
- this.videoList.innerHTML = `
No videos found.
`;
- return;
- }
-
- const videoArray = Array.isArray(videos) ? videos : [videos];
-
- if (videoArray.length === 0) {
- console.error('VIDEO ARRAY IS EMPTY');
- this.videoList.innerHTML = `
No videos available.
`;
- return;
- }
-
- // Sort by creation date
- videoArray.sort((a, b) => b.created_at - a.created_at);
-
- // Prepare to fetch user profiles
- const userProfiles = new Map();
- const uniquePubkeys = [...new Set(videoArray.map(v => v.pubkey))];
-
- for (const pubkey of uniquePubkeys) {
- try {
- const userEvents = await nostrClient.pool.list(nostrClient.relays, [{
- kinds: [0],
- authors: [pubkey],
- limit: 1
- }]);
-
- if (userEvents[0]?.content) {
- const profile = JSON.parse(userEvents[0].content);
- userProfiles.set(pubkey, {
- name: profile.name || profile.display_name || 'Unknown',
- picture: profile.picture || `https://robohash.org/${pubkey}`
- });
- } else {
- userProfiles.set(pubkey, {
- name: 'Unknown',
- picture: `https://robohash.org/${pubkey}`
- });
- }
- } catch (error) {
- console.error(`Profile fetch error for ${pubkey}:`, error);
- userProfiles.set(pubkey, {
- name: 'Unknown',
- picture: `https://robohash.org/${pubkey}`
- });
- }
- }
-
- // Build HTML for each video
- const renderedVideos = videoArray.map((video, index) => {
- try {
- if (!this.validateVideo(video, index)) {
- console.error(`Invalid video: ${video.title}`);
- return '';
- }
-
- const profile = userProfiles.get(video.pubkey) || {
- name: 'Unknown',
- picture: `https://robohash.org/${video.pubkey}`
- };
- const timeAgo = this.formatTimeAgo(video.created_at);
-
- // If user is the owner
- const canEdit = (video.pubkey === this.pubkey);
+ } else {
+ userProfiles.set(pubkey, {
+ name: "Unknown",
+ picture: `https://robohash.org/${pubkey}`,
+ });
+ }
+ } catch (error) {
+ console.error(`Profile fetch error for ${pubkey}:`, error);
+ userProfiles.set(pubkey, {
+ name: "Unknown",
+ picture: `https://robohash.org/${pubkey}`,
+ });
+ }
+ }
- // If it's private + user owns it => highlight with a special border
- const highlightClass = (video.isPrivate && canEdit)
- ? 'border-2 border-yellow-500'
- : 'border-none'; // normal case
+ // Build HTML for each video
+ const renderedVideos = videoArray
+ .map((video, index) => {
+ try {
+ if (!this.validateVideo(video, index)) {
+ console.error(`Invalid video: ${video.title}`);
+ return "";
+ }
- // Gear menu (unchanged)
- const gearMenu = canEdit
- ? `
+ const profile = userProfiles.get(video.pubkey) || {
+ name: "Unknown",
+ picture: `https://robohash.org/${video.pubkey}`,
+ };
+ const timeAgo = this.formatTimeAgo(video.created_at);
+
+ // If user is the owner
+ const canEdit = video.pubkey === this.pubkey;
+
+ // If it's private + user owns it => highlight with a special border
+ const highlightClass =
+ video.isPrivate && canEdit
+ ? "border-2 border-yellow-500"
+ : "border-none"; // normal case
+
+ // Gear menu (unchanged)
+ const gearMenu = canEdit
+ ? `
`
- : '';
-
- return `
+ : "";
+
+ return `
${
video.thumbnail
- ? `
})
`
- : `
+ : `