Created
April 20, 2022 22:09
-
-
Save mdschweda/d974a15ec3170f2c993fc0309533fe54 to your computer and use it in GitHub Desktop.
UserScript: Copy Selected Links
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Copy Selected Links | |
// @description Copies selected links on pressing a hotkey (default: Alt+C / Option+C) | |
// @version 1 | |
// @match *://*/* | |
// ==/UserScript== | |
// Configuration | |
const preferUI = false; | |
const hotkey = e => e.altKey && ["c", "C"].includes(e.key); | |
async function copy(content) { | |
if (!preferUI && navigator.clipboard) { | |
try { | |
await navigator.clipboard.writeText(content); | |
return; | |
} catch (e) { | |
console.warn("Could not copy links to clipboard.", e); | |
} | |
} | |
// Fallback for when clipboard is not available (unsafe site) | |
let textArea = document.createElement("textarea"); | |
textArea.style.setProperty("position", "fixed", "important"); | |
textArea.style.setProperty("z-index", "9999", "important"); | |
textArea.style.setProperty("inset", "0", "important"); | |
textArea.style.setProperty("margin", "0", "important"); | |
textArea.style.setProperty("padding", "1em", "important"); | |
textArea.style.setProperty("border", "none", "important"); | |
textArea.style.setProperty("box-sizing", "border-box", "important"); | |
textArea.style.setProperty("width", "100vw", "important"); | |
textArea.style.setProperty("height", "100vh", "important"); | |
textArea.style.setProperty("font-family", "monospace", "important"); | |
textArea.style.setProperty("font-size", "11pt", "important"); | |
textArea.style.setProperty("color", "#ccc", "important"); | |
textArea.style.setProperty("background-color", "#333", "important"); | |
let previousSelectionRanges = []; | |
let selection = window.getSelection(); | |
for (let i = 0; i < selection.rangeCount; i++) { | |
previousSelectionRanges.push(selection.getRangeAt(i)); | |
} | |
function close(e) { | |
if (e && e.clipboardData) { | |
try { | |
// Set manually (textArea might be removed before content was copied) | |
let data = textArea.value.substring(textArea.selectionStart, textArea.selectionEnd); | |
e.clipboardData.setData("text/plain", data); | |
e.preventDefault(); | |
} catch (e) { | |
// Potential security limitations | |
console.warn(e); | |
} | |
} | |
// Delay for possible fallback / catch behavior | |
window.requestAnimationFrame(() => { | |
textArea.remove(); | |
selection.removeAllRanges(); | |
for (let r of previousSelectionRanges) { | |
selection.addRange(r); | |
} | |
}); | |
} | |
textArea.addEventListener("keydown", e => e.key === "Escape" && close()); | |
textArea.addEventListener("copy", close); | |
textArea.addEventListener("cut", close); | |
const message = preferUI ? | |
"Copy or press <Escape> to close.\n\n" : | |
"Can't access clipboard on an unsafe site. Copy or press <Escape> to close.\n\n"; | |
textArea.value = message + content; | |
document.body.appendChild(textArea); | |
textArea.setSelectionRange(message.length, textArea.value.length); | |
textArea.focus(); | |
} | |
function isUrl(str) { | |
try { | |
let url = new URL(str); | |
return ["http:", "https:"].includes(url.protocol); | |
} catch { | |
return false; | |
} | |
} | |
document.addEventListener("keydown", async e => { | |
if (!hotkey(e)) { | |
return; | |
} | |
let selection = window.getSelection(); | |
let allAnchors = Array.from(document.getElementsByTagName("a")); | |
let links = allAnchors | |
.filter(anchor => selection.containsNode(anchor, true)) | |
.map(anchor => anchor.href) | |
.filter(href => href && isUrl(href)); | |
if (links.length) { | |
await copy(links.join("\n")); | |
} else { | |
console.log("No links selected."); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment