WebUI: switch to lightweight clipboard library

The new library [1] will opt to the modern Clipboard API [2] when it is available. It will
fallback to the old method otherwise.
The new library is also smaller and without any bloat.

Note that the line `module.exports` is required to be removed/commented out. This is the only
patch required.

[1] https://github.com/feross/clipboard-copy
[2] https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API

PR #22792.
This commit is contained in:
Chocobo1 2025-05-31 17:55:10 +08:00 committed by GitHub
parent 4b07597d54
commit 96f0eebc4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 123 additions and 72 deletions

View file

@ -31,7 +31,7 @@
<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/mocha-init.js?locale=${LANG}&v=${CACHEID}"></script> <script defer src="scripts/mocha-init.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/lib/clipboard.min.js"></script> <script defer src="scripts/lib/clipboard-copy.js"></script>
<script defer src="scripts/filesystem.js?v=${CACHEID}"></script> <script defer src="scripts/filesystem.js?v=${CACHEID}"></script>
<script defer src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script> <script defer src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/progressbar.js?v=${CACHEID}"></script> <script defer src="scripts/progressbar.js?v=${CACHEID}"></script>

View file

@ -1785,26 +1785,29 @@ window.addEventListener("DOMContentLoaded", (event) => {
} }
}); });
new ClipboardJS(".copyToClipboard", { for (const element of document.getElementsByClassName("copyToClipboard")) {
text: (trigger) => { const setupClickEvent = (textFunc) => element.addEventListener("click", async (event) => await clipboardCopy(textFunc()));
switch (trigger.id) { switch (element.id) {
case "copyName": case "copyName":
return copyNameFN(); setupClickEvent(copyNameFN);
case "copyInfohash1": break;
return copyInfohashFN(1); case "copyInfohash1":
case "copyInfohash2": setupClickEvent(() => copyInfohashFN(1));
return copyInfohashFN(2); break;
case "copyMagnetLink": case "copyInfohash2":
return copyMagnetLinkFN(); setupClickEvent(() => copyInfohashFN(2));
case "copyID": break;
return copyIdFN(); case "copyMagnetLink":
case "copyComment": setupClickEvent(copyMagnetLinkFN);
return copyCommentFN(); break;
default: case "copyID":
return ""; setupClickEvent(copyIdFN);
} break;
case "copyComment":
setupClickEvent(copyCommentFN);
break;
} }
}); }
addEventListener("visibilitychange", (event) => { addEventListener("visibilitychange", (event) => {
if (document.hidden) if (document.hidden)

View file

@ -0,0 +1,63 @@
/*! clipboard-copy. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/* global DOMException */
//module.exports = clipboardCopy
function makeError () {
return new DOMException('The request is not allowed', 'NotAllowedError')
}
async function copyClipboardApi (text) {
// Use the Async Clipboard API when available. Requires a secure browsing
// context (i.e. HTTPS)
if (!navigator.clipboard) {
throw makeError()
}
return navigator.clipboard.writeText(text)
}
async function copyExecCommand (text) {
// Put the text to copy into a <span>
const span = document.createElement('span')
span.textContent = text
// Preserve consecutive spaces and newlines
span.style.whiteSpace = 'pre'
span.style.webkitUserSelect = 'auto'
span.style.userSelect = 'all'
// Add the <span> to the page
document.body.appendChild(span)
// Make a selection object representing the range of text selected by the user
const selection = window.getSelection()
const range = window.document.createRange()
selection.removeAllRanges()
range.selectNode(span)
selection.addRange(range)
// Copy text to the clipboard
let success = false
try {
success = window.document.execCommand('copy')
} finally {
// Cleanup
selection.removeAllRanges()
window.document.body.removeChild(span)
}
if (!success) throw makeError()
}
async function clipboardCopy (text) {
try {
await copyClipboardApi(text)
} catch (err) {
// ...Otherwise, use document.execCommand() fallback
try {
await copyExecCommand(text)
} catch (err2) {
throw (err2 || err || makeError())
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -183,10 +183,9 @@ window.qBittorrent.PropPeers ??= (() => {
} }
}); });
new ClipboardJS("#CopyPeerInfo", { document.getElementById("CopyPeerInfo").addEventListener("click", async (event) => {
text: (trigger) => { const text = torrentPeersTable.selectedRowsIds().join("\n");
return torrentPeersTable.selectedRowsIds().join("\n"); await clipboardCopy(text);
}
}); });
torrentPeersTable.setup("torrentPeersTableDiv", "torrentPeersTableFixedHeaderDiv", torrentPeersContextMenu, true); torrentPeersTable.setup("torrentPeersTableDiv", "torrentPeersTableFixedHeaderDiv", torrentPeersContextMenu, true);

View file

@ -248,10 +248,9 @@ window.qBittorrent.PropTrackers ??= (() => {
torrentTrackersTable.clear(); torrentTrackersTable.clear();
}; };
new ClipboardJS("#CopyTrackerUrl", { document.getElementById("CopyTrackerUrl").addEventListener("click", async (event) => {
text: (trigger) => { const text = torrentTrackersTable.selectedRowsIds().join("\n");
return torrentTrackersTable.selectedRowsIds().join("\n"); await clipboardCopy(text);
}
}); });
torrentTrackersTable.setup("torrentTrackersTableDiv", "torrentTrackersTableFixedHeaderDiv", torrentTrackersContextMenu, true); torrentTrackersTable.setup("torrentTrackersTableDiv", "torrentTrackersTableFixedHeaderDiv", torrentTrackersContextMenu, true);

View file

@ -219,10 +219,9 @@ window.qBittorrent.PropWebseeds ??= (() => {
torrentWebseedsTable.clear(); torrentWebseedsTable.clear();
}; };
new ClipboardJS("#CopyWebseedUrl", { document.getElementById("CopyWebseedUrl").addEventListener("click", async (event) => {
text: (trigger) => { const text = torrentWebseedsTable.selectedRowsIds().join("\n");
return torrentWebseedsTable.selectedRowsIds().join("\n"); await clipboardCopy(text);
}
}); });
torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu, true); torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu, true);

View file

@ -866,20 +866,20 @@ window.qBittorrent.Search ??= (() => {
state.loadResultsTimer = loadSearchResultsData.delay(500, this, searchId); state.loadResultsTimer = loadSearchResultsData.delay(500, this, searchId);
}; };
new ClipboardJS(".copySearchDataToClipboard", { for (const element of document.getElementsByClassName("copySearchDataToClipboard")) {
text: (trigger) => { const setupClickEvent = (textFunc) => element.addEventListener("click", async (event) => await clipboardCopy(textFunc()));
switch (trigger.id) { switch (element.id) {
case "copySearchTorrentName": case "copySearchTorrentName":
return copySearchTorrentName(); setupClickEvent(copySearchTorrentName);
case "copySearchTorrentDownloadLink": break;
return copySearchTorrentDownloadLink(); case "copySearchTorrentDownloadLink":
case "copySearchTorrentDescriptionUrl": setupClickEvent(copySearchTorrentDownloadLink);
return copySearchTorrentDescriptionUrl(); break;
default: case "copySearchTorrentDescriptionUrl":
return ""; setupClickEvent(copySearchTorrentDescriptionUrl);
} break;
} }
}); }
return exports(); return exports();
})(); })();

