diff --git a/src/webui/www/private/css/dynamicTable.css b/src/webui/www/private/css/dynamicTable.css index 86e3072df..ae0b75906 100644 --- a/src/webui/www/private/css/dynamicTable.css +++ b/src/webui/www/private/css/dynamicTable.css @@ -22,10 +22,6 @@ color: var(--color-text-white); } -#transferList tr:hover { - cursor: pointer; -} - #transferList img.stateIcon { height: 1.3em; margin-bottom: -1px; @@ -37,10 +33,6 @@ display: flex !important; } -tr.dynamicTableHeader { - cursor: pointer; -} - .dynamicTable { border-spacing: 0; padding: 0; @@ -54,6 +46,10 @@ tr.dynamicTableHeader { white-space: nowrap; } +.dynamicTable tr:hover { + cursor: pointer; +} + .dynamicTable tr:is(:hover, .selected) img:not(.flags) { filter: var(--color-icon-hover); } diff --git a/src/webui/www/private/css/style.css b/src/webui/www/private/css/style.css index d5c627dbd..116f4948a 100644 --- a/src/webui/www/private/css/style.css +++ b/src/webui/www/private/css/style.css @@ -861,10 +861,6 @@ td.statusBarSeparator { color: var(--color-text-green); } -.searchPluginsTableRow { - cursor: pointer; -} - #torrentFilesTableDiv .dynamicTable tr.nonAlt:hover { background-color: var(--color-background-hover); color: var(--color-text-white); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index f43d96440..913909cf4 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -73,6 +73,7 @@ window.qBittorrent.DynamicTable ??= (() => { setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) { this.dynamicTableDivId = dynamicTableDivId; this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId; + this.dynamicTableDiv = document.getElementById(dynamicTableDivId); this.fixedTableHeader = $(dynamicTableFixedHeaderDivId).getElements("tr")[0]; this.hiddenTableHeader = $(dynamicTableDivId).getElements("tr")[0]; this.tableBody = $(dynamicTableDivId).getElements("tbody")[0]; @@ -93,12 +94,81 @@ window.qBittorrent.DynamicTable ??= (() => { }, setupCommonEvents: function() { - const tableDiv = $(this.dynamicTableDivId); const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId); const tableElement = tableFixedHeaderDiv.querySelector("table"); - tableDiv.addEventListener("scroll", () => { - tableElement.style.left = `${-tableDiv.scrollLeft}px`; + this.dynamicTableDiv.addEventListener("scroll", function() { + tableElement.style.left = `${-this.scrollLeft}px`; + }); + + this.dynamicTableDiv.addEventListener("click", (e) => { + const tr = e.target.closest("tr"); + if (!tr) { + // clicking on the table body deselects all rows + this.deselectAll(); + this.setRowClass(); + return; + } + + if (e.ctrlKey || e.metaKey) { + // CTRL/CMD ⌘ key was pressed + if (this.isRowSelected(tr.rowId)) + this.deselectRow(tr.rowId); + else + this.selectRow(tr.rowId); + } + else if (e.shiftKey && (this.selectedRows.length === 1)) { + // Shift key was pressed + this.selectRows(this.getSelectedRowId(), tr.rowId); + } + else { + // Simple selection + this.deselectAll(); + this.selectRow(tr.rowId); + } + }); + + this.dynamicTableDiv.addEventListener("contextmenu", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + if (!this.isRowSelected(tr.rowId)) { + this.deselectAll(); + this.selectRow(tr.rowId); + } + }, true); + + this.dynamicTableDiv.addEventListener("touchstart", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + if (!this.isRowSelected(tr.rowId)) { + this.deselectAll(); + this.selectRow(tr.rowId); + } + }, { passive: true }); + + this.dynamicTableDiv.addEventListener("keydown", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + switch (e.key) { + case "ArrowUp": { + e.preventDefault(); + this.selectPreviousRow(); + this.dynamicTableDiv.querySelector(".selected").scrollIntoView({ block: "nearest" }); + break; + } + case "ArrowDown": { + e.preventDefault(); + this.selectNextRow(); + this.dynamicTableDiv.querySelector(".selected").scrollIntoView({ block: "nearest" }); + break; + } + } }); }, @@ -801,54 +871,6 @@ window.qBittorrent.DynamicTable ??= (() => { tr.setAttribute("data-row-id", rowId); tr["rowId"] = rowId; - tr._this = this; - tr.addEventListener("contextmenu", function(e) { - if (!this._this.isRowSelected(this.rowId)) { - this._this.deselectAll(); - this._this.selectRow(this.rowId); - } - return true; - }); - tr.addEventListener("click", function(e) { - e.preventDefault(); - - if (e.ctrlKey || e.metaKey) { - // CTRL/CMD ⌘ key was pressed - if (this._this.isRowSelected(this.rowId)) - this._this.deselectRow(this.rowId); - else - this._this.selectRow(this.rowId); - } - else if (e.shiftKey && (this._this.selectedRows.length === 1)) { - // Shift key was pressed - this._this.selectRows(this._this.getSelectedRowId(), this.rowId); - } - else { - // Simple selection - this._this.deselectAll(); - this._this.selectRow(this.rowId); - } - return false; - }); - tr.addEventListener("touchstart", function(e) { - if (!this._this.isRowSelected(this.rowId)) { - this._this.deselectAll(); - this._this.selectRow(this.rowId); - } - }, { passive: true }); - tr.addEventListener("keydown", function(event) { - switch (event.key) { - case "ArrowUp": - this._this.selectPreviousRow(); - return false; - case "ArrowDown": - this._this.selectNextRow(); - return false; - } - }); - - this.setupTr(tr); - for (let k = 0; k < this.columns.length; ++k) { const td = new Element("td"); if ((this.columns[k].visible === "0") || this.columns[k].force_hide) @@ -867,8 +889,7 @@ window.qBittorrent.DynamicTable ??= (() => { } // Update context menu - if (this.contextMenu) - this.contextMenu.addTarget(tr); + this.contextMenu?.addTarget(tr); this.updateRow(tr, true); } @@ -880,8 +901,6 @@ window.qBittorrent.DynamicTable ??= (() => { trs.pop().destroy(); }, - setupTr: (tr) => {}, - updateRow: function(tr, fullUpdate) { const row = this.rows.get(tr.rowId); const data = row[fullUpdate ? "full_data" : "data"]; @@ -1652,14 +1671,16 @@ window.qBittorrent.DynamicTable ??= (() => { return filteredRows; }, - setupTr: function(tr) { - tr.addEventListener("dblclick", function(e) { - e.preventDefault(); - e.stopPropagation(); + setupCommonEvents: function() { + this.parent(); + this.dynamicTableDiv.addEventListener("dblclick", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; - this._this.deselectAll(); - this._this.selectRow(this.rowId); - const row = this._this.rows.get(this.rowId); + this.deselectAll(); + this.selectRow(tr.rowId); + const row = this.getRow(tr.rowId); const state = row["full_data"].state; const prefKey = @@ -1679,9 +1700,7 @@ window.qBittorrent.DynamicTable ??= (() => { startFN(); else stopFN(); - return true; }); - tr.addClass("torrentsTableContextMenuTarget"); }, getCurrentTorrentID: function() { @@ -1920,10 +1939,6 @@ window.qBittorrent.DynamicTable ??= (() => { return filteredRows; }, - - setupTr: (tr) => { - tr.addClass("searchTableRow"); - } }); const SearchPluginsTable = new Class({ @@ -1955,10 +1970,6 @@ window.qBittorrent.DynamicTable ??= (() => { } }; }, - - setupTr: (tr) => { - tr.addClass("searchPluginsTableRow"); - } }); const TorrentTrackersTable = new Class({ @@ -2416,18 +2427,7 @@ window.qBittorrent.DynamicTable ??= (() => { row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100))); }, - setupTr: function(tr) { - tr.addEventListener("keydown", function(event) { - switch (event.key) { - case "ArrowLeft": - qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId()); - return false; - case "ArrowRight": - qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId()); - return false; - } - }); - } + setupCommonEvents: () => {} }); const TorrentFilesTable = new Class({ @@ -2754,15 +2754,22 @@ window.qBittorrent.DynamicTable ??= (() => { row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100))); }, - setupTr: function(tr) { - tr.addEventListener("keydown", function(event) { - switch (event.key) { + setupCommonEvents: function() { + this.parent(); + this.dynamicTableDiv.addEventListener("keydown", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + switch (e.key) { case "ArrowLeft": - qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId()); - return false; + e.preventDefault(); + window.qBittorrent.PropFiles.collapseFolder(this.getSelectedRowId()); + break; case "ArrowRight": - qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId()); - return false; + e.preventDefault(); + window.qBittorrent.PropFiles.expandFolder(this.getSelectedRowId()); + break; } }); } @@ -2805,12 +2812,14 @@ window.qBittorrent.DynamicTable ??= (() => { } window.qBittorrent.Rss.showRssFeed(path); }, - setupTr: function(tr) { - tr.addEventListener("dblclick", function(e) { - if (this.rowId !== 0) { - window.qBittorrent.Rss.moveItem(this._this.rows.get(this.rowId).full_data.dataPath); - return true; - } + setupCommonEvents: function() { + this.parent(); + this.dynamicTableDiv.addEventListener("dblclick", (e) => { + const tr = e.target.closest("tr"); + if (!tr || (tr.rowId === "0")) + return; + + window.qBittorrent.Rss.moveItem(this.getRow(tr.rowId).full_data.dataPath); }); }, updateRow: function(tr, fullUpdate) { @@ -2938,12 +2947,16 @@ window.qBittorrent.DynamicTable ??= (() => { } window.qBittorrent.Rss.showDetails(feedUid, articleId); }, - setupTr: function(tr) { - tr.addEventListener("dblclick", function(e) { - showDownloadPage([this._this.rows.get(this.rowId).full_data.torrentURL]); - return true; + + setupCommonEvents: function() { + this.parent(); + this.dynamicTableDiv.addEventListener("dblclick", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + showDownloadPage([this.getRow(tr.rowId).full_data.torrentURL]); }); - tr.addClass("torrentsTableContextMenuTarget"); }, updateRow: function(tr, fullUpdate) { const row = this.rows.get(tr.rowId); @@ -3033,10 +3046,15 @@ window.qBittorrent.DynamicTable ??= (() => { getFilteredAndSortedRows: function() { return [...this.getRowValues()]; }, - setupTr: function(tr) { - tr.addEventListener("dblclick", function(e) { - window.qBittorrent.RssDownloader.renameRule(this._this.rows.get(this.rowId).full_data.name); - return true; + + setupCommonEvents: function() { + this.parent(); + this.dynamicTableDiv.addEventListener("dblclick", (e) => { + const tr = e.target.closest("tr"); + if (!tr) + return; + + window.qBittorrent.RssDownloader.renameRule(this.getRow(tr.rowId).full_data.name); }); }, newColumn: function(name, style, caption, defaultWidth, defaultVisible) { @@ -3314,12 +3332,6 @@ window.qBittorrent.DynamicTable ??= (() => { return filteredRows; }, - - setupCommonEvents: () => {}, - - setupTr: (tr) => { - tr.addClass("logTableRow"); - } }); const LogPeerTable = new Class({ diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index eb29a5dc1..80ab62afc 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -109,7 +109,7 @@ window.qBittorrent.Search ??= (() => { // load "Search in" preference from local storage $("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere"; const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: ".searchTableRow", + targets: "#searchResultsTableDiv tr", menu: "searchResultsTableMenu", actions: { Download: downloadSearchTorrent, @@ -124,6 +124,8 @@ window.qBittorrent.Search ??= (() => { searchResultsTable.setup("searchResultsTableDiv", "searchResultsTableFixedHeaderDiv", searchResultsTableContextMenu); getPlugins(); + searchResultsTable.dynamicTableDiv.addEventListener("dblclick", (e) => { downloadSearchTorrent(); }); + // listen for changes to searchInNameFilter let searchInNameFilterTimer = -1; $("searchInNameFilter").addEventListener("input", () => { @@ -373,8 +375,6 @@ window.qBittorrent.Search ??= (() => { $("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length; $("numSearchResultsTotal").textContent = searchResultsTable.getRowSize(); - - setupSearchTableEvents(true); }; const getStatusIconElement = (text, image) => { @@ -769,20 +769,6 @@ window.qBittorrent.Search ??= (() => { $("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length; }; - const setupSearchTableEvents = (enable) => { - const clickHandler = (e) => { downloadSearchTorrent(); }; - if (enable) { - $$(".searchTableRow").each((target) => { - target.addEventListener("dblclick", clickHandler); - }); - } - else { - $$(".searchTableRow").each((target) => { - target.removeEventListener("dblclick", clickHandler); - }); - } - }; - const loadSearchResultsData = function(searchId) { const state = searchState.get(searchId); @@ -820,8 +806,6 @@ window.qBittorrent.Search ??= (() => { } if (response) { - setupSearchTableEvents(false); - const state = searchState.get(searchId); const newRows = []; @@ -859,8 +843,6 @@ window.qBittorrent.Search ??= (() => { searchResultsTable.updateTable(); } - setupSearchTableEvents(true); - if ((response.status === "Stopped") && (state.rowId >= response.total)) { resetSearchState(searchId); updateStatusIconElement(searchId, "QBT_TR(Search has finished)QBT_TR[CONTEXT=SearchJobWidget]", "images/task-complete.svg"); diff --git a/src/webui/www/private/views/log.html b/src/webui/www/private/views/log.html index d00c1696e..c88da6ac8 100644 --- a/src/webui/www/private/views/log.html +++ b/src/webui/www/private/views/log.html @@ -206,7 +206,7 @@ }); const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: ".logTableRow", + targets: ":is(#logMessageView, #logPeerView) tr", menu: "logTableMenu", actions: { Clear: () => { diff --git a/src/webui/www/private/views/rss.html b/src/webui/www/private/views/rss.html index 2fc0cd12f..e46f0f306 100644 --- a/src/webui/www/private/views/rss.html +++ b/src/webui/www/private/views/rss.html @@ -218,7 +218,7 @@ $("rssFetchingDisabled").removeClass("invisible"); const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({ - targets: ".rssFeedContextMenuTarget", + targets: "#rssFeedTableDiv tr", menu: "rssFeedMenu", actions: { update: (el) => { @@ -288,7 +288,7 @@ rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu); const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({ - targets: ".rssArticleElement", + targets: "#rssArticleTableDiv tr", menu: "rssArticleMenu", actions: { Download: (el) => { diff --git a/src/webui/www/private/views/searchplugins.html b/src/webui/www/private/views/searchplugins.html index 5ddbc99a5..9b25f03fb 100644 --- a/src/webui/www/private/views/searchplugins.html +++ b/src/webui/www/private/views/searchplugins.html @@ -95,7 +95,7 @@ const setup = () => { searchPluginsTable = new window.qBittorrent.DynamicTable.SearchPluginsTable(); searchPluginsTableContextMenu = new window.qBittorrent.ContextMenu.SearchPluginsTableContextMenu({ - targets: ".searchPluginsTableRow", + targets: "#searchPluginsTableDiv tr", menu: "searchPluginsTableMenu", actions: { Enabled: enablePlugin, @@ -104,6 +104,12 @@ offsets: calculateContextMenuOffsets() }); searchPluginsTable.setup("searchPluginsTableDiv", "searchPluginsTableFixedHeaderDiv", searchPluginsTableContextMenu); + + searchPluginsTable.dynamicTableDiv.addEventListener("dblclick", (e) => { enablePlugin(); }); + searchPluginsTable.dynamicTableDiv.addEventListener("contextmenu", (e) => { + updateSearchPluginsTableContextMenuOffset(); + }, true); + updateTable(); }; @@ -181,27 +187,7 @@ searchPluginsTableContextMenu.options.offsets = calculateContextMenuOffsets(); }; - const setupSearchPluginTableEvents = (enable) => { - const clickHandler = (e) => { enablePlugin(); }; - const menuHandler = (e) => { updateSearchPluginsTableContextMenuOffset(); }; - if (enable) { - $$(".searchPluginsTableRow").each((target) => { - target.addEventListener("dblclick", clickHandler); - target.addEventListener("contextmenu", menuHandler, true); - }); - } - else { - $$(".searchPluginsTableRow").each((target) => { - target.removeEventListener("dblclick", clickHandler); - target.removeEventListener("contextmenu", menuHandler, true); - }); - } - }; - const updateTable = () => { - // clear event listeners - setupSearchPluginTableEvents(false); - const oldPlugins = [...searchPluginsTable.getRowIds()]; // remove old rows from the table for (let i = 0; i < oldPlugins.length; ++i) { @@ -222,9 +208,6 @@ } searchPluginsTable.updateTable(); - - // add event listeners - setupSearchPluginTableEvents(true); }; return exports(); diff --git a/src/webui/www/private/views/transferlist.html b/src/webui/www/private/views/transferlist.html index 824133fb4..40dfdac7a 100644 --- a/src/webui/www/private/views/transferlist.html +++ b/src/webui/www/private/views/transferlist.html @@ -29,7 +29,7 @@ // create a context menu const contextMenu = new window.qBittorrent.ContextMenu.TorrentsTableContextMenu({ - targets: ".torrentsTableContextMenuTarget", + targets: "#torrentsTableDiv tr", menu: "torrentsTableMenu", actions: { start: (element, ref) => {