Add new Add Torrent experience to WebUI

This dialog more closely mimics the dialog presented by the GUI. It includes information about the contents of a torrent.

Signed-off-by: Thomas Piccirello <thomas@piccirello.com>
This commit is contained in:
Thomas Piccirello 2024-09-19 13:55:12 -07:00
commit 5963fc2032
No known key found for this signature in database
14 changed files with 607 additions and 413 deletions

View file

@ -155,7 +155,8 @@ Required by:
width: 5px;
}
#desktopNavbar li ul li a {
#desktopNavbar li ul li a,
#desktopNavbar li ul li div.anchor {
color: var(--color-text-default);
font-weight: normal;
min-width: 155px;
@ -163,7 +164,8 @@ Required by:
position: relative;
}
#desktopNavbar li ul li a:hover {
#desktopNavbar li ul li a:hover,
#desktopNavbar li ul li div.anchor:hover {
background-color: var(--color-background-hover);
color: var(--color-text-white);
}

View file

@ -184,6 +184,16 @@ div.mochaToolbarWrapper.bottom {
width: 16px;
}
.mochaErrorIcon {
background: url("../images/error.svg") no-repeat;
background-size: 16px;
bottom: 7px;
height: 16px;
left: 6px;
position: absolute;
width: 16px;
}
.mochaIframe {
width: 100%;
}

View file

@ -672,6 +672,17 @@ td.generalLabel {
width: 1px;
}
td.fullWidth {
box-sizing: border-box;
max-width: none;
width: 100%;
word-break: break-all;
}
td.noWrap {
white-space: nowrap;
}
#tristate_cb {
margin-bottom: 0;
margin-top: 0;
@ -835,7 +846,8 @@ td.statusBarSeparator {
color: var(--color-text-green);
}
#torrentFilesTableDiv .dynamicTable tr.nonAlt:hover {
#torrentFilesTableDiv .dynamicTable tr.nonAlt:hover,
#addTorrentFilesTableDiv .dynamicTable tr.nonAlt:hover {
background-color: var(--color-background-hover);
color: var(--color-text-white);
}

View file