View file

@ -144,7 +144,7 @@
</div> </div>
<ul id="logTableMenu" class="contextMenu"> <ul id="logTableMenu" class="contextMenu">
<li><a href="#" class="copyLogDataToClipboard"><img src="images/edit-copy.svg" alt="QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li> <li><a href="#" id="copyLogDataToClipboard"><img src="images/edit-copy.svg" alt="QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Copy)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li>
<li><a href="#Clear"><img src="images/list-remove.svg" alt="QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li> <li><a href="#Clear"><img src="images/list-remove.svg" alt="QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]">QBT_TR(Clear)QBT_TR[CONTEXT=ExecutionLogWidget]</a></li>
</ul> </ul>
@ -418,15 +418,11 @@
}); });
}; };
new ClipboardJS(".copyLogDataToClipboard", { document.getElementById("copyLogDataToClipboard").addEventListener("click", async (event) => {
text: () => { const instance = tableInfo[currentSelectedTab].instance;
const msg = []; const type = (currentSelectedTab === "main") ? "message" : "ip";
tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => { const msg = instance.selectedRowsIds().map((rowId) => instance.getRow(rowId).full_data[type]);
msg.push(tableInfo[currentSelectedTab].instance.getRow(rowId).full_data[(currentSelectedTab === "main") ? "message" : "ip"]); await clipboardCopy(msg.join("\n"));
});
return msg.join("\n");
}
}); });
return exports(); return exports();

View file

@ -277,17 +277,16 @@
} }
}); });
new ClipboardJS("#CopyFeedURL", { document.getElementById("CopyFeedURL").addEventListener("click", async (event) => {
text: () => { let joined = "";
let joined = ""; for (const rowID of rssFeedTable.selectedRows) {
for (const rowID of rssFeedTable.selectedRows) { const row = rssFeedTable.getRow(rowID);
const row = rssFeedTable.getRow(rowID); if (row.full_data.dataUid !== "")
if (row.full_data.dataUid !== "") joined += `${row.full_data.dataUrl}\n`;
joined += `${row.full_data.dataUrl}\n`;
}
return joined.slice(0, -1);
} }
await clipboardCopy(joined.slice(0, -1));
}); });
rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu); rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu);
const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({ const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({

View file

@ -400,7 +400,7 @@
<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>
<file>private/scripts/lib/clipboard.min.js</file> <file>private/scripts/lib/clipboard-copy.js</file>
<file>private/scripts/lib/mocha.min.js</file> <file>private/scripts/lib/mocha.min.js</file>
<file>private/scripts/lib/MooTools-Core-1.6.0-compat-compressed.js</file> <file>private/scripts/lib/MooTools-Core-1.6.0-compat-compressed.js</file>
<file>private/scripts/lib/MooTools-More-1.6.0-compat-compressed.js</file> <file>private/scripts/lib/MooTools-More-1.6.0-compat-compressed.js</file>