From 8a0c68d15592de9131b4c13441721c5fb71c2d6f Mon Sep 17 00:00:00 2001
From: Keep Creating Online <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:34:54 -0500
Subject: [PATCH] update
---
src/js/app.js | 86 ++++++++++++++++++++++++++++++++++++++--
src/js/nostr.js | 102 +++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 170 insertions(+), 18 deletions(-)
diff --git a/src/js/app.js b/src/js/app.js
index f1898d2..340a66f 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -279,7 +279,7 @@ class NosTubeApp {
magnet: document.getElementById('magnet') ? document.getElementById('magnet').value.trim() : '',
thumbnail: document.getElementById('thumbnail') ? document.getElementById('thumbnail').value.trim() : '',
description: descriptionElement ? descriptionElement.value.trim() : '',
- mode: isDevMode ? 'dev' : 'live',
+ mode: isDevMode ? 'dev' : 'live'
};
// Debugging Log: Check formData
@@ -430,6 +430,16 @@ class NosTubeApp {
};
const timeAgo = this.formatTimeAgo(video.created_at);
+ // Only show "Edit" button if this user owns the video (video.pubkey === this.pubkey)
+ const canEdit = (video.pubkey === this.pubkey);
+ const editButton = canEdit
+ ? ``
+ : '';
+
return `
-
${this.escapeHTML(video.title)}
-
+
})
@@ -467,6 +477,7 @@ class NosTubeApp {
+ ${editButton}
`;
@@ -708,6 +719,75 @@ class NosTubeApp {
setTimeout(() => this.updateTorrentStatus(torrent), 1000);
}
}
+
+ /**
+ * Allows the user to edit a video note (only if they are the owner).
+ * We reuse the note's existing d tag via nostrClient.editVideo.
+ * @param {number} index - The index of the video in the rendered list
+ */
+ async handleEditVideo(index) {
+ try {
+ const videos = await nostrClient.fetchVideos();
+ const video = videos[index];
+
+ if (!this.pubkey) {
+ this.showError('Please login to edit videos.');
+ return;
+ }
+ if (video.pubkey !== this.pubkey) {
+ this.showError('You do not own this video.');
+ return;
+ }
+
+ // Prompt for new fields, but leave old value if user cancels or leaves blank.
+ const newTitle = prompt('New Title? (Leave blank to keep existing)', video.title);
+ const newMagnet = prompt('New Magnet Link? (Leave blank to keep existing)', video.magnet);
+ const newThumbnail = prompt('New Thumbnail URL? (Leave blank to keep existing)', video.thumbnail);
+ const newDescription = prompt('New Description? (Leave blank to keep existing)', video.description);
+
+ // If user cancels ANY prompt, it returns `null`.
+ // If user typed nothing and clicked OK, it’s an empty string ''.
+ // So we do checks to keep the old value if needed:
+ const title = (newTitle === null || newTitle.trim() === '')
+ ? video.title
+ : newTitle.trim();
+
+ const magnet = (newMagnet === null || newMagnet.trim() === '')
+ ? video.magnet
+ : newMagnet.trim();
+
+ const thumbnail = (newThumbnail === null || newThumbnail.trim() === '')
+ ? video.thumbnail
+ : newThumbnail.trim();
+
+ const description = (newDescription === null || newDescription.trim() === '')
+ ? video.description
+ : newDescription.trim();
+
+ // Build updated data
+ const updatedData = {
+ title,
+ magnet,
+ thumbnail,
+ description,
+ mode: isDevMode ? 'dev' : 'live'
+ };
+
+ const originalEvent = {
+ id: video.id,
+ pubkey: video.pubkey,
+ tags: video.tags // Must include ["d","someValue"] to reuse the same note
+ };
+
+ await nostrClient.editVideo(originalEvent, updatedData, this.pubkey);
+ this.showSuccess('Video updated successfully!');
+ await this.loadVideos();
+ } catch (err) {
+ this.log('Failed to edit video:', err.message);
+ this.showError('Failed to edit video. Please try again later.');
+ }
+ }
+
}
export const app = new NosTubeApp();
diff --git a/src/js/nostr.js b/src/js/nostr.js
index 5d02761..2501a8b 100644
--- a/src/js/nostr.js
+++ b/src/js/nostr.js
@@ -143,7 +143,7 @@ class NostrClient {
}
/**
- * Publishes a new video event to all relays.
+ * Publishes a new video event to all relays (creates a new note).
*/
async publishVideo(videoData, pubkey) {
if (!pubkey) {
@@ -212,6 +212,81 @@ class NostrClient {
throw new Error('Failed to sign event.');
}
}
+
+ /**
+ * Edits an existing video event by reusing its "d" tag.
+ * @param {Object} originalEvent - The entire event object you're editing.
+ * @param {Object} updatedVideoData - The updated fields (title, magnet, etc.).
+ * @param {string} pubkey - The user's pubkey (must match originalEvent.pubkey).
+ */
+ async editVideo(originalEvent, updatedVideoData, pubkey) {
+ if (!pubkey) {
+ throw new Error('User is not logged in.');
+ }
+ if (originalEvent.pubkey !== pubkey) {
+ throw new Error('You do not own this event (different pubkey).');
+ }
+
+ // Debugging log
+ if (isDevMode) {
+ console.log('Editing video event:', originalEvent);
+ console.log('New video data:', updatedVideoData);
+ }
+
+ // Grab the d tag from the original event
+ const dTag = originalEvent.tags.find(tag => tag[0] === 'd');
+ if (!dTag) {
+ throw new Error('This event has no "d" tag, cannot edit as addressable kind=30078.');
+ }
+ const existingD = dTag[1];
+
+ // Build the updated event with the same (kind, pubkey, d) so relays see it as an update
+ const event = {
+ kind: 30078,
+ pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [
+ ['t', 'video'],
+ ['d', existingD]
+ ],
+ content: JSON.stringify(updatedVideoData)
+ };
+
+ if (isDevMode) {
+ console.log('Reusing d tag:', existingD);
+ console.log('Updated event content:', event.content);
+ }
+
+ try {
+ // Sign the new updated event
+ const signedEvent = await window.nostr.signEvent(event);
+
+ if (isDevMode) {
+ console.log('Signed edited event:', signedEvent);
+ }
+
+ // Publish the edited event to all relays
+ await Promise.all(this.relays.map(async url => {
+ try {
+ await this.pool.publish([url], signedEvent);
+ if (isDevMode) {
+ console.log(`Edited event published to ${url} (d="${existingD}")`);
+ }
+ } catch (err) {
+ if (isDevMode) {
+ console.error(`Failed to publish edited event to ${url}:`, err.message);
+ }
+ }
+ }));
+
+ return signedEvent;
+ } catch (error) {
+ if (isDevMode) {
+ console.error('Failed to sign edited event:', error.message);
+ }
+ throw new Error('Failed to sign edited event.');
+ }
+ }
/**
* Fetches videos from all configured relays.
@@ -227,7 +302,6 @@ class NostrClient {
// Use a Map so duplicates (same event ID) across multiple relays don't overwrite each other
const videoEvents = new Map();
- // Optional: Only log if in dev mode (to avoid flooding console in production).
if (isDevMode) {
console.log('[fetchVideos] Starting fetch from all relays...');
console.log('[fetchVideos] Filter:', filter);
@@ -237,24 +311,20 @@ class NostrClient {
// Fetch from each relay in parallel
await Promise.all(
this.relays.map(async (url) => {
- // Log relay being queried
if (isDevMode) console.log(`[fetchVideos] Querying relay: ${url}`);
try {
const events = await this.pool.list([url], [filter]);
- // How many events came back from this relay?
if (isDevMode) {
console.log(`Events from ${url}:`, events.length);
- }
-
- // For deeper insight, you can log each event
- if (isDevMode && events.length > 0) {
- events.forEach((evt, idx) => {
- console.log(
- `[fetchVideos] [${url}] Event[${idx}] ID: ${evt.id} | pubkey: ${evt.pubkey} | created_at: ${evt.created_at}`
- );
- });
+ if (events.length > 0) {
+ events.forEach((evt, idx) => {
+ console.log(
+ `[fetchVideos] [${url}] Event[${idx}] ID: ${evt.id} | pubkey: ${evt.pubkey} | created_at: ${evt.created_at}`
+ );
+ });
+ }
}
// Process each event
@@ -272,7 +342,9 @@ class NostrClient {
description: content.description || '',
mode: content.mode || 'live',
pubkey: event.pubkey,
- created_at: event.created_at
+ created_at: event.created_at,
+ // Keep the original tags array in case we need them later (e.g. for editing)
+ tags: event.tags
});
}
} catch (parseError) {
@@ -330,7 +402,7 @@ class NostrClient {
typeof content.mode === 'string' &&
['dev', 'live'].includes(content.mode) &&
(typeof content.thumbnail === 'string' || typeof content.thumbnail === 'undefined') &&
- (typeof content.description === 'string' || typeof content.description === 'undefined') // Ensure description is optional
+ (typeof content.description === 'string' || typeof content.description === 'undefined')
);
if (isDevMode && !isValid) {