Backport changes to v5.1.x branch

PR #22268.
This commit is contained in:
Vladimir Golovnev 2025-03-16 10:39:31 +03:00 committed by GitHub
commit 76a3aba7e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 251 additions and 68 deletions

View file

@ -138,16 +138,15 @@ jobs:
- name: Install AppImage
run: |
sudo apt install libfuse2
curl \
-L \
-Z \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x \
linuxdeploy-static-x86_64.AppImage \
linuxdeploy-plugin-qt-static-x86_64.AppImage \
linuxdeploy-x86_64.AppImage \
linuxdeploy-plugin-qt-x86_64.AppImage \
linuxdeploy-plugin-appimage-x86_64.AppImage
- name: Prepare files for AppImage
@ -160,12 +159,12 @@ jobs:
- name: Package AppImage
run: |
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
NO_APPSTREAM=1 \
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
- name: Upload build artifacts
uses: actions/upload-artifact@v4

View file

@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
*
* This program is free software; you can redistribute it and/or
@ -124,6 +124,28 @@ namespace
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
const QString PARAM_CATEGORY = u"@category"_s;
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
const QString PARAM_SAVEPATH = u"@savePath"_s;
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
{
return paramName + u'=' + paramValue;
}
std::pair<QStringView, QStringView> parseParam(const QStringView param)
{
const qsizetype sepIndex = param.indexOf(u'=');
if (sepIndex >= 0)
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
return {param, {}};
}
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
@ -138,85 +160,86 @@ namespace
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(u"@savePath=" + addTorrentParams.savePath.data());
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
if (addTorrentParams.addStopped.has_value())
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
if (addTorrentParams.skipChecking)
result.append(u"@skipChecking"_s);
result.append(PARAM_SKIPCHECKING);
if (!addTorrentParams.category.isEmpty())
result.append(u"@category=" + addTorrentParams.category);
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
if (addTorrentParams.sequential)
result.append(u"@sequential"_s);
result.append(PARAM_SEQUENTIAL);
if (addTorrentParams.firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_s);
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
if (params.skipDialog.has_value())
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QString &str)
QBtCommandLineParameters parseParams(const QStringView str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
const auto [paramName, paramValue] = parseParam(param);
// Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath="))
if (paramName == PARAM_SAVEPATH)
{
addTorrentParams.savePath = Path(param.mid(10));
addTorrentParams.savePath = Path(paramValue.toString());
continue;
}
if (param.startsWith(u"@addStopped="))
if (paramName == PARAM_ADDSTOPPED)
{
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
addTorrentParams.addStopped = (paramValue.toInt() != 0);
continue;
}
if (param == u"@skipChecking")
if (paramName == PARAM_SKIPCHECKING)
{
addTorrentParams.skipChecking = true;
continue;
}
if (param.startsWith(u"@category="))
if (paramName == PARAM_CATEGORY)
{
addTorrentParams.category = param.mid(10);
addTorrentParams.category = paramValue.toString();
continue;
}
if (param == u"@sequential")
if (paramName == PARAM_SEQUENTIAL)
{
addTorrentParams.sequential = true;
continue;
}
if (param == u"@firstLastPiecePriority")
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (param.startsWith(u"@skipDialog="))
if (paramName == PARAM_SKIPDIALOG)
{
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
parsedParams.skipDialog = (paramValue.toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param);
parsedParams.torrentSources.append(param.toString());
}
return parsedParams;

View file

@ -30,8 +30,10 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QHostAddress>
#include <QList>
#include <QMap>
#include <QString>
#include "base/global.h"

View file

@ -287,6 +287,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
macosdockbadge/badger.mm
macosdockbadge/badgeview.h
macosdockbadge/badgeview.mm
macosshiftclickhandler.h
macosshiftclickhandler.cpp
macutilities.h
macutilities.mm
)

View file

