From a50e5c861a56fc27ca0d664f37ac9960cf5b2d87 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:05:13 +0000 Subject: [PATCH 1/2] Add subscription history and backup/restore functionality - Added `SUBSCRIPTION_BACKUP` note type schema - Implemented `createBackup`, `fetchHistory`, `restoreBackup`, `rebroadcastBackup` in `SubscriptionsManager` - Added Subscription History UI modal with list/details view - Integrated History/Backup buttons into Profile Modal Subscriptions pane - Fixed XSS vulnerability in history details view - Added profile fetching for history details Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- components/profile-modal.html | 36 ++- components/subscription-history-modal.html | 111 +++++++ js/nostrEventSchemas.js | 15 + js/subscriptions.js | 97 +++++- js/ui/profileModalController.js | 32 ++ js/ui/subscriptionHistoryController.js | 352 +++++++++++++++++++++ 6 files changed, 633 insertions(+), 10 deletions(-) create mode 100644 components/subscription-history-modal.html create mode 100644 js/ui/subscriptionHistoryController.js diff --git a/components/profile-modal.html b/components/profile-modal.html index 301b7d0c..b610f523 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -1087,15 +1087,33 @@ >
${ + isBackup ? "Backup" : "Standard List" + }
+${dateStr}
+Decryption failed or content is invalid.
`; + this.selectedMeta.textContent = "Unknown content"; + } finally { + this.decryptingIndicator.classList.add("hidden"); + } + } + + async renderChannels(pubkeys) { + this.selectedChannelsList.innerHTML = ""; + + const app = getApplication(); + if (app && typeof app.batchFetchProfiles === "function") { + try { + await app.batchFetchProfiles(pubkeys); + } catch (e) { + userLogger.warn("Failed to batch fetch profiles", e); + } + } + + pubkeys.forEach((pubkey) => { + const profile = profileCache.getProfile(pubkey); + const name = profile?.name || profile?.display_name || pubkey.slice(0, 8); + const avatar = profile?.picture || "assets/svg/default-profile.svg"; + + const item = document.createElement("li"); + item.className = + "flex items-center gap-3 p-2 rounded-md hover:bg-surface-2 border border-transparent hover:border-border/50 transition-colors"; + + const img = document.createElement("img"); + img.src = avatar; + img.className = "w-8 h-8 rounded-full object-cover bg-surface-2"; + img.onerror = function () { + this.src = "assets/svg/default-profile.svg"; + }; + + const div = document.createElement("div"); + div.className = "overflow-hidden"; + + const pName = document.createElement("p"); + pName.className = "text-sm font-medium truncate text-text"; + pName.textContent = name; + + const pKey = document.createElement("p"); + pKey.className = "text-xs text-muted truncate font-mono"; + pKey.textContent = `${pubkey.slice(0, 10)}...`; + + div.appendChild(pName); + div.appendChild(pKey); + + item.appendChild(img); + item.appendChild(div); + + this.selectedChannelsList.appendChild(item); + }); + } + + async handleRestore() { + if (!this.selectedEvent) return; + if ( + !confirm( + "Are you sure you want to restore this list? It will overwrite your current subscriptions." + ) + ) + return; + + this.restoreBtn.disabled = true; + this.restoreBtn.textContent = "Restoring..."; + + try { + await subscriptions.restoreBackup(this.selectedEvent, this.activePubkey); + // alert("Subscriptions restored successfully!"); // alert blocks, toast is better if available, but alert is fine for now + this.hide(); + } catch (error) { + userLogger.error("Restore failed:", error); + alert("Failed to restore: " + error.message); + } finally { + this.restoreBtn.disabled = false; + this.restoreBtn.textContent = "Restore as Active"; + } + } + + async handleRebroadcast() { + if (!this.selectedEvent) return; + this.rebroadcastBtn.disabled = true; + this.rebroadcastBtn.textContent = "Broadcasting..."; + + try { + await subscriptions.rebroadcastBackup( + this.selectedEvent, + this.activePubkey + ); + // Refresh to show potentially new event + this.fetchHistory(); + } catch (error) { + userLogger.error("Rebroadcast failed:", error); + alert("Failed to rebroadcast: " + error.message); + } finally { + this.rebroadcastBtn.disabled = false; + this.rebroadcastBtn.textContent = "Rebroadcast"; + } + } + + async handleCreateBackup(pubkey) { + if (!pubkey) return; + + try { + await subscriptions.createBackup(pubkey); + // If modal is open, refresh + if ( + this.modal && + !this.modal.classList.contains("hidden") && + this.activePubkey === pubkey + ) { + this.fetchHistory(); + } else { + // Show success feedback + // Using a simple alert or console log if UI controller doesn't pass a toast function + // userLogger.info("Backup created"); + } + } catch (error) { + userLogger.error("Backup failed:", error); + alert("Backup failed: " + error.message); + } + } +} From 00ca2298e12d9f56208045e64c7e2fd5954421f8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:11:19 +0000 Subject: [PATCH 2/2] Fix CI lint error: Replace arbitrary tailwind value - Replaced `h-[80vh]` with standard `h-4/5` in `components/subscription-history-modal.html` to satisfy `lint:tailwind-brackets` token rules. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- components/subscription-history-modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/subscription-history-modal.html b/components/subscription-history-modal.html index 8718ec00..a47d7cea 100644 --- a/components/subscription-history-modal.html +++ b/components/subscription-history-modal.html @@ -10,7 +10,7 @@