From 40e39367f630d7dab99b23e26895b124fd8a53e4 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Wed, 25 Jun 2025 10:24:37 +0300 Subject: [PATCH 1/5] WebUI: Allow closing modals with Escape Closes [#13891](https://github.com/qbittorrent/qBittorrent/issues/13891) --- src/webui/www/private/scripts/client.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 01f29e25f..082b55726 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1773,6 +1773,15 @@ window.addEventListener("DOMContentLoaded", (event) => { event.preventDefault(); deleteSelectedTorrentsFN(event.shiftKey); break; + + case "Escape": + if (event.target.isContentEditable) + return; + event.preventDefault(); + Object.values(MochaUI.Windows.instances).forEach((modal) => { + modal.close(); + }); + break; } }); From 2f90dc319920f7143fe1197d21f420688113a7fb Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Wed, 25 Jun 2025 15:01:39 +0300 Subject: [PATCH 2/5] WebUI: Add Escape event handler to iframed modals --- src/webui/www/private/confirmfeeddeletion.html | 8 ++++++++ src/webui/www/private/confirmruleclear.html | 8 ++++++++ src/webui/www/private/confirmruledeletion.html | 8 ++++++++ src/webui/www/private/confirmtrackerdeletion.html | 8 ++++++++ src/webui/www/private/download.html | 8 ++++++++ src/webui/www/private/upload.html | 9 +++++++++ 6 files changed, 49 insertions(+) diff --git a/src/webui/www/private/confirmfeeddeletion.html b/src/webui/www/private/confirmfeeddeletion.html index 27103f860..93d82a758 100644 --- a/src/webui/www/private/confirmfeeddeletion.html +++ b/src/webui/www/private/confirmfeeddeletion.html @@ -11,6 +11,14 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmruleclear.html b/src/webui/www/private/confirmruleclear.html index 022e78eef..f98de30a9 100644 --- a/src/webui/www/private/confirmruleclear.html +++ b/src/webui/www/private/confirmruleclear.html @@ -11,6 +11,14 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmruledeletion.html b/src/webui/www/private/confirmruledeletion.html index 16c224829..bd6ab45f7 100644 --- a/src/webui/www/private/confirmruledeletion.html +++ b/src/webui/www/private/confirmruledeletion.html @@ -11,6 +11,14 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmtrackerdeletion.html b/src/webui/www/private/confirmtrackerdeletion.html index 6a6950c5c..16cccbbbb 100644 --- a/src/webui/www/private/confirmtrackerdeletion.html +++ b/src/webui/www/private/confirmtrackerdeletion.html @@ -11,6 +11,14 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); const searchParams = new URLSearchParams(window.location.search); const host = searchParams.get("host"); diff --git a/src/webui/www/private/download.html b/src/webui/www/private/download.html index 53921e64d..ca9bf5065 100644 --- a/src/webui/www/private/download.html +++ b/src/webui/www/private/download.html @@ -14,6 +14,14 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); const encodedUrls = new URLSearchParams(window.location.search).get("urls"); if (encodedUrls !== null) { const urls = encodedUrls.split("|").map(decodeURIComponent); diff --git a/src/webui/www/private/upload.html b/src/webui/www/private/upload.html index 1a89cffe1..3cf21f84e 100644 --- a/src/webui/www/private/upload.html +++ b/src/webui/www/private/upload.html @@ -14,6 +14,15 @@ "use strict"; window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("keydown", (event) => { + switch (event.key) { + case "Escape": + event.preventDefault(); + window.parent.qBittorrent.Client.closeFrameWindow(window); + break; + } + }); + let submitted = false; document.getElementById("uploadForm").addEventListener("submit", (event) => { From 52bf8fb8d0520059e3073d2f9151f815148e6cb2 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Thu, 26 Jun 2025 09:00:58 +0300 Subject: [PATCH 3/5] WebUI: Close modals in the same order as opening --- src/webui/www/private/scripts/client.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 082b55726..db1637a73 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1774,14 +1774,21 @@ window.addEventListener("DOMContentLoaded", (event) => { deleteSelectedTorrentsFN(event.shiftKey); break; - case "Escape": + case "Escape": { if (event.target.isContentEditable) return; event.preventDefault(); - Object.values(MochaUI.Windows.instances).forEach((modal) => { - modal.close(); + const modalInstances = Object.values(MochaUI.Windows.instances); + if (modalInstances.length <= 0) + return; + // MochaUI.currentModal does not update after a modal is closed + // use `timestamp` for sequential closing + const latestModal = modalInstances.reduce((prev, curr) => { + return (prev.timestamp > curr.timestamp) ? prev : curr; }); + latestModal.close(); break; + } } }); From db341a2e6ee366d1c0cff9ab5899e4e6d6c4922c Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Thu, 26 Jun 2025 09:45:15 +0300 Subject: [PATCH 4/5] WebUI: Close only focused modal --- src/webui/www/private/scripts/client.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index db1637a73..6f0ced81b 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1782,11 +1782,12 @@ window.addEventListener("DOMContentLoaded", (event) => { if (modalInstances.length <= 0) return; // MochaUI.currentModal does not update after a modal is closed - // use `timestamp` for sequential closing - const latestModal = modalInstances.reduce((prev, curr) => { - return (prev.timestamp > curr.timestamp) ? prev : curr; + const focusedModal = modalInstances.find((modal) => { + return modal.windowEl.hasClass("isFocused"); }); - latestModal.close(); + if (!focusedModal) + return; + focusedModal.close(); break; } } From 5f527c2a6fd3e979153fe18921712062b85c3361 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Sat, 28 Jun 2025 14:08:35 +0300 Subject: [PATCH 5/5] WebUI: Coding style fixes --- src/webui/www/private/confirmfeeddeletion.html | 1 + src/webui/www/private/confirmruleclear.html | 1 + src/webui/www/private/confirmruledeletion.html | 1 + src/webui/www/private/confirmtrackerdeletion.html | 1 + src/webui/www/private/download.html | 1 + src/webui/www/private/scripts/client.js | 6 +++--- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/webui/www/private/confirmfeeddeletion.html b/src/webui/www/private/confirmfeeddeletion.html index 93d82a758..e802e3f70 100644 --- a/src/webui/www/private/confirmfeeddeletion.html +++ b/src/webui/www/private/confirmfeeddeletion.html @@ -19,6 +19,7 @@ break; } }); + document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmruleclear.html b/src/webui/www/private/confirmruleclear.html index f98de30a9..8b6f7889b 100644 --- a/src/webui/www/private/confirmruleclear.html +++ b/src/webui/www/private/confirmruleclear.html @@ -19,6 +19,7 @@ break; } }); + document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmruledeletion.html b/src/webui/www/private/confirmruledeletion.html index bd6ab45f7..db0a1c175 100644 --- a/src/webui/www/private/confirmruledeletion.html +++ b/src/webui/www/private/confirmruledeletion.html @@ -19,6 +19,7 @@ break; } }); + document.getElementById("cancelBtn").focus(); document.getElementById("cancelBtn").addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/webui/www/private/confirmtrackerdeletion.html b/src/webui/www/private/confirmtrackerdeletion.html index 16cccbbbb..c3d1f0a49 100644 --- a/src/webui/www/private/confirmtrackerdeletion.html +++ b/src/webui/www/private/confirmtrackerdeletion.html @@ -19,6 +19,7 @@ break; } }); + const searchParams = new URLSearchParams(window.location.search); const host = searchParams.get("host"); diff --git a/src/webui/www/private/download.html b/src/webui/www/private/download.html index ca9bf5065..807e95f70 100644 --- a/src/webui/www/private/download.html +++ b/src/webui/www/private/download.html @@ -22,6 +22,7 @@ break; } }); + const encodedUrls = new URLSearchParams(window.location.search).get("urls"); if (encodedUrls !== null) { const urls = encodedUrls.split("|").map(decodeURIComponent); diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 6f0ced81b..3156f246d 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1781,13 +1781,13 @@ window.addEventListener("DOMContentLoaded", (event) => { const modalInstances = Object.values(MochaUI.Windows.instances); if (modalInstances.length <= 0) return; + // MochaUI.currentModal does not update after a modal is closed const focusedModal = modalInstances.find((modal) => { return modal.windowEl.hasClass("isFocused"); }); - if (!focusedModal) - return; - focusedModal.close(); + if (focusedModal !== undefined) + focusedModal.close(); break; } }