@ -0,0 +1,73 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "macosshiftclickhandler.h"
#include <QMouseEvent>
#include <QTreeView>
MacOSShiftClickHandler::MacOSShiftClickHandler(QTreeView *treeView)
: QObject(treeView)
, m_treeView {treeView}
{
treeView->installEventFilter(this);
}
bool MacOSShiftClickHandler::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == m_treeView) && (event->type() == QEvent::MouseButtonPress))
{
const auto *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() != Qt::LeftButton)
return false;
const QModelIndex clickedIndex = m_treeView->indexAt(mouseEvent->position().toPoint());
if (!clickedIndex.isValid())
return false;
const Qt::KeyboardModifiers modifiers = mouseEvent->modifiers();
const bool shiftPressed = modifiers.testFlag(Qt::ShiftModifier);
if (shiftPressed && m_lastClickedIndex.isValid())
{
const QItemSelection selection(m_lastClickedIndex, clickedIndex);
const bool commandPressed = modifiers.testFlag(Qt::ControlModifier);
if (commandPressed)
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::Select | QItemSelectionModel::Rows));
else
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows));
m_treeView->selectionModel()->setCurrentIndex(clickedIndex, QItemSelectionModel::NoUpdate);
return true;
}
if (!modifiers.testFlags(Qt::AltModifier | Qt::MetaModifier))
m_lastClickedIndex = clickedIndex;
}
return QObject::eventFilter(watched, event);
}

View file

@ -0,0 +1,50 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QObject>
#include <QPersistentModelIndex>
class QTreeView;
// Workaround for QTBUG-115838: Shift-click range selection not working properly on macOS
class MacOSShiftClickHandler final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSShiftClickHandler)
public:
explicit MacOSShiftClickHandler(QTreeView *treeView);
private:
bool eventFilter(QObject *watched, QEvent *event) override;
QTreeView *m_treeView = nullptr;
QPersistentModelIndex m_lastClickedIndex;
};

View file

@ -74,6 +74,7 @@
#include "utils.h"
#ifdef Q_OS_MACOS
#include "macosshiftclickhandler.h"
#include "macutilities.h"
#endif
@ -158,6 +159,7 @@ TransferListWidget::TransferListWidget(IGUIApplication *app, QWidget *parent)
setDropIndicatorShown(true);
#if defined(Q_OS_MACOS)
setAttribute(Qt::WA_MacShowFocusRect, false);
new MacOSShiftClickHandler(this);
#endif
header()->setFirstSectionMovable(true);
header()->setStretchLastSection(false);

View file

@ -80,7 +80,33 @@ inline QHash<QString, UIThemeColor> defaultUIThemeColors()
{u"TransferList.StoppedUploading"_s, {Color::Primer::Light::doneFg, Color::Primer::Dark::doneFg}},
{u"TransferList.Moving"_s, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}},
{u"TransferList.MissingFiles"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}
{u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"Palette.Window"_s, {{}, {}}},
{u"Palette.WindowText"_s, {{}, {}}},
{u"Palette.Base"_s, {{}, {}}},
{u"Palette.AlternateBase"_s, {{}, {}}},
{u"Palette.Text"_s, {{}, {}}},
{u"Palette.ToolTipBase"_s, {{}, {}}},
{u"Palette.ToolTipText"_s, {{}, {}}},
{u"Palette.BrightText"_s, {{}, {}}},
{u"Palette.Highlight"_s, {{}, {}}},
{u"Palette.HighlightedText"_s, {{}, {}}},
{u"Palette.Button"_s, {{}, {}}},
{u"Palette.ButtonText"_s, {{}, {}}},
{u"Palette.Link"_s, {{}, {}}},
{u"Palette.LinkVisited"_s, {{}, {}}},
{u"Palette.Light"_s, {{}, {}}},
{u"Palette.Midlight"_s, {{}, {}}},
{u"Palette.Mid"_s, {{}, {}}},
{u"Palette.Dark"_s, {{}, {}}},
{u"Palette.Shadow"_s, {{}, {}}},
{u"Palette.WindowTextDisabled"_s, {{}, {}}},
{u"Palette.TextDisabled"_s, {{}, {}}},
{u"Palette.ToolTipTextDisabled"_s, {{}, {}}},
{u"Palette.BrightTextDisabled"_s, {{}, {}}},
{u"Palette.HighlightedTextDisabled"_s, {{}, {}}},
{u"Palette.ButtonTextDisabled"_s, {{}, {}}}
};
}

