Copy all links from selected text to clipboard using keyboard shortcut (default: Shift+C)
// ==UserScript==
// @name Link2Clip
// @name:zh-TW Link2Clip - 連結複製工具
// @namespace https://github.com/livinginpurple
// @version 1.0
// @license WTFPL
// @author livinginpurple
// @description Copy all links from selected text to clipboard using keyboard shortcut (default: Shift+C)
// @description:zh-TW 使用鍵盤快捷鍵(預設:Shift+C)將選取文字中的所有連結複製到剪貼簿
// @match http://*/*
// @match https://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const scriptName = GM_info.script.name;
const log = (msg) => console.log(`${scriptName}: ${msg}`);
const warn = (msg) => console.warn(`${scriptName}: ${msg}`);
const error = (msg) => console.error(`${scriptName}: ${msg}`);
// Default keyboard shortcut configuration
const DEFAULT_CONFIG = {
alt: false,
ctrl: false,
shift: true,
keyCode: 67 // 'C'
};
// Load configuration from storage
let config = {
alt: GM_getValue('accel.alt', DEFAULT_CONFIG.alt),
ctrl: GM_getValue('accel.ctrl', DEFAULT_CONFIG.ctrl),
shift: GM_getValue('accel.shift', DEFAULT_CONFIG.shift),
keyCode: GM_getValue('accel.key', DEFAULT_CONFIG.keyCode)
};
// Save configuration to storage
const saveConfig = () => {
Object.entries(config).forEach(([key, value]) => {
GM_setValue(`accel.${key === 'keyCode' ? 'key' : key}`, value);
});
};
// Get shortcut string representation
const getShortcutString = ({ alt, ctrl, shift, keyCode }) => {
const keys = [];
if (alt) keys.push('Alt');
if (ctrl) keys.push('Ctrl');
if (shift) keys.push('Shift');
keys.push(String.fromCharCode(keyCode));
return keys.join(' + ');
};
// Show configuration dialog
const showConfigDialog = () => {
const currentShortcut = getShortcutString(config);
const newShortcut = prompt(
`Current shortcut: ${currentShortcut}\n\n` +
'To set a new shortcut, press the desired key combination in the next prompt.\n' +
'Click OK to continue, or Cancel to abort.',
'Click OK then press your desired keys'
);
if (newShortcut === null) return;
alert('Press your desired key combination now...');
// Temporarily attach a one-time listener
const tempListener = (e) => {
e.preventDefault();
e.stopPropagation();
const { altKey, ctrlKey, shiftKey, keyCode } = e;
config = { alt: altKey, ctrl: ctrlKey, shift: shiftKey, keyCode };
saveConfig();
const newShortcutStr = getShortcutString(config);
alert(`Shortcut updated to: ${newShortcutStr}`);
log(`Shortcut updated to: ${newShortcutStr}`);
document.removeEventListener('keydown', tempListener, true);
};
document.addEventListener('keydown', tempListener, true);
};
// Reset to default configuration
const resetToDefault = () => {
config = { ...DEFAULT_CONFIG };
saveConfig();
alert('Shortcut reset to default: Shift+C');
log('Shortcut reset to default: Shift+C');
};
// Register menu commands for configuration
GM_registerMenuCommand('⌨️ Configure Keyboard Shortcut', showConfigDialog);
GM_registerMenuCommand('🔄 Reset to Default (Shift+C)', resetToDefault);
// Check if pressed keys match configured shortcut
const acceleratorMatch = ({ keyCode, altKey, ctrlKey, shiftKey }) =>
keyCode === config.keyCode &&
altKey === config.alt &&
ctrlKey === config.ctrl &&
shiftKey === config.shift;
// Get links from selection or entire page
const getLinks = (selectedOnly) => {
const selection = window.getSelection();
return Array.from(document.links)
.filter(link => !selectedOnly || selection.containsNode(link, true))
.map(link => link.href);
};
// Get platform-specific line ending
const getEOL = () => {
const platform = navigator.platform.toLowerCase();
if (platform.includes('win')) return '\r\n';
if (platform.includes('mac')) return '\r';
return '\n';
};
// Copy text to clipboard using GM_setClipboard
const copyToClipboard = (text) => {
try {
GM_setClipboard(text);
showNotification('Links copied to clipboard!', 'success');
log(`Copied ${text.split('\n').length} links to clipboard`);
} catch (err) {
error(`Failed to copy to clipboard: ${err?.message ?? err}`);
showNotification('Failed to copy links to clipboard', 'error');
}
};
// Show visual notification
const showNotification = (message, type = 'success') => {
// Remove existing notification if any
document.getElementById('link2clip-notification')?.remove();
const notification = Object.assign(document.createElement('div'), {
id: 'link2clip-notification',
textContent: message
});
const bgColor = type === 'success' ? '#4CAF50' : '#f44336';
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${bgColor};
color: white;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
font-weight: 500;
animation: slideIn 0.3s ease-out;
`;
// Add animation styles
if (!document.getElementById('link2clip-styles')) {
const style = Object.assign(document.createElement('style'), {
id: 'link2clip-styles',
textContent: `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`
});
document.head.appendChild(style);
}
document.body.appendChild(notification);
// Remove notification after 2.5 seconds
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => notification.remove(), 300);
}, 2500);
};
// Extract and copy links
const extractAndCopyLinks = () => {
const links = getLinks(true);
if (links.length === 0) {
showNotification('No links found in the selected text', 'error');
return;
}
copyToClipboard(links.join(getEOL()));
};
// Keyboard event listener
const onKeyDown = (event) => {
if (!acceleratorMatch(event)) return;
const selection = window.getSelection();
if (selection?.toString().trim()) {
event.preventDefault();
event.stopPropagation();
extractAndCopyLinks();
}
};
// Register event listener
document.addEventListener('keydown', onKeyDown, false);
log(`Initialized with shortcut: ${getShortcutString(config)}`);
})();