WebUI: Add new Add Torrent experience

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 c1a18ad3f2
No known key found for this signature in database
13 changed files with 654 additions and 417 deletions

View file

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

View file

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

View file

@ -676,6 +676,17 @@ td.generalLabel {
width: 1px; width: 1px;
} }
td.fullWidth {
box-sizing: border-box;
max-width: none;
width: 100%;
word-break: break-all;
}
td.noWrap {
white-space: nowrap;
}
#tristate_cb { #tristate_cb {
margin-bottom: 0; margin-bottom: 0;
margin-top: 0; margin-top: 0;
@ -839,7 +850,8 @@ td.statusBarSeparator {
color: var(--color-text-green); 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); background-color: var(--color-background-hover);
color: var(--color-text-white); color: var(--color-text-white);
} }

View file

@ -3,13 +3,11 @@
<head> <head>
<meta charset="UTF-8"> <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/style.css?v=${CACHEID}" type="text/css">
<link rel="stylesheet" href="css/Window.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/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.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> <script>
"use strict"; "use strict";
@ -23,6 +21,8 @@
} }
}); });
document.getElementById("urls").focus();
const encodedUrls = new URLSearchParams(window.location.search).get("urls"); const encodedUrls = new URLSearchParams(window.location.search).get("urls");
if (encodedUrls !== null) { if (encodedUrls !== null) {
const urls = encodedUrls.split("|").map(decodeURIComponent); const urls = encodedUrls.split("|").map(decodeURIComponent);
@ -30,172 +30,42 @@
document.getElementById("urls").value = urls.join("\n"); document.getElementById("urls").value = urls.join("\n");
} }
let submitted = false; document.getElementById("submitButton").addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("downloadForm").addEventListener("submit", (event) => { const urls = document.getElementById("urls").value.split("\n").map(s => s.trim()).filter(s => (s.length > 0));
document.getElementById("startTorrentHidden").value = document.getElementById("startTorrent").checked ? "false" : "true"; if (urls.length === 0)
return;
document.getElementById("dlLimitHidden").value = Number(document.getElementById("dlLimitText").value) * 1024; for (const url of urls)
document.getElementById("upLimitHidden").value = Number(document.getElementById("upLimitText").value) * 1024; window.parent.qBittorrent.Client.createAddTorrentWindow("QBT_TR(Magnet link)QBT_TR[CONTEXT=DownloadFromURLDialog]", url);
document.getElementById("download_spinner").style.display = "block";
submitted = true;
});
document.getElementById("download_frame").addEventListener("load", (event) => {
if (submitted)
window.parent.qBittorrent.Client.closeFrameWindow(window); window.parent.qBittorrent.Client.closeFrameWindow(window);
}); });
window.qBittorrent.pathAutofill.attachPathAutofill(); window.addEventListener("keydown", (event) => {
switch (event.key) {
case "Escape":
event.preventDefault();
window.parent.qBittorrent.Client.closeFrameWindow(window);
break;
}
});
}); });
</script> </script>
</head> </head>
<body> <body>
<iframe id="download_frame" name="download_frame" class="invisible" title="" src="about:blank"></iframe> <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;"> <div style="text-align: center;">
<br> <br>
<h2><label for="urls">QBT_TR(Add torrent links)QBT_TR[CONTEXT=AddNewTorrentDialog]</label></h2> <h2 class="vcenter">QBT_TR(Add torrent links)QBT_TR[CONTEXT=DownloadFromURLDialog]</h2>
<textarea id="urls" rows="10" name="urls"></textarea> <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=AddNewTorrentDialog]</i></p> <p><i>QBT_TR(One link per line (HTTP links, Magnet links and info-hashes are supported))QBT_TR[CONTEXT=DownloadFromURLDialog]</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;"> <div id="submitbutton" style="margin-top: 12px; text-align: center;">
<button type="submit" id="submitButton">QBT_TR(Download)QBT_TR[CONTEXT=downloadFromURL]</button> <button type="submit" id="submitButton">QBT_TR(Download)QBT_TR[CONTEXT=DownloadFromURLDialog]</button>
</div> </div>
</fieldset>
</div> </div>
</form>
<div id="download_spinner" class="mochaSpinner"></div>
</body> </body>
</html> </html>