View file

@ -49,7 +49,7 @@ void RSSController::addFolderAction()
{
requireParams({u"path"_s});
const QString path = params()[u"path"_s].trimmed();
const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFolder(path);
if (!result)
throw APIError(APIErrorType::Conflict, result.error());
@ -59,8 +59,8 @@ void RSSController::addFeedAction()
{
requireParams({u"url"_s, u"path"_s});
const QString url = params()[u"url"_s].trimmed();
const QString path = params()[u"path"_s].trimmed();
const QString url = params()[u"url"_s];
const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path));
if (!result)
throw APIError(APIErrorType::Conflict, result.error());
@ -70,8 +70,8 @@ void RSSController::setFeedURLAction()
{
requireParams({u"path"_s, u"url"_s});
const QString path = params()[u"path"_s].trimmed();
const QString url = params()[u"url"_s].trimmed();
const QString path = params()[u"path"_s];
const QString url = params()[u"url"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(path, url);
if (!result)
throw APIError(APIErrorType::Conflict, result.error());
@ -81,7 +81,7 @@ void RSSController::removeItemAction()
{
requireParams({u"path"_s});
const QString path = params()[u"path"_s].trimmed();
const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->removeItem(path);
if (!result)
throw APIError(APIErrorType::Conflict, result.error());
@ -91,8 +91,8 @@ void RSSController::moveItemAction()
{
requireParams({u"itemPath"_s, u"destPath"_s});
const QString itemPath = params()[u"itemPath"_s].trimmed();
const QString destPath = params()[u"destPath"_s].trimmed();
const QString itemPath = params()[u"itemPath"_s];
const QString destPath = params()[u"destPath"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->moveItem(itemPath, destPath);
if (!result)
throw APIError(APIErrorType::Conflict, result.error());
@ -146,8 +146,8 @@ void RSSController::setRuleAction()
{
requireParams({u"ruleName"_s, u"ruleDef"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()};
const QByteArray ruleDef {params()[u"ruleDef"_s].trimmed().toUtf8()};
const QString ruleName {params()[u"ruleName"_s]};
const QByteArray ruleDef {params()[u"ruleDef"_s].toUtf8()};
const auto jsonObj = QJsonDocument::fromJson(ruleDef).object();
RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName));
@ -157,8 +157,8 @@ void RSSController::renameRuleAction()
{
requireParams({u"ruleName"_s, u"newRuleName"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()};
const QString newRuleName {params()[u"newRuleName"_s].trimmed()};
const QString ruleName {params()[u"ruleName"_s]};
const QString newRuleName {params()[u"newRuleName"_s]};
RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName);
}
@ -167,7 +167,7 @@ void RSSController::removeRuleAction()
{
requireParams({u"ruleName"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()};
const QString ruleName {params()[u"ruleName"_s]};
RSS::AutoDownloader::instance()->removeRule(ruleName);
}

View file

@ -100,6 +100,7 @@ ol {
.dynamicTableDiv,
.mochaContentWrapper,
.panel,
.scrollableMenu,
#rssDetailsView {
scrollbar-width: thin;
}

View file

@ -21,7 +21,7 @@
} = window.MUI.Windows.instances["multiRenamePage"];
const bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#bulkRenameFilesTableDiv tr",
targets: "#bulkRenameFilesTableDiv tbody tr",
menu: "multiRenameFilesMenu",
actions: {
ToggleSelection: (element, ref) => {

View file

@ -294,6 +294,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.updateTableHeaders();
this.tableBody.replaceChildren();
this.updateTable(true);
this.reselectRows(this.selectedRowsIds());
}
if (this.currentHeaderAction === "drag") {
resetElementBorderStyle(el);
@ -750,10 +751,7 @@ window.qBittorrent.DynamicTable ??= (() => {
reselectRows: function(rowIds) {
this.deselectAll();
this.selectedRows = rowIds.slice();
for (const tr of this.getTrs()) {
if (rowIds.includes(tr.rowId))
tr.classList.add("selected");
}
this.setRowClass();
},
setRowClass: function() {
@ -1752,7 +1750,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.append(span);
}
span.style.backgroundImage = `url('images/flags/${country_code ?? "xx"}.svg')`;
span.style.backgroundImage = `url('images/flags/${country_code || "xx"}.svg')`;
span.textContent = country;
td.title = country;
};
@ -2058,7 +2056,11 @@ window.qBittorrent.DynamicTable ??= (() => {
break;
}
td.className = statusClass;
for (const c of [...td.classList]) {
if (c.startsWith("tracker"))
td.classList.remove(c);
}
td.classList.add(statusClass);
td.textContent = status;
td.title = status;
};

View file

@ -575,7 +575,7 @@ window.qBittorrent.PropFiles ??= (() => {
};
const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#torrentFilesTableDiv tr",
targets: "#torrentFilesTableDiv tbody tr",
menu: "torrentFilesMenu",
actions: {
Rename: (element, ref) => {

View file

@ -196,7 +196,7 @@ window.qBittorrent.PropTrackers ??= (() => {
if (current_hash.length === 0)
return;
const trackerUrl = encodeURIComponent(element.childNodes[1].textContent);
const trackerUrl = encodeURIComponent(torrentTrackersTable.selectedRowsIds()[0]);
new MochaUI.Window({
id: "trackersPage",
icon: "images/qbittorrent-tray.svg",

View file

@ -97,8 +97,8 @@ window.qBittorrent.Search ??= (() => {
}
},
offsets: {
x: -15,
y: -53
x: 2,
y: -60
},
onShow: function() {
setActiveTab(this.options.element);
@ -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: "#searchResultsTableDiv tr",
targets: "#searchResultsTableDiv tbody tr",
menu: "searchResultsTableMenu",
actions: {
Download: downloadSearchTorrent,
@ -184,7 +184,10 @@ window.qBittorrent.Search ??= (() => {
closeTabElem.src = "images/application-exit.svg";
closeTabElem.width = "10";
closeTabElem.height = "10";
closeTabElem.addEventListener("click", function(e) { qBittorrent.Search.closeSearchTab(this); });
closeTabElem.addEventListener("click", function(e) {
e.stopPropagation();
closeSearchTab(this);
});
tabElem.prepend(closeTabElem);
tabElem.appendChild(getStatusIconElement("QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]", "images/queued.svg"));

View file

@ -98,7 +98,7 @@
}
},
offsets: {
x: -15,
x: 0,
y: 2
},
onShow: function() {

View file

@ -206,7 +206,7 @@
});
const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: ":is(#logMessageView, #logPeerView) tr",
targets: ":is(#logMessageTableDiv, #logPeerTableDiv) tbody tr",
menu: "logTableMenu",
actions: {
Clear: () => {

View file

@ -218,7 +218,7 @@
$("rssFetchingDisabled").classList.remove("invisible");
const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({
targets: "#rssFeedTableDiv tr",
targets: "#rssFeedTableDiv tbody tr",
menu: "rssFeedMenu",
actions: {
update: (el) => {
@ -288,7 +288,7 @@
rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu);
const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({
targets: "#rssArticleTableDiv tr",
targets: "#rssArticleTableDiv tbody tr",
menu: "rssArticleMenu",
actions: {
Download: (el) => {

View file

@ -94,7 +94,7 @@
const setup = () => {
searchPluginsTable = new window.qBittorrent.DynamicTable.SearchPluginsTable();
searchPluginsTableContextMenu = new window.qBittorrent.ContextMenu.SearchPluginsTableContextMenu({
targets: "#searchPluginsTableDiv tr",
targets: "#searchPluginsTableDiv tbody tr",
menu: "searchPluginsTableMenu",
actions: {
Enabled: enablePlugin,

View file

@ -29,7 +29,7 @@
// create a context menu
const contextMenu = new window.qBittorrent.ContextMenu.TorrentsTableContextMenu({
targets: "#torrentsTableDiv tr",
targets: "#torrentsTableDiv tbody tr",
menu: "torrentsTableMenu",
actions: {
start: (element, ref) => {