@ -3,17 +3,17 @@
<head>
<meta charset="UTF-8">
<title>QBT_TR(Add Torrent Links)QBT_TR[CONTEXT=downloadFromURL]</title>
<title>QBT_TR(Add Torrent Links)QBT_TR[CONTEXT=DownloadFromURLDialog]</title>
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css">
<link rel="stylesheet" href="css/Window.css?v=${CACHEID}" type="text/css">
<script defer src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.js?v=${CACHEID}"></script>
<script defer src="scripts/download.js?v=${CACHEID}"></script>
<script defer src="scripts/pathAutofill.js?v=${CACHEID}"></script>
<script>
"use strict";
window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("urls").focus();
const encodedUrls = new URLSearchParams(window.location.search).get("urls");
if (encodedUrls !== null) {
const urls = encodedUrls.split("|").map(decodeURIComponent);
@ -21,172 +21,42 @@
document.getElementById("urls").value = urls.join("\n");
}
let submitted = false;
document.getElementById("submitButton").addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("downloadForm").addEventListener("submit", (event) => {
document.getElementById("startTorrentHidden").value = document.getElementById("startTorrent").checked ? "false" : "true";
const urls = document.getElementById("urls").value.split("\n").map(s => s.trim()).filter(s => (s.length > 0));
if (urls.length === 0)
return;
document.getElementById("dlLimitHidden").value = Number(document.getElementById("dlLimitText").value) * 1024;
document.getElementById("upLimitHidden").value = Number(document.getElementById("upLimitText").value) * 1024;
for (const url of urls)
window.parent.qBittorrent.Client.createAddTorrentWindow("QBT_TR(Magnet link)QBT_TR[CONTEXT=DownloadFromURLDialog]", url);
document.getElementById("download_spinner").style.display = "block";
submitted = true;
window.parent.qBittorrent.Client.closeFrameWindow(window);
});
document.getElementById("download_frame").addEventListener("load", (event) => {
if (submitted)
window.parent.qBittorrent.Client.closeFrameWindow(window);
window.addEventListener("keydown", (event) => {
switch (event.key) {
case "Escape":
event.preventDefault();
window.parent.qBittorrent.Client.closeFrameWindow(window);
break;
}
});
window.qBittorrent.pathAutofill.attachPathAutofill();
});
</script>
</head>
<body>
<iframe id="download_frame" name="download_frame" class="invisible" title="" src="about:blank"></iframe>
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="downloadForm" style="text-align: center;" target="download_frame" autocorrect="off" autocapitalize="none">
<div style="text-align: center;">
<br>
<h2><label for="urls">QBT_TR(Add torrent links)QBT_TR[CONTEXT=AddNewTorrentDialog]</label></h2>
<textarea id="urls" rows="10" name="urls"></textarea>
<p><i>QBT_TR(One link per line (HTTP links, Magnet links and info-hashes are supported))QBT_TR[CONTEXT=AddNewTorrentDialog]</i></p>
<fieldset class="settings" style="border: 0; text-align: left; margin-top: 6px;">
<legend>QBT_TR(Torrent options)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="margin: auto;">
<tbody>
<tr>
<td>
<label for="autoTMM">QBT_TR(Torrent Management Mode:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="autoTMM" name="autoTMM" onchange="qBittorrent.Download.changeTMM(this)">
<option selected value="false">QBT_TR(Manual)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="true">QBT_TR(Automatic)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="savepath">QBT_TR(Save files to location:)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="text" id="savepath" name="savepath" class="pathDirectory" style="width: 16em;">
</td>
</tr>
<tr>
<td>
<label for="rename">QBT_TR(Rename torrent)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="text" id="rename" name="rename" style="width: 16em;">
</td>
</tr>
<tr>
<td>
<label id="categoryLabel" for="categorySelect">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<div class="select-watched-folder-editable">
<select id="categorySelect" onchange="qBittorrent.Download.changeCategorySelect(this)">
<option selected value="\other"></option>
</select>
<input type="text" name="category" aria-labelledby="categoryLabel">
</div>
</td>
</tr>
<tr>
<td>
<label for="startTorrent">QBT_TR(Start torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="hidden" id="startTorrentHidden" name="stopped">
<input type="checkbox" id="startTorrent">
</td>
</tr>
<tr>
<td>
<label for="addToTopOfQueue">QBT_TR(Add to top of queue)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="addToTopOfQueue" name="addToTopOfQueue" value="true">
</td>
</tr>
<tr>
<td>
<label for="stopCondition">QBT_TR(Stop condition:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="stopCondition" name="stopCondition">
<option selected value="None">QBT_TR(None)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="MetadataReceived">QBT_TR(Metadata received)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="FilesChecked">QBT_TR(Files checked)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="skip_checking">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="skip_checking" name="skip_checking" value="true">
</td>
</tr>
<tr>
<td>
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="contentLayout" name="contentLayout">
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="sequentialDownload">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=TransferListWidget]</label>
</td>
<td>
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true">
</td>
</tr>
<tr>
<td>
<label for="firstLastPiecePrio">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=TransferListWidget]</label>
</td>
<td>
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true">
</td>
</tr>
<tr>
<td>
<label for="dlLimitText">QBT_TR(Limit download rate)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="hidden" id="dlLimitHidden" name="dlLimit">
<input type="text" id="dlLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
<tr>
<td>
<label for="upLimitText">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="hidden" id="upLimitHidden" name="upLimit">
<input type="text" id="upLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
</tbody>
</table>
<div id="submitbutton" style="margin-top: 12px; text-align: center;">
<button type="submit" id="submitButton">QBT_TR(Download)QBT_TR[CONTEXT=downloadFromURL]</button>
</div>
</fieldset>
<div style="text-align: center;">
<br>
<h2 class="vcenter">QBT_TR(Add torrent links)QBT_TR[CONTEXT=DownloadFromURLDialog]</h2>
<textarea id="urls" name="urls" rows="10" required style="width: 80%;" aria-label="QBT_TR(URLs)QBT_TR[CONTEXT=DownloadFromURLDialog]"></textarea>
<p><i>QBT_TR(One link per line (HTTP links, Magnet links and info-hashes are supported))QBT_TR[CONTEXT=DownloadFromURLDialog]</i></p>
<div id="submitbutton" style="margin-top: 12px; text-align: center;">
<button type="submit" id="submitButton">QBT_TR(Download)QBT_TR[CONTEXT=DownloadFromURLDialog]</button>
</div>
</form>
<div id="download_spinner" class="mochaSpinner"></div>
</div>
</body>
</html>

View file

@ -60,7 +60,15 @@
<li>
<a class="returnFalse">QBT_TR(File)QBT_TR[CONTEXT=MainWindow]</a>
<ul>
<li><a id="uploadLink"><img class="MyMenuIcon" alt="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" src="images/list-add.svg" width="16" height="16">QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]</a></li>
<li>
<div id="uploadLink" class="anchor">
<label id="uploadLinkLabel" for="fileselectLink" style="cursor: pointer;">
<img class="MyMenuIcon" alt="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" src="images/list-add.svg" width="16" height="16">
<span>QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]</span>
</label>
<input type="file" id="fileselectLink" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple style="display: none;">
</div>
</li>
<li><a id="downloadLink"><img class="MyMenuIcon" alt="QBT_TR(Add Torrent Link...)QBT_TR[CONTEXT=MainWindow]" src="images/insert-link.svg" width="16" height="16">QBT_TR(Add Torrent Link...)QBT_TR[CONTEXT=MainWindow]</a></li>
<li class="divider"><a id="logoutLink"><img class="MyMenuIcon" alt="QBT_TR(Logout)QBT_TR[CONTEXT=HttpServer]" src="images/system-log-out.svg" width="16" height="16">QBT_TR(Logout)QBT_TR[CONTEXT=HttpServer]</a></li>
<li><a id="shutdownLink"><img class="MyMenuIcon" alt="QBT_TR(Exit qBittorrent)QBT_TR[CONTEXT=HttpServer]" src="images/application-exit.svg" width="16" height="16">QBT_TR(Exit qBittorrent)QBT_TR[CONTEXT=HttpServer]</a></li>
@ -114,8 +122,13 @@
</div>
<div id="mochaToolbar">
&nbsp;&nbsp;
<a id="uploadButton"><img class="mochaToolButton" title="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" src="images/list-add.svg" alt="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" width="24" height="24"></a>
<a id="downloadButton"><img class="mochaToolButton" title="QBT_TR(Add Torrent Link...)QBT_TR[CONTEXT=MainWindow]" src="images/insert-link.svg" alt="QBT_TR(Add Torrent Link...)QBT_TR[CONTEXT=MainWindow]" width="24" height="24"></a>
<div id="uploadButton" style="display: inline-block;">
<label for="fileselectButton" style="cursor: pointer;">
<img class="mochaToolButton" title="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" src="images/list-add.svg" alt="QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow]" width="24" height="24">
</label>
<input type="file" id="fileselectButton" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple style="display: none;">
</div>
<a id="deleteButton"><img class="mochaToolButton" title="QBT_TR(Remove)QBT_TR[CONTEXT=TransferListWidget]" src="images/list-remove.svg" alt="QBT_TR(Remove)QBT_TR[CONTEXT=TransferListWidget]" width="24" height="24"></a>
<a id="startButton" class="divider"><img class="mochaToolButton" title="QBT_TR(Start)QBT_TR[CONTEXT=TransferListWidget]" src="images/torrent-start.svg" alt="QBT_TR(Start)QBT_TR[CONTEXT=TransferListWidget]" width="24" height="24"></a>
<a id="stopButton"><img class="mochaToolButton" title="QBT_TR(Stop)QBT_TR[CONTEXT=TransferListWidget]" src="images/torrent-stop.svg" alt="QBT_TR(Stop)QBT_TR[CONTEXT=TransferListWidget]" width="24" height="24"></a>

