Skip to content

Instantly share code, notes, and snippets.

@mdschweda
Created April 20, 2022 22:09
Show Gist options
  • Save mdschweda/d974a15ec3170f2c993fc0309533fe54 to your computer and use it in GitHub Desktop.
Save mdschweda/d974a15ec3170f2c993fc0309533fe54 to your computer and use it in GitHub Desktop.
UserScript: Copy Selected Links
// ==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