View file

@ -60,7 +60,15 @@
<li> <li>
<a class="returnFalse">QBT_TR(File)QBT_TR[CONTEXT=MainWindow]</a> <a class="returnFalse">QBT_TR(File)QBT_TR[CONTEXT=MainWindow]</a>
<ul> <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" class="fileselect" 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><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 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> <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,7 +122,12 @@
</div> </div>
<div id="mochaToolbar"> <div id="mochaToolbar">
&nbsp;&nbsp; &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> <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" class="fileselect" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple style="display: none;">
</div>
<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> <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>
<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="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="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>

View file

@ -24,16 +24,23 @@
"use strict"; "use strict";
window.qBittorrent ??= {}; window.qBittorrent ??= {};
window.qBittorrent.Download ??= (() => { window.qBittorrent.AddTorrent ??= (() => {
const exports = () => { const exports = () => {
return { return {
changeCategorySelect: changeCategorySelect, changeCategorySelect: changeCategorySelect,
changeTMM: changeTMM changeTMM: changeTMM,
loadMetadata: loadMetadata,
metadataCompleted: metadataCompleted,
populateMetadata: populateMetadata,
setWindowId: setWindowId,
submitForm: submitForm
}; };
}; };
let categories = {}; let categories = {};
let defaultSavePath = ""; let defaultSavePath = "";
let windowId = "";
let source = "";
const getCategories = () => { const getCategories = () => {
fetch("api/v2/torrents/categories", { fetch("api/v2/torrents/categories", {
@ -131,6 +138,100 @@ window.qBittorrent.Download ??= (() => {
} }
}; };
let loadMetadataTimer = -1;
const loadMetadata = (sourceUrl = undefined) => {
if (sourceUrl !== undefined)
source = sourceUrl;
fetch("api/v2/torrents/fetchMetadata", {
method: "POST",
body: new URLSearchParams({
source: source
})
})
.then(async (response) => {
if (!response.ok) {
metadataFailed();
return;
}
const data = await response.json();
populateMetadata(data);
if (response.status === 200)
metadataCompleted();
else
loadMetadataTimer = loadMetadata.delay(1000);
});
};
const metadataCompleted = (showDownloadButton = true) => {
clearTimeout(loadMetadataTimer);
loadMetadataTimer = -1;
document.getElementById("metadataStatus").destroy();
document.getElementById("loadingSpinner").style.display = "none";
if (showDownloadButton)
document.getElementById("saveTorrent").classList.remove("invisible");
};
const metadataFailed = () => {
clearTimeout(loadMetadataTimer);
loadMetadataTimer = -1;
document.getElementById("metadataStatus").textContent = "Metadata retrieval failed";
document.getElementById("metadataStatus").classList.add("red");
document.getElementById("loadingSpinner").style.display = "none";
document.getElementById("errorIcon").classList.remove("invisible");
};
const populateMetadata = (metadata) => {
// update window title
if (metadata.info?.name !== undefined)
window.parent.document.getElementById(`${windowId}_title`).textContent = metadata.info.name;
const notAvailable = "QBT_TR(Not available)QBT_TR[CONTEXT=AddNewTorrentDialog]";
const notApplicable = "QBT_TR(N/A)QBT_TR[CONTEXT=AddNewTorrentDialog]";
document.getElementById("infoHashV1").textContent = (metadata.infohash_v1 === undefined) ? notAvailable : (metadata.infohash_v1 || notApplicable);
document.getElementById("infoHashV2").textContent = (metadata.infohash_v2 === undefined) ? notAvailable : (metadata.infohash_v2 || notApplicable);
if (metadata.info?.length !== undefined)
document.getElementById("size").textContent = window.qBittorrent.Misc.friendlyUnit(metadata.info.length, false);
if ((metadata.creation_date !== undefined) && (metadata.creation_date > 1))
document.getElementById("createdDate").textContent = new Date(metadata.creation_date * 1000).toLocaleString();
if (metadata.comment !== undefined)
document.getElementById("comment").textContent = metadata.comment;
if (metadata.info?.files !== undefined) {
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;
};
const submitForm = () => {
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("filePriorities").value = [...document.getElementsByClassName("combo_priority")]
.filter((el) => !window.qBittorrent.TorrentContent.isFolder(Number(el.dataset.fileId)))
.sort((el1, el2) => Number(el1.dataset.fileId) - Number(el2.dataset.fileId))
.map((el) => Number(el.value));
document.getElementById("loadingSpinner").style.display = "block";
};
window.addEventListener("load", async (event) => { window.addEventListener("load", async (event) => {
// user might load this page directly (via browser magnet handler) // user might load this page directly (via browser magnet handler)
// so wait for crucial initialization to complete // so wait for crucial initialization to complete
@ -142,4 +243,4 @@ window.qBittorrent.Download ??= (() => {
return exports(); return exports();
})(); })();
Object.freeze(window.qBittorrent.Download); Object.freeze(window.qBittorrent.AddTorrent);

View file

@ -43,6 +43,8 @@ window.qBittorrent.Client ??= (() => {
isShowSearchEngine: isShowSearchEngine, isShowSearchEngine: isShowSearchEngine,
isShowRssReader: isShowRssReader, isShowRssReader: isShowRssReader,
isShowLogViewer: isShowLogViewer, isShowLogViewer: isShowLogViewer,
createAddTorrentWindow: createAddTorrentWindow,
uploadTorrentFiles: uploadTorrentFiles,
categoryMap: categoryMap, categoryMap: categoryMap,
tagMap: tagMap tagMap: tagMap
}; };
@ -128,6 +130,82 @@ window.qBittorrent.Client ??= (() => {
return showingLogViewer; 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({
v: "${CACHEID}",
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);
}
fetch("api/v2/torrents/parseMetadata", {
method: "POST",
body: formData
})
.then(async (response) => {
if (!response.ok) {
alert(await response.text());
return;
}
const json = await response.json();
for (const fileName of fileNames) {
let title = fileName;
const metadata = json[fileName];
if (metadata !== undefined)
title = metadata.name;
const hash = metadata.infohash_v2 || metadata.infohash_v1;
createAddTorrentWindow(title, hash, metadata);
}
})
.catch((error) => {
alert(`QBT_TR(Unable to parse response.)QBT_TR[CONTEXT=HttpServer] ${error.toString()}`);
});
};
return exports(); return exports();
})(); })();
Object.freeze(window.qBittorrent.Client); Object.freeze(window.qBittorrent.Client);
@ -1687,32 +1765,11 @@ window.addEventListener("DOMContentLoaded", (event) => {
// can't handle folder due to cannot put the filelist (from dropped folder) // can't handle folder due to cannot put the filelist (from dropped folder)
// to <input> `files` field // to <input> `files` field
for (const item of ev.dataTransfer.items) { for (const item of ev.dataTransfer.items) {
if (item.webkitGetAsEntry().isDirectory) if ((item.kind !== "file") || (item.webkitGetAsEntry().isDirectory))
return; return;
} }
const id = "uploadPage"; window.qBittorrent.Client.uploadTorrentFiles(droppedFiles);
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?v=${CACHEID}",
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;
}
});
} }
const droppedText = ev.dataTransfer.getData("text"); const droppedText = ev.dataTransfer.getData("text");
@ -1730,33 +1787,8 @@ window.addEventListener("DOMContentLoaded", (event) => {
|| ((str.length === 32) && !(/[^2-7A-Z]/i.test(str))); // v1 Base32 encoded SHA-1 info-hash || ((str.length === 32) && !(/[^2-7A-Z]/i.test(str))); // v1 Base32 encoded SHA-1 info-hash
}); });
if (urls.length <= 0) for (const url of urls)
return; qBittorrent.Client.createAddTorrentWindow(url, url);
const id = "downloadPage";
const contentURL = new URL("download.html", window.location);
contentURL.search = new URLSearchParams({
v: "${CACHEID}",
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);
})
});
} }
}); });
}; };