View file

@ -24,16 +24,22 @@
"use strict";
window.qBittorrent ??= {};
window.qBittorrent.Download ??= (() => {
window.qBittorrent.AddTorrent ??= (() => {
const exports = () => {
return {
changeCategorySelect: changeCategorySelect,
changeTMM: changeTMM
changeTMM: changeTMM,
loadMetadata: loadMetadata,
metadataCompleted: metadataCompleted,
populateMetadata: populateMetadata,
setWindowId: setWindowId
};
};
let categories = {};
let defaultSavePath = "";
let windowId = "";
let source;
const getCategories = () => {
fetch("api/v2/torrents/categories", {
@ -131,6 +137,80 @@ window.qBittorrent.Download ??= (() => {
}
};
let loadMetadataTimer;
const loadMetadata = (sourceUrl = undefined) => {
if (sourceUrl)
source = sourceUrl;
fetch("api/v2/torrents/fetchMetadata", {
method: "POST",
body: new URLSearchParams({
source: source
})
})
.then(async (response) => {
if (!response.ok)
metadataFailed();
const data = await response.json();
populateMetadata(data);
if (response.status === 200)
metadataCompleted();
else
loadMetadataTimer = loadMetadata.delay(1000);
});
};
const metadataCompleted = (showDownloadButton = true) => {
clearTimeout(loadMetadataTimer);
document.getElementById("metadataStatus").destroy();
document.getElementById("loading_spinner").style.display = "none";
if (showDownloadButton)
document.getElementById("saveTorrent").classList.remove("invisible");
};
const metadataFailed = () => {
clearTimeout(loadMetadataTimer);
document.getElementById("metadataStatus").textContent = "Metadata retrieval failed";
document.getElementById("metadataStatus").classList.add("red");
document.getElementById("loading_spinner").style.display = "none";
document.getElementById("error_icon").classList.remove("invisible");
};
const populateMetadata = (metadata) => {
// update window title
if (metadata.info?.name)
window.parent.document.getElementById(`${windowId}_title`).textContent = metadata.info.name;
document.getElementById("infoHashV1").textContent = metadata.infohash_v1 || "N/A";
document.getElementById("infoHashV2").textContent = metadata.infohash_v2 || "N/A";
if (metadata.info?.length)
document.getElementById("size").textContent = window.qBittorrent.Misc.friendlyUnit(metadata.info.length, false);
if (metadata.creation_date && (metadata.creation_date > 1))
document.getElementById("createdDate").textContent = new Date(metadata.creation_date * 1000).toLocaleString();
if (metadata.comment)
document.getElementById("comment").textContent = metadata.comment;
if (metadata.info?.files) {
const files = metadata.info.files.map((file, index) => ({
index: index,
name: file.path,
size: file.length,
priority: window.qBittorrent.FileTree.FilePriority.Normal,
}));
window.qBittorrent.TorrentContent.updateData(files);
}
};
const setWindowId = (id) => {
windowId = id;
};
window.addEventListener("load", async (event) => {
// user might load this page directly (via browser magnet handler)
// so wait for crucial initialization to complete
@ -142,4 +222,4 @@ window.qBittorrent.Download ??= (() => {
return exports();
})();
Object.freeze(window.qBittorrent.Download);
Object.freeze(window.qBittorrent.AddTorrent);

View file

@ -42,7 +42,9 @@ window.qBittorrent.Client ??= (() => {
showLogViewer: showLogViewer,
isShowSearchEngine: isShowSearchEngine,
isShowRssReader: isShowRssReader,
isShowLogViewer: isShowLogViewer
isShowLogViewer: isShowLogViewer,
createAddTorrentWindow: createAddTorrentWindow,
uploadTorrentFiles: uploadTorrentFiles
};
};
@ -121,6 +123,90 @@ window.qBittorrent.Client ??= (() => {
return showingLogViewer;
};
const createAddTorrentWindow = (title, source, metadata = undefined) => {
const isFirefox = navigator.userAgent.includes("Firefox");
const isSafari = navigator.userAgent.includes("AppleWebKit") && !navigator.userAgent.includes("Chrome");
let height = 855;
if (isSafari)
height -= 40;
else if (isFirefox)
height -= 10;
const staticId = "uploadPage";
const id = `${staticId}-${encodeURIComponent(source)}`;
const contentURL = new URL("addtorrent.html", window.location);
contentURL.search = new URLSearchParams({
source: source,
fetch: metadata === undefined,
windowId: id
});
new MochaUI.Window({
id: id,
icon: "images/qbittorrent-tray.svg",
title: title,
loadMethod: "iframe",
contentURL: contentURL.toString(),
scrollbars: true,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(staticId, 980),
height: loadWindowHeight(staticId, height),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(staticId, id);
}),
onContentLoaded: () => {
if (metadata !== undefined)
document.getElementById(`${id}_iframe`).contentWindow.postMessage(metadata, window.origin);
}
});
};
const uploadTorrentFiles = (files) => {
const fileNames = [];
const formData = new FormData();
for (const file of files) {
fileNames.push(file.name);
formData.append("file", file);
}
const xhr = new XMLHttpRequest();
xhr.open("POST", "api/v2/torrents/parseMetadata");
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4) { // DONE state
if ((xhr.status >= 200) && (xhr.status < 300)) {
let response;
try {
response = JSON.parse(xhr.responseText);
}
catch (error) {
alert("QBT_TR(Unable to parse response)QBT_TR[CONTEXT=HttpServer]");
return;
}
for (const fileName of fileNames) {
let title = fileName;
const metadata = response[fileName];
if (metadata !== undefined)
title = metadata.name;
createAddTorrentWindow(title, metadata.hash, metadata);
}
}
else if (xhr.responseText) {
alert(xhr.responseText);
}
}
});
xhr.addEventListener("error", () => {
if (xhr.responseText)
alert(xhr.responseText);
});
xhr.send(formData);
};
return exports();
})();
Object.freeze(window.qBittorrent.Client);
@ -1677,32 +1763,11 @@ window.addEventListener("DOMContentLoaded", (event) => {
// can't handle folder due to cannot put the filelist (from dropped folder)
// to <input> `files` field
for (const item of ev.dataTransfer.items) {
if (item.webkitGetAsEntry().isDirectory)
if ((item.kind !== "file") || (item.webkitGetAsEntry().isDirectory))
return;
}
const id = "uploadPage";
new MochaUI.Window({
id: id,
icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
loadMethod: "iframe",
contentURL: "upload.html",
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
scrollbars: true,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 500),
height: loadWindowHeight(id, 460),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(id);
}),
onContentLoaded: () => {
const fileInput = document.getElementById(`${id}_iframe`).contentDocument.getElementById("fileselect");
fileInput.files = droppedFiles;
}
});
window.qBittorrent.Client.uploadTorrentFiles(droppedFiles);
}
const droppedText = ev.dataTransfer.getData("text");
@ -1720,32 +1785,8 @@ window.addEventListener("DOMContentLoaded", (event) => {
|| ((str.length === 32) && !(/[^2-7A-Z]/i.test(str))); // v1 Base32 encoded SHA-1 info-hash
});
if (urls.length <= 0)
return;
const id = "downloadPage";
const contentURL = new URL("download.html", window.location);
contentURL.search = new URLSearchParams({
urls: urls.map(encodeURIComponent).join("|")
});
new MochaUI.Window({
id: id,
icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
loadMethod: "iframe",
contentURL: contentURL.toString(),
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
scrollbars: true,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 500),
height: loadWindowHeight(id, 600),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(id);
})
});
for (const url of urls)
qBittorrent.Client.createAddTorrentWindow(url, url);
}
});
};

