From e5f24bf4b03b8cdc3cc7498bf8926ef8860e33bf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 03:20:08 +0000 Subject: [PATCH] refactor(ai): extract handleRevertVideo from js/app.js (agent) Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/app.js | 117 ++++++---------------- js/ui/revertModalController.js | 118 ++++++++++++++++++++++ tests/revert-modal-controller.test.mjs | 132 +++++++++++++++++++++++++ 3 files changed, 281 insertions(+), 86 deletions(-) create mode 100644 js/ui/revertModalController.js create mode 100644 tests/revert-modal-controller.test.mjs diff --git a/js/app.js b/js/app.js index ca73f976..1b3ce27b 100644 --- a/js/app.js +++ b/js/app.js @@ -113,6 +113,7 @@ import { } from "./ui/components/staticModalAccessibility.js"; import LoginModalController from "./ui/loginModalController.js"; import EditModalController from "./ui/editModalController.js"; +import RevertModalController from "./ui/revertModalController.js"; import TagPreferenceMenuController from "./ui/tagPreferenceMenuController.js"; import ReactionController from "./ui/reactionController.js"; import { pointerArrayToKey } from "./utils/pointer.js"; @@ -420,6 +421,32 @@ class Application { }, }); + this.revertModalController = new RevertModalController({ + revertModal: this.modalManager?.revertModal, + services: { + nostrService: this.nostrService, + nostrClient: nostrClient, + }, + state: { + getPubkey: () => this.pubkey, + getBlacklistedEventIds: () => this.blacklistedEventIds, + }, + ui: { + showError: (msg) => this.showError(msg), + showSuccess: (msg) => this.showSuccess(msg), + }, + callbacks: { + loadVideos: () => this.loadVideos(), + forceRefreshAllProfiles: () => this.forceRefreshAllProfiles(), + isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey), + }, + helpers: { + normalizeActionTarget: (t) => this.normalizeActionTarget(t), + resolveVideoActionTarget: (opts) => this.resolveVideoActionTarget(opts), + formatAbsoluteTimestamp: (ts) => this.formatAbsoluteTimestamp(ts), + }, + }); + this.handleShareNostrSignerChange = () => { this.updateShareNostrAuthState({ reason: "signer-change" }); }; @@ -7817,96 +7844,14 @@ class Application { } async handleRevertVideo(target) { - try { - const normalizedTarget = this.normalizeActionTarget(target); - const { triggerElement } = normalizedTarget; - const activeVideos = await this.nostrService.fetchVideos({ - blacklistedEventIds: this.blacklistedEventIds, - isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey), - }); - const video = await this.resolveVideoActionTarget({ - ...normalizedTarget, - preloadedList: activeVideos, - }); - - if (!this.pubkey) { - this.showError("Please login to revert."); - return; - } - const userPubkey = (this.pubkey || "").toLowerCase(); - const videoPubkey = (video?.pubkey || "").toLowerCase(); - if (!video || !videoPubkey || videoPubkey !== userPubkey) { - this.showError("You do not own this video."); - return; - } - - if (!this.revertModal) { - this.showError("Revert modal is not available right now."); - return; - } - - const loaded = await this.revertModal.load(); - if (!loaded) { - this.showError("Revert modal is not available right now."); - return; - } - - const history = await nostrClient.hydrateVideoHistory(video); - - this.revertModal.setHistory(video, history); - this.revertModal.open({ video }, { triggerElement }); - } catch (err) { - devLogger.error("Failed to revert video:", err); - this.showError("Failed to load revision history. Please try again."); + if (this.revertModalController) { + return this.revertModalController.open(target); } } async handleRevertModalConfirm(event) { - const detail = event?.detail || {}; - const target = detail.target; - const entries = Array.isArray(detail.entries) - ? detail.entries.slice() - : []; - - if (!target || !entries.length) { - return; - } - - if (!this.pubkey) { - this.showError("Please login to revert."); - return; - } - - if (!this.revertModal) { - this.showError("Revert modal is not available right now."); - return; - } - - this.revertModal.setBusy(true, "Reverting…"); - - try { - for (const entry of entries) { - await nostrClient.revertVideo( - { - id: entry.id, - pubkey: entry.pubkey, - tags: entry.tags, - }, - this.pubkey - ); - } - - await this.loadVideos(); - - const timestampLabel = this.formatAbsoluteTimestamp(target.created_at); - this.showSuccess(`Reverted to revision from ${timestampLabel}.`); - this.revertModal.close(); - this.forceRefreshAllProfiles(); - } catch (err) { - devLogger.error("Failed to revert video:", err); - this.showError("Failed to revert video. Please try again."); - } finally { - this.revertModal.setBusy(false); + if (this.revertModalController) { + return this.revertModalController.handleConfirm(event); } } diff --git a/js/ui/revertModalController.js b/js/ui/revertModalController.js new file mode 100644 index 00000000..8815ff6d --- /dev/null +++ b/js/ui/revertModalController.js @@ -0,0 +1,118 @@ +import { devLogger } from "../utils/logger.js"; + +export default class RevertModalController { + constructor({ + revertModal, + services = {}, + state = {}, + ui = {}, + callbacks = {}, + helpers = {}, + }) { + this.revertModal = revertModal; + this.services = services; + this.state = state; + this.ui = ui; + this.callbacks = callbacks; + this.helpers = helpers; + } + + async open(target) { + try { + const normalizedTarget = this.helpers.normalizeActionTarget(target); + const { triggerElement } = normalizedTarget; + + const activeVideos = await this.services.nostrService.fetchVideos({ + blacklistedEventIds: this.state.getBlacklistedEventIds(), + isAuthorBlocked: (pubkey) => this.callbacks.isAuthorBlocked(pubkey), + }); + + const video = await this.helpers.resolveVideoActionTarget({ + ...normalizedTarget, + preloadedList: activeVideos, + }); + + const pubkey = this.state.getPubkey(); + if (!pubkey) { + this.ui.showError("Please login to revert."); + return; + } + + const userPubkey = (pubkey || "").toLowerCase(); + const videoPubkey = (video?.pubkey || "").toLowerCase(); + if (!video || !videoPubkey || videoPubkey !== userPubkey) { + this.ui.showError("You do not own this video."); + return; + } + + if (!this.revertModal) { + this.ui.showError("Revert modal is not available right now."); + return; + } + + const loaded = await this.revertModal.load(); + if (!loaded) { + this.ui.showError("Revert modal is not available right now."); + return; + } + + const history = await this.services.nostrClient.hydrateVideoHistory(video); + + this.revertModal.setHistory(video, history); + this.revertModal.open({ video }, { triggerElement }); + } catch (err) { + devLogger.error("Failed to revert video:", err); + this.ui.showError("Failed to load revision history. Please try again."); + } + } + + async handleConfirm(event) { + const detail = event?.detail || {}; + const target = detail.target; + const entries = Array.isArray(detail.entries) + ? detail.entries.slice() + : []; + + if (!target || !entries.length) { + return; + } + + const pubkey = this.state.getPubkey(); + if (!pubkey) { + this.ui.showError("Please login to revert."); + return; + } + + if (!this.revertModal) { + this.ui.showError("Revert modal is not available right now."); + return; + } + + this.revertModal.setBusy(true, "Reverting…"); + + try { + for (const entry of entries) { + await this.services.nostrClient.revertVideo( + { + id: entry.id, + pubkey: entry.pubkey, + tags: entry.tags, + }, + pubkey, + ); + } + + await this.callbacks.loadVideos(); + + const timestampLabel = this.helpers.formatAbsoluteTimestamp(target.created_at); + this.ui.showSuccess(`Reverted to revision from ${timestampLabel}.`); + this.revertModal.close(); + this.callbacks.forceRefreshAllProfiles(); + } catch (err) { + devLogger.error("Failed to revert video:", err); + this.ui.showError("Failed to revert video. Please try again."); + } finally { + this.revertModal.setBusy(false); + } + } +} diff --git a/tests/revert-modal-controller.test.mjs b/tests/revert-modal-controller.test.mjs new file mode 100644 index 00000000..26a69b22 --- /dev/null +++ b/tests/revert-modal-controller.test.mjs @@ -0,0 +1,132 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import RevertModalController from '../js/ui/revertModalController.js'; + +test('RevertModalController', async (t) => { + const mockVideo = { id: 'v1', pubkey: 'p1' }; + const mockHistory = [{ id: 'v1' }, { id: 'v0' }]; + + const mocks = { + revertModal: { + load: async () => true, + setHistory: () => {}, + open: () => {}, + close: () => {}, + setBusy: () => {}, + }, + services: { + nostrService: { + fetchVideos: async () => [mockVideo], + }, + nostrClient: { + hydrateVideoHistory: async () => mockHistory, + revertVideo: async () => {}, + }, + }, + state: { + getPubkey: () => 'p1', // must match video pubkey + getBlacklistedEventIds: () => new Set(), + }, + ui: { + showError: () => {}, + showSuccess: () => {}, + }, + callbacks: { + loadVideos: async () => {}, + forceRefreshAllProfiles: () => {}, + isAuthorBlocked: () => false, + }, + helpers: { + normalizeActionTarget: () => ({ triggerElement: null }), + resolveVideoActionTarget: async () => mockVideo, + formatAbsoluteTimestamp: () => 'timestamp', + }, + }; + + await t.test('open() fetches history and opens modal', async () => { + let historySet = null; + let modalOpened = false; + + const controller = new RevertModalController({ + ...mocks, + revertModal: { + ...mocks.revertModal, + setHistory: (_v, h) => { + historySet = h; + }, + open: () => { + modalOpened = true; + }, + }, + }); + + await controller.open('v1'); + + assert.deepEqual(historySet, mockHistory, 'History should be set'); + assert.equal(modalOpened, true, 'Modal should be opened'); + }); + + await t.test('open() shows error if not logged in', async () => { + let errorMsg = ''; + const controller = new RevertModalController({ + ...mocks, + state: { ...mocks.state, getPubkey: () => null }, + ui: { + ...mocks.ui, + showError: (m) => { + errorMsg = m; + }, + }, + }); + + await controller.open('v1'); + assert.match(errorMsg, /login/, 'Should show login error'); + }); + + await t.test('handleConfirm() calls revertVideo and refreshes', async () => { + let revertCalled = false; + let refreshCalled = false; + let modalClosed = false; + let modalBusyState = null; + + const controller = new RevertModalController({ + ...mocks, + services: { + ...mocks.services, + nostrClient: { + ...mocks.services.nostrClient, + revertVideo: async () => { + revertCalled = true; + }, + }, + }, + callbacks: { + ...mocks.callbacks, + loadVideos: async () => { + refreshCalled = true; + }, + }, + revertModal: { + ...mocks.revertModal, + close: () => { + modalClosed = true; + }, + setBusy: (busy) => { + modalBusyState = busy; + }, + }, + }); + + await controller.handleConfirm({ + detail: { + target: mockVideo, + entries: [{ id: 'v1', pubkey: 'p1', tags: [] }], + }, + }); + + assert.equal(revertCalled, true, 'revertVideo should be called'); + assert.equal(refreshCalled, true, 'loadVideos should be called'); + assert.equal(modalClosed, true, 'Modal should be closed'); + assert.equal(modalBusyState, false, 'Modal should not be busy at end'); + }); +});