mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 00:12:25 +00:00
refactor(ai): extract handleRevertVideo from js/app.js (agent)
Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
2fd0d5cab6
commit
e5f24bf4b0
3 files changed
+281
-86
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user