View file

@ -44,6 +44,7 @@ window.qBittorrent.DynamicTable ??= (() => {
TorrentTrackersTable: TorrentTrackersTable,
BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable,
TorrentFilesTable: TorrentFilesTable,
AddTorrentFilesTable: AddTorrentFilesTable,
LogMessageTable: LogMessageTable,
LogPeerTable: LogPeerTable,
RssFeedTable: RssFeedTable,
@ -66,6 +67,9 @@ window.qBittorrent.DynamicTable ??= (() => {
let DynamicTableHeaderContextMenuClass = null;
if (typeof LocalPreferences === "undefined")
window.LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
class DynamicTable {
setup(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu, useVirtualList = false) {
this.dynamicTableDivId = dynamicTableDivId;
@ -2894,16 +2898,18 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns["size"].updateTd = displaySize;
// progress
this.columns["progress"].updateTd = function(td, row) {
const value = Number(this.getRowValue(row));
if (this.columns["progress"]) {
this.columns["progress"].updateTd = function(td, row) {
const value = Number(this.getRowValue(row));
const progressBar = td.firstElementChild;
if (progressBar === null)
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value));
else
progressBar.setValue(value);
};
this.columns["progress"].staticWidth = 100;
const progressBar = td.firstElementChild;
if (progressBar === null)
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value));
else
progressBar.setValue(value);
};
this.columns["progress"].staticWidth = 100;
}
// priority
this.columns["priority"].updateTd = function(td, row) {
@ -2919,8 +2925,10 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns["priority"].staticWidth = 140;
// remaining, availability
this.columns["remaining"].updateTd = displaySize;
this.columns["availability"].updateTd = displayPercentage;
if (this.columns["remaining"])
this.columns["remaining"].updateTd = displaySize;
if (this.columns["availability"])
this.columns["availability"].updateTd = displayPercentage;
}
#sortNodesByColumn(root, column) {
@ -3075,6 +3083,17 @@ window.qBittorrent.DynamicTable ??= (() => {
}
}
class AddTorrentFilesTable extends TorrentFilesTable {
initColumns() {
this.newColumn("checked", "", "", 50, true);
this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TrackerListWidget]", 190, true);
this.newColumn("size", "", "QBT_TR(Total Size)QBT_TR[CONTEXT=TrackerListWidget]", 75, true);
this.newColumn("priority", "", "QBT_TR(Download Priority)QBT_TR[CONTEXT=TrackerListWidget]", 140, true);
this.initColumnsFunctions();
}
}
class RssFeedTable extends DynamicTable {
initColumns() {
this.newColumn("state_icon", "", "", 30, true);
@ -3253,7 +3272,8 @@ window.qBittorrent.DynamicTable ??= (() => {
if (!tr)
return;
showDownloadPage([this.getRow(tr.rowId).full_data.torrentURL]);
const { name, torrentURL } = this._this.rows.get(this.rowId).full_data;
qBittorrent.Client.createAddTorrentWindow(name, torrentURL);
});
}
updateRow(tr, fullUpdate) {

View file

@ -150,10 +150,11 @@ let setQueuePositionFN = () => {};
let exportTorrentFN = () => {};
const initializeWindows = () => {
saveWindowSize = (windowId) => {
const size = document.getElementById(windowId).getSize();
LocalPreferences.set(`window_${windowId}_width`, size.x);
LocalPreferences.set(`window_${windowId}_height`, size.y);
saveWindowSize = (windowName, windowId = windowName) => {
const windowInstance = MochaUI.Windows.instances[windowId];
const size = windowInstance.contentWrapperEl.getSize();
LocalPreferences.set(`window_${windowName}_width`, size.x);
LocalPreferences.set(`window_${windowName}_height`, size.y);
};
loadWindowWidth = (windowId, defaultValue) => {
@ -193,14 +194,13 @@ const initializeWindows = () => {
title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
loadMethod: "iframe",
contentURL: contentURL.toString(),
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
scrollbars: true,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 500),
height: loadWindowHeight(id, 600),
height: loadWindowHeight(id, 300),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(id);
})
@ -283,31 +283,30 @@ const initializeWindows = () => {
});
});
addClickEvent("upload", (e) => {
e.preventDefault();
e.stopPropagation();
const id = "uploadPage";
new MochaUI.Window({
id: id,
icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
loadMethod: "iframe",
contentURL: "upload.html",
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
scrollbars: true,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 500),
height: loadWindowHeight(id, 460),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(id);
})
});
updateMainData();
document.querySelector("#uploadButton #fileselectButton").addEventListener("click", function() {
// clear the value so that reselecting the same file(s) still triggers the 'change' event
this.value = null;
});
// make the entire anchor tag trigger the input, despite the input's label not spanning the entire anchor
document.getElementById("uploadLink").addEventListener("click", (e) => {
// clear the value so that reselecting the same file(s) still triggers the 'change' event
if (e.target === document.getElementById("fileselectLink")) {
e.target.value = null;
}
else {
e.preventDefault();
document.getElementById("fileselectLink").click();
}
});
document.querySelectorAll("#uploadButton #fileselectButton, #uploadLink #fileselectLink").forEach((element) => element.addEventListener("change", () => {
if (element.files.length === 0)
return;
window.qBittorrent.Client.uploadTorrentFiles(element.files);
}));
globalUploadLimitFN = () => {
const contentURL = new URL("speedlimit.html", window.location);
contentURL.search = new URLSearchParams({
@ -1356,4 +1355,8 @@ const initializeWindows = () => {
e.stopPropagation();
});
}
const userAgent = (navigator.userAgentData?.platform ?? navigator.platform).toLowerCase();
if (userAgent.includes("ipad") || userAgent.includes("iphone") || (userAgent.includes("mac") && (navigator.maxTouchPoints > 1)))
document.getElementById("fileselect").accept = ".torrent";
};