View file

@ -44,6 +44,7 @@ window.qBittorrent.DynamicTable ??= (() => {
TorrentTrackersTable: TorrentTrackersTable, TorrentTrackersTable: TorrentTrackersTable,
BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable, BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable,
TorrentFilesTable: TorrentFilesTable, TorrentFilesTable: TorrentFilesTable,
AddTorrentFilesTable: AddTorrentFilesTable,
LogMessageTable: LogMessageTable, LogMessageTable: LogMessageTable,
LogPeerTable: LogPeerTable, LogPeerTable: LogPeerTable,
RssFeedTable: RssFeedTable, RssFeedTable: RssFeedTable,
@ -66,6 +67,9 @@ window.qBittorrent.DynamicTable ??= (() => {
let DynamicTableHeaderContextMenuClass = null; let DynamicTableHeaderContextMenuClass = null;
if (typeof LocalPreferences === "undefined")
window.LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
class DynamicTable { class DynamicTable {
setup(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu, useVirtualList = false) { setup(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu, useVirtualList = false) {
this.dynamicTableDivId = dynamicTableDivId; this.dynamicTableDivId = dynamicTableDivId;
@ -2889,6 +2893,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns["size"].updateTd = displaySize; this.columns["size"].updateTd = displaySize;
// progress // progress
if (this.columns["progress"] !== undefined) {
this.columns["progress"].updateTd = function(td, row) { this.columns["progress"].updateTd = function(td, row) {
const value = Number(this.getRowValue(row)); const value = Number(this.getRowValue(row));
@ -2899,6 +2904,7 @@ window.qBittorrent.DynamicTable ??= (() => {
progressBar.setValue(value); progressBar.setValue(value);
}; };
this.columns["progress"].staticWidth = 100; this.columns["progress"].staticWidth = 100;
}
// priority // priority
this.columns["priority"].updateTd = function(td, row) { this.columns["priority"].updateTd = function(td, row) {
@ -2914,7 +2920,9 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns["priority"].staticWidth = 140; this.columns["priority"].staticWidth = 140;
// remaining, availability // remaining, availability
if (this.columns["remaining"] !== undefined)
this.columns["remaining"].updateTd = displaySize; this.columns["remaining"].updateTd = displaySize;
if (this.columns["availability"] !== undefined)
this.columns["availability"].updateTd = displayPercentage; this.columns["availability"].updateTd = displayPercentage;
} }
@ -3070,6 +3078,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 { class RssFeedTable extends DynamicTable {
initColumns() { initColumns() {
this.newColumn("state_icon", "", "", 30, true); this.newColumn("state_icon", "", "", 30, true);
@ -3248,7 +3267,8 @@ window.qBittorrent.DynamicTable ??= (() => {
if (!tr) if (!tr)
return; 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) { updateRow(tr, fullUpdate) {

View file

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

View file

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

View file

@ -3,13 +3,22 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]</title> <title>QBT_TR(Add torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</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/style.css?v=${CACHEID}" type="text/css">
<link rel="stylesheet" href="css/Window.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/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.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/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> <script>
"use strict"; "use strict";
@ -23,16 +32,24 @@
} }
}); });
const searchParams = new URLSearchParams(window.location.search);
const source = searchParams.get("source");
if ((source === null) || (source.length === 0))
return;
document.getElementById("urls").value = source;
// fetch unless explicitly told not to
const fetchMetadata = searchParams.get("fetch") !== "false";
let submitted = false; let submitted = false;
const windowId = searchParams.get("windowId");
window.qBittorrent.AddTorrent.setWindowId(windowId);
document.getElementById("uploadForm").addEventListener("submit", (event) => { 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";
submitted = true; submitted = true;
window.qBittorrent.AddTorrent.submitForm();
}); });
document.getElementById("upload_frame").addEventListener("load", (event) => { document.getElementById("upload_frame").addEventListener("load", (event) => {
@ -40,59 +57,134 @@
window.parent.qBittorrent.Client.closeFrameWindow(window); window.parent.qBittorrent.Client.closeFrameWindow(window);
}); });
const userAgent = (navigator.userAgentData?.platform ?? navigator.platform).toLowerCase(); document.getElementById("saveTorrent").addEventListener("click", (event) => {
if (userAgent.includes("ipad") || userAgent.includes("iphone") || (userAgent.includes("mac") && (navigator.maxTouchPoints > 1))) const url = new URL("api/v2/torrents/saveMetadata", window.location);
document.getElementById("fileselect").accept = ".torrent"; 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.pathAutofill.attachPathAutofill();
window.qBittorrent.TorrentContent.init("addTorrentFilesTableDiv", window.qBittorrent.DynamicTable.AddTorrentFilesTable);
if (fetchMetadata)
window.qBittorrent.AddTorrent.loadMetadata(source);
}); });
</script> </script>
<style>
.container {
display: flex;
width: 100%;
}
.column {
width: 50%;
flex: 1;
box-sizing: border-box;
}
@media (max-width: 760px) {
.container {
flex-direction: column;
}
.column {
width: 100%;
}
}
@media (max-width: 500px) {
td.noWrap {
white-space: normal;
width: 125px;
word-wrap: break-word;
vertical-align: middle;
}
td.fullWidth {
width: auto;
}
}
</style>
</head> </head>
<body> <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> <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"> <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">
<div style="margin-top: 25px; display: inline-block; border: 1px solid lightgrey; border-radius: 4px;"> <input type="hidden" id="urls" name="urls">
<input type="file" id="fileselect" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple aria-label="QBT_TR(Select .torrent files)QBT_TR[CONTEXT=AddNewTorrentDialog]"> <input type="hidden" id="filePriorities" name="filePriorities">
</div> <div class="container">
<fieldset class="settings" style="border: 0; text-align: left; margin-top: 12px;"> <div class="column">
<legend>QBT_TR(Torrent options)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend> <fieldset class="settings" style="text-align: left;">
<table style="margin: auto;"> <legend>QBT_TR(Save at)QBT_TR[CONTEXT=AddNewTorrentDialog]</legend>
<table style="width: 100%">
<tbody> <tbody>
<tr> <tr>
<td> <td class="noWrap">
<label for="autoTMM">QBT_TR(Torrent Management Mode:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label> <label for="autoTMM">QBT_TR(Torrent Management Mode:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<select id="autoTMM" name="autoTMM" onchange="qBittorrent.Download.changeTMM(this)"> <select id="autoTMM" name="autoTMM" onchange="qBittorrent.AddTorrent.changeTMM(this)">
<option selected value="false">QBT_TR(Manual)QBT_TR[CONTEXT=AddNewTorrentDialog]</option> <option selected value="false">QBT_TR(Manual)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
<option value="true">QBT_TR(Automatic)QBT_TR[CONTEXT=AddNewTorrentDialog]</option> <option value="true">QBT_TR(Automatic)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="savepath">QBT_TR(Save files to location:)QBT_TR[CONTEXT=HttpServer]</label> <label for="savepath">QBT_TR(Save files to location:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<input type="text" id="savepath" name="savepath" class="pathDirectory" style="width: 16em;"> <input type="text" id="savepath" name="savepath" class="pathDirectory" style="width: 100%;">
</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 class="noWrap">
<label for="rename">QBT_TR(Rename torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<input type="text" id="rename" name="rename" style="width: 100%; max-width: 16em;">
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<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> <label id="categoryLabel" for="categorySelect">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<div class="select-watched-folder-editable"> <div class="select-watched-folder-editable">
<select id="categorySelect" onchange="qBittorrent.Download.changeCategorySelect(this)"> <select id="categorySelect" onchange="qBittorrent.AddTorrent.changeCategorySelect(this)">
<option selected value="\other"></option> <option selected value="\other"></option>
</select> </select>
<input type="text" name="category" aria-labelledby="categoryLabel"> <input type="text" name="category" aria-labelledby="categoryLabel">
@ -100,27 +192,19 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="startTorrent">QBT_TR(Start torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label> <label for="startTorrent">QBT_TR(Start torrent)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<input type="hidden" id="startTorrentHidden" name="stopped"> <input type="hidden" id="startTorrentHidden" name="stopped">
<input type="checkbox" id="startTorrent"> <input type="checkbox" id="startTorrent">
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<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> <label for="stopCondition">QBT_TR(Stop condition:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<select id="stopCondition" name="stopCondition"> <select id="stopCondition" name="stopCondition">
<option selected value="None">QBT_TR(None)QBT_TR[CONTEXT=AddNewTorrentDialog]</option> <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="MetadataReceived">QBT_TR(Metadata received)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
@ -129,18 +213,42 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="addToTopOfQueue">QBT_TR(Add to top of queue)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<input type="checkbox" id="addToTopOfQueue" name="addToTopOfQueue" value="true">
</td>
</tr>
<tr>
<td class="noWrap">
<label for="skip_checking">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label> <label for="skip_checking">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<input type="checkbox" id="skip_checking" name="skip_checking" value="true"> <input type="checkbox" id="skip_checking" name="skip_checking" value="true">
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="sequentialDownload">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true">
</td>
</tr>
<tr>
<td class="noWrap">
<label for="firstLastPiecePrio">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td>
<td class="fullWidth">
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true">
</td>
</tr>
<tr>
<td class="noWrap">
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label> <label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<select id="contentLayout" name="contentLayout"> <select id="contentLayout" name="contentLayout">
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option> <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="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
@ -149,47 +257,113 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="sequentialDownload">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=TransferListWidget]</label> <label for="dlLimitText">QBT_TR(Limit download rate)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<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="hidden" id="dlLimitHidden" name="dlLimit">
<input type="text" id="dlLimitText" style="width: 16em;" placeholder="KiB/s"> <input type="text" id="dlLimitText" placeholder="KiB/s">
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td class="noWrap">
<label for="upLimitText">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=HttpServer]</label> <label for="upLimitText">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
</td> </td>
<td> <td class="fullWidth">
<input type="hidden" id="upLimitHidden" name="upLimit"> <input type="hidden" id="upLimitHidden" name="upLimit">
<input type="text" id="upLimitText" style="width: 16em;" placeholder="KiB/s"> <input type="text" id="upLimitText" placeholder="KiB/s">
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div id="submitbutton" style="margin-top: 30px; text-align: center;"> </fieldset>
<button type="submit" style="font-size: 1em;">QBT_TR(Upload Torrents)QBT_TR[CONTEXT=HttpServer]</button>
<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>
<div class="column" style="max-height: 750px; overflow-y: auto;">
<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> </div>
</fieldset> </fieldset>
</div>
</div>
<div id="submitbutton" style="display: flex; margin-top: 10px; align-items: center; position: relative;">
<div id="loadingSpinner" class="mochaSpinner" style="display: block; bottom: 2px;"></div>
<div id="errorIcon" 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> </form>
<div id="upload_spinner" class="mochaSpinner"></div>
</body> </body>
</html> </html>

View file

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

View file

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