diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 23001f92c..08f0b9faa 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -31,7 +31,7 @@ - + diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index d0b13f1c1..91d6f4695 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1785,26 +1785,29 @@ window.addEventListener("DOMContentLoaded", (event) => { } }); - new ClipboardJS(".copyToClipboard", { - text: (trigger) => { - switch (trigger.id) { - case "copyName": - return copyNameFN(); - case "copyInfohash1": - return copyInfohashFN(1); - case "copyInfohash2": - return copyInfohashFN(2); - case "copyMagnetLink": - return copyMagnetLinkFN(); - case "copyID": - return copyIdFN(); - case "copyComment": - return copyCommentFN(); - default: - return ""; - } + for (const element of document.getElementsByClassName("copyToClipboard")) { + const setupClickEvent = (textFunc) => element.addEventListener("click", async (event) => await clipboardCopy(textFunc())); + switch (element.id) { + case "copyName": + setupClickEvent(copyNameFN); + break; + case "copyInfohash1": + setupClickEvent(() => copyInfohashFN(1)); + break; + case "copyInfohash2": + setupClickEvent(() => copyInfohashFN(2)); + break; + case "copyMagnetLink": + setupClickEvent(copyMagnetLinkFN); + break; + case "copyID": + setupClickEvent(copyIdFN); + break; + case "copyComment": + setupClickEvent(copyCommentFN); + break; } - }); + } addEventListener("visibilitychange", (event) => { if (document.hidden) diff --git a/src/webui/www/private/scripts/lib/clipboard-copy.js b/src/webui/www/private/scripts/lib/clipboard-copy.js new file mode 100644 index 000000000..bef3217f0 --- /dev/null +++ b/src/webui/www/private/scripts/lib/clipboard-copy.js @@ -0,0 +1,63 @@ +/*! clipboard-copy. MIT License. Feross Aboukhadijeh */ +/* 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 + 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 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()) + } + } +} diff --git a/src/webui/www/private/scripts/lib/clipboard.min.js b/src/webui/www/private/scripts/lib/clipboard.min.js deleted file mode 100644 index 1103f811e..000000000 --- a/src/webui/www/private/scripts/lib/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1 { } }); - new ClipboardJS("#CopyPeerInfo", { - text: (trigger) => { - return torrentPeersTable.selectedRowsIds().join("\n"); - } + document.getElementById("CopyPeerInfo").addEventListener("click", async (event) => { + const text = torrentPeersTable.selectedRowsIds().join("\n"); + await clipboardCopy(text); }); torrentPeersTable.setup("torrentPeersTableDiv", "torrentPeersTableFixedHeaderDiv", torrentPeersContextMenu, true); diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 5261a7db2..7214895b4 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -248,10 +248,9 @@ window.qBittorrent.PropTrackers ??= (() => { torrentTrackersTable.clear(); }; - new ClipboardJS("#CopyTrackerUrl", { - text: (trigger) => { - return torrentTrackersTable.selectedRowsIds().join("\n"); - } + document.getElementById("CopyTrackerUrl").addEventListener("click", async (event) => { + const text = torrentTrackersTable.selectedRowsIds().join("\n"); + await clipboardCopy(text); }); torrentTrackersTable.setup("torrentTrackersTableDiv", "torrentTrackersTableFixedHeaderDiv", torrentTrackersContextMenu, true); diff --git a/src/webui/www/private/scripts/prop-webseeds.js b/src/webui/www/private/scripts/prop-webseeds.js index 737642f69..3b756cfb5 100644 --- a/src/webui/www/private/scripts/prop-webseeds.js +++ b/src/webui/www/private/scripts/prop-webseeds.js @@ -219,10 +219,9 @@ window.qBittorrent.PropWebseeds ??= (() => { torrentWebseedsTable.clear(); }; - new ClipboardJS("#CopyWebseedUrl", { - text: (trigger) => { - return torrentWebseedsTable.selectedRowsIds().join("\n"); - } + document.getElementById("CopyWebseedUrl").addEventListener("click", async (event) => { + const text = torrentWebseedsTable.selectedRowsIds().join("\n"); + await clipboardCopy(text); }); torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu, true); diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index b4b7e23db..ce3edf6cd 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -866,20 +866,20 @@ window.qBittorrent.Search ??= (() => { state.loadResultsTimer = loadSearchResultsData.delay(500, this, searchId); }; - new ClipboardJS(".copySearchDataToClipboard", { - text: (trigger) => { - switch (trigger.id) { - case "copySearchTorrentName": - return copySearchTorrentName(); - case "copySearchTorrentDownloadLink": - return copySearchTorrentDownloadLink(); - case "copySearchTorrentDescriptionUrl": - return copySearchTorrentDescriptionUrl(); - default: - return ""; - } + for (const element of document.getElementsByClassName("copySearchDataToClipboard")) { + const setupClickEvent = (textFunc) => element.addEventListener("click", async (event) => await clipboardCopy(textFunc())); + switch (element.id) { + case "copySearchTorrentName": + setupClickEvent(copySearchTorrentName); + break; + case "copySearchTorrentDownloadLink": + setupClickEvent(copySearchTorrentDownloadLink); + break; + case "copySearchTorrentDescriptionUrl": + setupClickEvent(copySearchTorrentDescriptionUrl); + break; } - }); + } return exports(); })(); diff --git a/src/webui/www/private/views/log.html b/src/webui/www/private/views/log.html index b7944b784..a1fc55472 100644 --- a/src/webui/www/private/views/log.html +++ b/src/webui/www/private/views/log.html @@ -144,7 +144,7 @@ @@ -418,15 +418,11 @@ }); }; - new ClipboardJS(".copyLogDataToClipboard", { - text: () => { - const msg = []; - tableInfo[currentSelectedTab].instance.selectedRowsIds().forEach((rowId) => { - msg.push(tableInfo[currentSelectedTab].instance.getRow(rowId).full_data[(currentSelectedTab === "main") ? "message" : "ip"]); - }); - - return msg.join("\n"); - } + document.getElementById("copyLogDataToClipboard").addEventListener("click", async (event) => { + const instance = tableInfo[currentSelectedTab].instance; + const type = (currentSelectedTab === "main") ? "message" : "ip"; + const msg = instance.selectedRowsIds().map((rowId) => instance.getRow(rowId).full_data[type]); + await clipboardCopy(msg.join("\n")); }); return exports(); diff --git a/src/webui/www/private/views/rss.html b/src/webui/www/private/views/rss.html index 287e9741a..cd264b130 100644 --- a/src/webui/www/private/views/rss.html +++ b/src/webui/www/private/views/rss.html @@ -277,17 +277,16 @@ } }); - new ClipboardJS("#CopyFeedURL", { - text: () => { - let joined = ""; - for (const rowID of rssFeedTable.selectedRows) { - const row = rssFeedTable.getRow(rowID); - if (row.full_data.dataUid !== "") - joined += `${row.full_data.dataUrl}\n`; - } - return joined.slice(0, -1); + document.getElementById("CopyFeedURL").addEventListener("click", async (event) => { + let joined = ""; + for (const rowID of rssFeedTable.selectedRows) { + const row = rssFeedTable.getRow(rowID); + if (row.full_data.dataUid !== "") + joined += `${row.full_data.dataUrl}\n`; } + await clipboardCopy(joined.slice(0, -1)); }); + rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu); const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({ diff --git a/src/webui/www/webui.qrc b/src/webui/www/webui.qrc index d2ddbe9e6..864df1e6a 100644 --- a/src/webui/www/webui.qrc +++ b/src/webui/www/webui.qrc @@ -400,7 +400,7 @@ private/scripts/dynamicTable.js private/scripts/file-tree.js private/scripts/filesystem.js - private/scripts/lib/clipboard.min.js + private/scripts/lib/clipboard-copy.js private/scripts/lib/mocha.min.js private/scripts/lib/MooTools-Core-1.6.0-compat-compressed.js private/scripts/lib/MooTools-More-1.6.0-compat-compressed.js