diff --git a/js/accessControl.js b/js/accessControl.js index 02706d89..7a851bc6 100644 --- a/js/accessControl.js +++ b/js/accessControl.js @@ -750,6 +750,7 @@ class AccessControl { } export const accessControl = new AccessControl(); +export default accessControl; export { AccessControl, ADMIN_WHITELIST_MODE_STORAGE_KEY, diff --git a/js/app.js b/js/app.js index 2a4906bd..ede0b2eb 100644 --- a/js/app.js +++ b/js/app.js @@ -4454,10 +4454,18 @@ class Application { /** * Cleanup resources on unload or modal close. + * + * When `preserveModals` is true the modal infrastructure is kept alive so the + * next playback session can reuse the existing controllers without + * reinitializing DOM bindings. */ - async cleanup({ preserveSubscriptions = false, preserveObservers = false } = {}) { + async cleanup({ + preserveSubscriptions = false, + preserveObservers = false, + preserveModals = false, + } = {}) { this.log( - `[cleanup] Requested (preserveSubscriptions=${preserveSubscriptions}, preserveObservers=${preserveObservers})` + `[cleanup] Requested (preserveSubscriptions=${preserveSubscriptions}, preserveObservers=${preserveObservers}, preserveModals=${preserveModals})` ); // Serialise teardown so overlapping calls (e.g. close button spam) don't // race each other and clobber a fresh playback setup. @@ -4472,7 +4480,7 @@ class Application { const runCleanup = async () => { this.log( - `[cleanup] Begin (preserveSubscriptions=${preserveSubscriptions}, preserveObservers=${preserveObservers})` + `[cleanup] Begin (preserveSubscriptions=${preserveSubscriptions}, preserveObservers=${preserveObservers}, preserveModals=${preserveModals})` ); try { this.cancelPendingViewLogging(); @@ -4547,20 +4555,22 @@ class Application { this.teardownModalCommentSubscription({ resetUi: false }); - if (this.modalManager) { - try { - this.modalManager.teardown(); - } catch (error) { - devLogger.warn("[cleanup] Modal teardown failed:", error); + if (!preserveModals) { + if (this.modalManager) { + try { + this.modalManager.teardown(); + } catch (error) { + devLogger.warn("[cleanup] Modal teardown failed:", error); + } + this.modalManager = null; } - this.modalManager = null; - } - if (this.bootstrapper) { - try { - this.bootstrapper.teardown(); - } catch (error) { - devLogger.warn("[cleanup] Bootstrap teardown failed:", error); + if (this.bootstrapper) { + try { + this.bootstrapper.teardown(); + } catch (error) { + devLogger.warn("[cleanup] Bootstrap teardown failed:", error); + } } } @@ -5211,6 +5221,7 @@ class Application { await this.cleanup({ preserveSubscriptions: true, preserveObservers: true, + preserveModals: true, }); }; diff --git a/js/ui/ModalManager.js b/js/ui/ModalManager.js index eaad078e..ccc751d6 100644 --- a/js/ui/ModalManager.js +++ b/js/ui/ModalManager.js @@ -415,11 +415,58 @@ export default class ModalManager { return; } - app.openVideoModal({ - video: selectedVideo, - triggerElement: triggerCandidate || null, - source: detail?.source || "modal", - }); + const playbackOptions = { + trigger: triggerCandidate || null, + }; + + const rawUrl = + typeof selectedVideo.url === "string" ? selectedVideo.url.trim() : ""; + if (rawUrl) { + playbackOptions.url = rawUrl; + } + + const rawMagnet = + typeof selectedVideo.magnet === "string" + ? selectedVideo.magnet.trim() + : ""; + if (rawMagnet) { + playbackOptions.magnet = rawMagnet; + } + + const hasPlayById = typeof app.playVideoByEventId === "function"; + const hasFallbackPlayback = + typeof app.playVideoWithFallback === "function"; + + const selectedId = + typeof selectedVideo.id === "string" ? selectedVideo.id : ""; + + if (selectedId && hasPlayById) { + Promise.resolve(app.playVideoByEventId(selectedId, playbackOptions)).catch( + (error) => { + devLogger.error( + "[ModalManager] Failed to play selected similar video:", + error, + ); + }, + ); + return; + } + + if (!hasFallbackPlayback) { + devLogger.warn( + "[ModalManager] Unable to start playback for similar video; no playback handler is available.", + ); + return; + } + + Promise.resolve(app.playVideoWithFallback(playbackOptions)).catch( + (error) => { + devLogger.error( + "[ModalManager] Failed to start playback for similar video:", + error, + ); + }, + ); }; this.videoModal.addEventListener( "similar:select", diff --git a/js/ui/applicationBootstrap.js b/js/ui/applicationBootstrap.js index 5970f2c0..cb2db2dd 100644 --- a/js/ui/applicationBootstrap.js +++ b/js/ui/applicationBootstrap.js @@ -22,13 +22,13 @@ import { ALLOW_NSFW_CONTENT } from "../config.js"; import { relayManager } from "../relayManager.js"; import { userBlocks } from "../userBlocks.js"; import { subscriptions } from "../subscriptions.js"; -import accessControl from "../accessControl.js"; +import { accessControl } from "../accessControl.js"; import moderationService from "../services/moderationService.js"; import ProfileIdentityController from "./profileIdentityController.js"; import VideoListViewController from "./videoListViewController.js"; import ProfileModalController from "./profileModalController.js"; import LoginModalController from "./loginModalController.js"; -import VideoListView from "./views/VideoListView.js"; +import { VideoListView } from "./views/VideoListView.js"; import MoreMenuController from "./moreMenuController.js"; import AppChromeController from "./appChromeController.js"; import { getSidebarLoadingMarkup } from "../sidebarLoading.js";