View file

@ -41,7 +41,7 @@ window.qBittorrent.PropFiles ??= (() => {
const onFilePriorityChanged = (fileIds, priority) => {
// ignore folders
fileIds = fileIds.map(id => parseInt(id, 10)).filter(id => !window.qBittorrent.TorrentContent.isFolder(id));
fileIds = fileIds.map(id => Number(id)).filter(id => !window.qBittorrent.TorrentContent.isFolder(id));
clearTimeout(loadTorrentFilesDataTimer);
loadTorrentFilesDataTimer = -1;

View file

@ -559,15 +559,10 @@ window.qBittorrent.Search ??= (() => {
};
const downloadSearchTorrent = () => {
const urls = [];
for (const rowID of searchResultsTable.selectedRowsIds())
urls.push(searchResultsTable.getRow(rowID).full_data.fileUrl);
// only proceed if at least 1 row was selected
if (!urls.length)
return;
showDownloadPage(urls);
for (const rowID of searchResultsTable.selectedRowsIds()) {
const { fileName, fileUrl } = searchResultsTable.getRow(rowID).full_data;
qBittorrent.Client.createAddTorrentWindow(fileName, fileUrl);
}
};
const manageSearchPlugins = () => {

View file

@ -3,27 +3,55 @@
<head>
<meta charset="UTF-8">
<title>QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]</title>
<title>QBT_TR(Add torrent)QBT_TR[CONTEXT=HttpServer]</title>
<link rel="stylesheet" type="text/css" href="css/dynamicTable.css?v=${CACHEID}">
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css">
<link rel="stylesheet" href="css/Window.css?v=${CACHEID}" type="text/css">
<script defer src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
<script defer src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
<script defer src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.js?v=${CACHEID}"></script>
<script defer src="scripts/download.js?v=${CACHEID}"></script>
<script defer src="scripts/addtorrent.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/pathAutofill.js?v=${CACHEID}"></script>
<script defer src="scripts/file-tree.js?v=${CACHEID}"></script>
<script defer src="scripts/filesystem.js?v=${CACHEID}"></script>
<script defer src="scripts/contextmenu.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/torrent-content.js?v=${CACHEID}"></script>
<script>
"use strict";
window.addEventListener("DOMContentLoaded", (event) => {
const searchParams = new URLSearchParams(window.location.search);
const source = searchParams.get("source");
if (!source)
return;
document.getElementById("urls").value = source;
// fetch unless explicitly told not to
const fetchMetadata = searchParams.get("fetch") !== "false";
let submitted = false;
const windowId = searchParams.get("windowId");
window.qBittorrent.AddTorrent.setWindowId(windowId);
document.getElementById("uploadForm").addEventListener("submit", (event) => {
document.getElementById("startTorrentHidden").value = document.getElementById("startTorrent").checked ? "false" : "true";
document.getElementById("dlLimitHidden").value = Number(document.getElementById("dlLimitText").value) * 1024;
document.getElementById("upLimitHidden").value = Number(document.getElementById("upLimitText").value) * 1024;
document.getElementById("upload_spinner").style.display = "block";
document.getElementById("loading_spinner").style.display = "block";
submitted = true;
// ignore folders
const priorities = Array.prototype.filter.call(
document.getElementsByClassName("combo_priority"), (e) => !window.qBittorrent.TorrentContent.isFolder(Number(e.dataset.fileId))
).map((e) => Number(e.value)).filter(p => (p !== -1));
document.getElementById("filePriorities").value = priorities;
});
document.getElementById("upload_frame").addEventListener("load", (event) => {
@ -31,156 +59,276 @@
window.parent.qBittorrent.Client.closeFrameWindow(window);
});
const userAgent = (navigator.userAgentData?.platform ?? navigator.platform).toLowerCase();
if (userAgent.includes("ipad") || userAgent.includes("iphone") || (userAgent.includes("mac") && (navigator.maxTouchPoints > 1)))
document.getElementById("fileselect").accept = ".torrent";
document.getElementById("saveTorrent").addEventListener("click", () => {
const url = new URL("api/v2/torrents/saveMetadata", window.location);
url.search = new URLSearchParams({
source: source
});
window.qBittorrent.Misc.downloadFile(url, "torrent.torrent", "QBT_TR(Unable to download torrent file)QBT_TR[CONTEXT=AddNewTorrentDialog]");
});
window.addEventListener("message", (event) => {
// ensure event is from a trusted source
if (event.origin !== window.origin)
return;
window.qBittorrent.AddTorrent.populateMetadata(event.data);
window.qBittorrent.AddTorrent.metadataCompleted(false);
});
window.qBittorrent.pathAutofill.attachPathAutofill();
window.qBittorrent.TorrentContent.init("addTorrentFilesTableDiv", window.qBittorrent.DynamicTable.AddTorrentFilesTable);
if (fetchMetadata)
window.qBittorrent.AddTorrent.loadMetadata(source);
});
</script>
</head>
<body>
<ul id="torrentFilesMenu" class="contextMenu">
<li class="separator">
<a href="#FilePrio" class="arrow-right"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Priority)QBT_TR[CONTEXT=AddNewTorrentDialog]</a>
<ul>
<li><a href="#FilePrioIgnore"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Do not download)QBT_TR[CONTEXT=AddNewTorrentDialog]</a></li>
<li><a href="#FilePrioNormal"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Normal)QBT_TR[CONTEXT=AddNewTorrentDialog]</a></li>
<li><a href="#FilePrioHigh"><span style="display: inline-block; width: 16px;"></span> QBT_TR(High)QBT_TR[CONTEXT=AddNewTorrentDialog]</a></li>
<li><a href="#FilePrioMaximum"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Maximum)QBT_TR[CONTEXT=AddNewTorrentDialog]</a></li>
</ul>
</li>
</ul>
<iframe id="upload_frame" name="upload_frame" class="invisible" title="" src="about:blank"></iframe>
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="uploadForm" style="text-align: center;" target="upload_frame" autocorrect="off" autocapitalize="none">
<div style="margin-top: 25px; display: inline-block; border: 1px solid lightgrey; border-radius: 4px;">
<input type="file" id="fileselect" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple aria-label="QBT_TR(Select .torrent files)QBT_TR[CONTEXT=AddNewTorrentDialog]">
</div>
<fieldset class="settings" style="border: 0; text-align: left; margin-top: 12px;">
<legend>QBT_TR(Torrent options)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="margin: auto;">
<tbody>
<tr>
<td>
<label for="autoTMM">QBT_TR(Torrent Management Mode:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="autoTMM" name="autoTMM" onchange="qBittorrent.Download.changeTMM(this)">
<option selected value="false">QBT_TR(Manual)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="true">QBT_TR(Automatic)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="savepath">QBT_TR(Save files to location:)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="text" id="savepath" name="savepath" class="pathDirectory" style="width: 16em;">
</td>
</tr>
<tr>
<td>
<label for="rename">QBT_TR(Rename torrent)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="text" id="rename" name="rename" style="width: 16em;">
</td>
</tr>
<tr>
<td>
<label id="categoryLabel" for="categorySelect">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<div class="select-watched-folder-editable">
<select id="categorySelect" onchange="qBittorrent.Download.changeCategorySelect(this)">
<option selected value="\other"></option>
</select>
<input type="text" name="category" aria-labelledby="categoryLabel">
</div>
</td>
</tr>
<tr>
<td>
<label for="startTorrent">QBT_TR(Start torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="hidden" id="startTorrentHidden" name="stopped">
<input type="checkbox" id="startTorrent">
</td>
</tr>
<tr>
<td>
<label for="addToTopOfQueue">QBT_TR(Add to top of queue)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="addToTopOfQueue" name="addToTopOfQueue" value="true">
</td>
</tr>
<tr>
<td>
<label for="stopCondition">QBT_TR(Stop condition:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="stopCondition" name="stopCondition">
<option selected value="None">QBT_TR(None)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="MetadataReceived">QBT_TR(Metadata received)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="FilesChecked">QBT_TR(Files checked)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="skip_checking">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="skip_checking" name="skip_checking" value="true">
</td>
</tr>
<tr>
<td>
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="contentLayout" name="contentLayout">
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="sequentialDownload">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=TransferListWidget]</label>
</td>
<td>
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true">
</td>
</tr>
<tr>
<td>
<label for="firstLastPiecePrio">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=TransferListWidget]</label>
</td>
<td>
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true">
</td>
</tr>
<tr>
<td>
<label for="dlLimitText">QBT_TR(Limit download rate)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="hidden" id="dlLimitHidden" name="dlLimit">
<input type="text" id="dlLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
<tr>
<td>
<label for="upLimitText">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=HttpServer]</label>
</td>
<td>
<input type="hidden" id="upLimitHidden" name="upLimit">
<input type="text" id="upLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
</tbody>
</table>
<div id="submitbutton" style="margin-top: 30px; text-align: center;">
<button type="submit" style="font-size: 1em;">QBT_TR(Upload Torrents)QBT_TR[CONTEXT=HttpServer]</button>
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="uploadForm" style="text-align: center; padding: 10px 12px;" target="upload_frame" autocorrect="off" autocapitalize="none">
<input type="hidden" id="urls" name="urls">
<input type="hidden" id="filePriorities" name="filePriorities">
<div style="display: flex; width: 100%;">
<div style="width: 50%; flex: 1; box-sizing: border-box;">
<fieldset class="settings" style="text-align: left;">
<legend>QBT_TR(Save at)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="width: 100%">
<tbody>
<tr>
<td>
<label for="autoTMM">QBT_TR(Torrent Management Mode:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="autoTMM" name="autoTMM" onchange="qBittorrent.Download.changeTMM(this)">
<option selected value="false">QBT_TR(Manual)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="true">QBT_TR(Automatic)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="savepath">QBT_TR(Save files to location:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="text" id="savepath" name="savepath" class="pathDirectory" style="width: 16em;">
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="settings" style="text-align: left;">
<legend>QBT_TR(Torrent settings)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="width: 100%">
<tbody>
<tr>
<td>
<label for="rename">QBT_TR(Rename torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="text" id="rename" name="rename" style="width: 16em;">
</td>
</tr>
<tr>
<td>
<label id="categoryLabel" for="categorySelect">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<div class="select-watched-folder-editable">
<select id="categorySelect" onchange="qBittorrent.Download.changeCategorySelect(this)">
<option selected value="\other"></option>
</select>
<input type="text" name="category" aria-labelledby="categoryLabel">
</div>
</td>
</tr>
<tr>
<td>
<label for="startTorrent">QBT_TR(Start torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="hidden" id="startTorrentHidden" name="stopped">
<input type="checkbox" id="startTorrent">
</td>
</tr>
<tr>
<td>
<label for="stopCondition">QBT_TR(Stop condition:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="stopCondition" name="stopCondition">
<option selected value="None">QBT_TR(None)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="MetadataReceived">QBT_TR(Metadata received)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="FilesChecked">QBT_TR(Files checked)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="addToTopOfQueue">QBT_TR(Add to top of queue)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="addToTopOfQueue" name="addToTopOfQueue" value="true">
</td>
</tr>
<tr>
<td>
<label for="skip_checking">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="skip_checking" name="skip_checking" value="true">
</td>
</tr>
<tr>
<td>
<label for="sequentialDownload">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true">
</td>
</tr>
<tr>
<td>
<label for="firstLastPiecePrio">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true">
</td>
</tr>
<tr>
<td>
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<select id="contentLayout" name="contentLayout">
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="dlLimitText">QBT_TR(Limit download rate)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="hidden" id="dlLimitHidden" name="dlLimit">
<input type="text" id="dlLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
<tr>
<td>
<label for="upLimitText">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td>
<input type="hidden" id="upLimitHidden" name="upLimit">
<input type="text" id="upLimitText" style="width: 16em;" placeholder="KiB/s">
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="settings" style="text-align: left;">
<legend>QBT_TR(Torrent information)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="width: 100%">
<tbody>
<tr>
<td class="noWrap">
<label for="size">QBT_TR(Size:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<span id="size">QBT_TR(Not available)QBT_TR[CONTEXT=AddNewTorrentDialog]</span>
</td>
</tr>
<tr>
<td class="noWrap">
<label for="createdDate">QBT_TR(Date:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<span id="createdDate">QBT_TR(Not available)QBT_TR[CONTEXT=AddNewTorrentDialog]</span>
</td>
</tr>
<tr>
<td class="noWrap">
<label for="infoHashV1">QBT_TR(Info hash v1:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<span id="infoHashV1">QBT_TR(Not available)QBT_TR[CONTEXT=AddNewTorrentDialog]</span>
</td>
</tr>
<tr>
<td class="noWrap">
<label for="infoHashV2">QBT_TR(Info hash v2:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<span id="infoHashV2">QBT_TR(Not available)QBT_TR[CONTEXT=AddNewTorrentDialog]</span>
</td>
</tr>
<tr>
<td class="noWrap">
<label for="comment">QBT_TR(Comment:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<textarea id="comment" rows="6" readonly style="width: calc(100% - 10px); resize: none;"></textarea>
</td>
</tr>
</tbody>
</table>
</fieldset>
</div>
</fieldset>
<div style="width: 50%; flex: 1; height: 750px; overflow-y: auto; box-sizing: border-box;">
<fieldset class="settings" style="text-align: left; display: flex; flex-direction: column; height: 100%; box-sizing: border-box;">
<legend style="width: fit-content;">QBT_TR(Files)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<div style="text-align: right; padding-bottom: 10px;">
<input type="text" id="torrentFilesFilterInput" placeholder="QBT_TR(Filter files...)QBT_TR[CONTEXT=AddNewTorrentDialog]" aria-label="QBT_TR(Filter files...)QBT_TR[CONTEXT=AddNewTorrentDialog]" autocorrect="off" autocapitalize="none">
</div>
<div id="torrentFiles" style="overflow: auto;">
<div id="torrentFilesTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable" style="position:relative;">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
</table>
</div>
<div id="addTorrentFilesTableDiv" class="dynamicTableDiv">
<table class="dynamicTable">
<thead>
<tr class="dynamicTableHeader"></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</fieldset>
</div>
</div>
<div id="submitbutton" style="display: flex; margin-top: 10px; align-items: center; position: relative;">
<div id="loading_spinner" class="mochaSpinner" style="display: block; bottom: 2px;"></div>
<div id="error_icon" class="mochaErrorIcon invisible" style="bottom: 2px;"></div>
<span id="metadataStatus" style="padding-left: 28px;">Retrieving metadata</span>
<button id="saveTorrent" type="button" class="invisible" style="font-size: 1em;">QBT_TR(Save as .torrent file)QBT_TR[CONTEXT=AddNewTorrentDialog]</button>
<!-- maintains button's relative position after metadataStatus element is deleted -->
<span>&nbsp;</span>
<div style="position: absolute; left: 50%; transform: translateX(-50%);">
<button type="submit" style="font-size: 1em;">QBT_TR(Add Torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</button>
</div>
</div>
</form>
<div id="upload_spinner" class="mochaSpinner"></div>
</body>
</html>

View file

@ -294,10 +294,10 @@
menu: "rssArticleMenu",
actions: {
Download: (el) => {
let dlString = "";
for (const rowID of rssArticleTable.selectedRows)
dlString += `${rssArticleTable.getRow(rowID).full_data.torrentURL}\n`;
showDownloadPage([dlString]);
for (const rowID of rssArticleTable.selectedRows) {
const { name, torrentURL } = rssArticleTable.getRow(rowID).full_data;
window.qBittorrent.Client.createAddTorrentWindow(name, torrentURL);
}
},
OpenNews: (el) => {
for (const rowID of rssArticleTable.selectedRows)

View file

@ -392,11 +392,11 @@
<file>private/rename_file.html</file>
<file>private/rename_files.html</file>
<file>private/rename_rule.html</file>
<file>private/scripts/addtorrent.js</file>
<file>private/scripts/cache.js</file>
<file>private/scripts/client.js</file>
<file>private/scripts/color-scheme.js</file>
<file>private/scripts/contextmenu.js</file>
<file>private/scripts/download.js</file>
<file>private/scripts/dynamicTable.js</file>
<file>private/scripts/file-tree.js</file>
<file>private/scripts/filesystem.js</file>