【油猴脚本】右键链接增强 - 快速复制链接文字、地址及多种格式
本帖最后由 MRBANK 于 2026-2-8 15:36 编辑右键链接增强 - 快速复制链接文字、地址及多种格式
它能做什么?
[*]复制链接文字 - 再也不用手动选中了!
[*]复制链接地址 - 直接获取完整URL
[*]文字+链接 - 方便保存或分享
[*]Markdown格式 - [文字](链接) 一键生成
[*]HTML格式 - <a href="链接">文字</a> 直接复制
[*]BBCode格式 - 文字 论坛发帖神器
[*]复制选中文字 - 选中后右键链接就能复制文字
[*]新标签页打开 - 还是熟悉的操作
与浏览器右键菜单不干扰,空白处右键还是会出现浏览器右键菜单。
效果:
// ==UserScript==
// @name 右键链接增强
// @namespace http://tampermonkey.net/
// @version 1.0
// @description右键链接快速复制文字、地址及多种格式,智能识别选中文本
// @author MRBANK
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_openInTab
// @grant GM_notification
// @icon https://img.icons8.com/color/96/000000/copy-link.png
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 设计配置
const DESIGN = {
menuWidth: 240,
borderRadius: 8,
animationSpeed: 100,
colors: {
primary: '#3b82f6',
success: '#10b981',
error: '#ef4444',
background: '#ffffff',
hover: '#f3f4f6',
border: '#e5e7eb',
text: '#111827',
subtitle: '#6b7280'
},
shadows: {
menu: '0 6px 16px rgba(0, 0, 0, 0.12)',
notification: '0 4px 12px rgba(0, 0, 0, 0.1)'
},
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
};
// 状态变量
let currentLink = null;
let selectedText = '';
let menuElement = null;
let isMenuVisible = false;
// 初始化
function init() {
injectStyles();
createMenu();
attachEventListeners();
console.log('链接右键智能增强已加载');
}
// 注入CSS样式
function injectStyles() {
const style = document.createElement('style');
style.textContent = `
#link-menu {
position: fixed;
z-index: 999999;
background: ${DESIGN.colors.background};
border-radius: ${DESIGN.borderRadius}px;
box-shadow: ${DESIGN.shadows.menu};
font-family: ${DESIGN.fontFamily};
width: ${DESIGN.menuWidth}px;
opacity: 0;
transform: scale(0.95);
transition: all ${DESIGN.animationSpeed}ms ease-out;
border: 1px solid ${DESIGN.colors.border};
overflow: hidden;
display: none;
padding: 6px 0;
}
#link-menu.visible {
opacity: 1;
transform: scale(1);
}
.menu-group {
padding: 4px 0;
border-bottom: 1px solid ${DESIGN.colors.border};
}
.menu-group:last-child {
border-bottom: none;
}
.group-title {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
padding: 6px 12px 4px;
font-weight: 500;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.menu-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.15s ease;
font-size: 13px;
color: ${DESIGN.colors.text};
}
.menu-item:hover {
background-color: ${DESIGN.colors.hover};
}
.item-icon {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
color: ${DESIGN.colors.primary};
}
.item-label {
flex: 1;
font-size: 13px;
}
.item-hint {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
margin-left: 8px;
background: ${DESIGN.colors.hover};
padding: 2px 6px;
border-radius: 3px;
}
/* 通知样式 */
.menu-notification {
position: fixed;
top: 16px;
right: 16px;
background: ${DESIGN.colors.background};
border-radius: 6px;
box-shadow: ${DESIGN.shadows.notification};
padding: 10px 12px;
z-index: 1000000;
transform: translateX(120%);
transition: transform 0.25s ease;
border-left: 3px solid ${DESIGN.colors.primary};
max-width: 260px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.menu-notification.show {
transform: translateX(0);
}
.notification-success {
border-left-color: ${DESIGN.colors.success};
}
.notification-error {
border-left-color: ${DESIGN.colors.error};
}
.notification-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
font-weight: bold;
font-size: 12px;
margin-top: 1px;
}
.notification-content {
flex: 1;
}
.notification-title {
font-weight: 600;
font-size: 12px;
color: ${DESIGN.colors.text};
margin-bottom: 2px;
}
.notification-message {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
line-height: 1.3;
}
/* 动画效果 */
@keyframes itemFadeIn {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-in {
animation: itemFadeIn 0.15s ease forwards;
opacity: 0;
}
`;
document.head.appendChild(style);
}
// 创建菜单
function createMenu() {
// 移除已存在的菜单
const existingMenu = document.getElementById('link-menu');
if (existingMenu) existingMenu.remove();
// 创建新菜单
menuElement = document.createElement('div');
menuElement.id = 'link-menu';
menuElement.innerHTML = `
<div class="menu-group">
<div class="group-title">链接操作</div>
<div class="menu-item" data-action="copy-link-text">
<div class="item-icon">📝</div>
<div class="item-label">复制链接文字</div>
</div>
<div class="menu-item" data-action="copy-link-url">
<div class="item-icon">🔗</div>
<div class="item-label">复制链接地址</div>
</div>
<div class="menu-item" data-action="copy-text-url">
<div class="item-icon">📄</div>
<div class="item-label">文字 + 链接</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">格式化复制</div>
<div class="menu-item" data-action="copy-markdown">
<div class="item-icon">📋</div>
<div class="item-label">Markdown格式</div>
<div class="item-hint">[文字](链接)</div>
</div>
<div class="menu-item" data-action="copy-html">
<div class="item-icon">🌐</div>
<div class="item-label">HTML格式</div>
<div class="item-hint"><a>标签</div>
</div>
<div class="menu-item" data-action="copy-bbcode">
<div class="item-icon">📋</div>
<div class="item-label">BBCode格式</div>
<div class="item-hint">标签</div>
</div>
</div>
<!-- 文本操作分组 - 默认隐藏 -->
<div class="menu-group" id="text-group" style="display: none;">
<div class="group-title">文本操作</div>
<div class="menu-item" data-action="copy-selected-text">
<div class="item-icon">📋</div>
<div class="item-label">复制选中文字</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">网页操作</div>
<div class="menu-item" data-action="open-new-tab">
<div class="item-icon">➕</div>
<div class="item-label">新标签页打开</div>
</div>
<div class="menu-item" data-action="copy-page-url">
<div class="item-icon">📄</div>
<div class="item-label">复制页面地址</div>
</div>
<div class="menu-item" data-action="view-source">
<div class="item-icon">📄</div>
<div class="item-label">查看源代码</div>
<div class="item-hint">Ctrl+U</div>
</div>
</div>
`;
document.body.appendChild(menuElement);
// 为菜单项添加点击事件
menuElement.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', handleMenuClick);
});
}
// 添加事件监听器
function attachEventListeners() {
// 监听右键点击
document.addEventListener('contextmenu', function(e) {
const target = e.target;
const link = target.closest('a');
if (link) {
// 阻止默认右键菜单
e.preventDefault();
e.stopPropagation();
// 获取选中文本
selectedText = window.getSelection().toString().trim();
// 更新状态
currentLink = link;
// 更新菜单显示
updateMenuItems(selectedText);
// 显示菜单
showMenu(e.clientX, e.clientY);
return false;
}
}, true);
// 点击其他地方隐藏菜单
document.addEventListener('click', function(e) {
if (menuElement && !menuElement.contains(e.target) && isMenuVisible) {
hideMenu();
}
}, true);
// ESC键隐藏菜单
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isMenuVisible) {
hideMenu();
e.preventDefault();
}
// 快捷键:Ctrl+Shift+C 复制链接文字
if (e.ctrlKey && e.shiftKey && e.key === 'C' && currentLink) {
e.preventDefault();
copyLinkText();
}
// 快捷键:Ctrl+Shift+L 复制链接地址
if (e.ctrlKey && e.shiftKey && e.key === 'L' && currentLink) {
e.preventDefault();
copyLinkUrl();
}
});
// 窗口大小变化时重新定位菜单
window.addEventListener('resize', function() {
if (isMenuVisible) {
repositionMenu();
}
});
}
// 更新菜单项
function updateMenuItems(text) {
if (!menuElement) return;
// 获取文本操作分组
const textGroup = menuElement.querySelector('#text-group');
if (textGroup) {
if (text && text.length > 0) {
// 有选中文字,显示文本操作分组
textGroup.style.display = 'block';
} else {
// 没有选中文字,隐藏文本操作分组
textGroup.style.display = 'none';
}
}
}
// 显示菜单
function showMenu(x, y) {
if (!menuElement) return;
// 显示菜单
menuElement.style.display = 'block';
// 设置初始位置
menuElement.style.left = x + 'px';
menuElement.style.top = y + 'px';
// 确保菜单位置正确
repositionMenu();
// 添加显示动画
setTimeout(() => {
menuElement.classList.add('visible');
isMenuVisible = true;
// 添加项目动画
setTimeout(() => {
const items = menuElement.querySelectorAll('.menu-item:not()');
items.forEach((item, index) => {
item.style.animationDelay = `${index * 0.012}s`;
item.classList.add('animate-in');
});
}, 10);
}, 10);
}
// 重新定位菜单
function repositionMenu() {
if (!menuElement) return;
const rect = menuElement.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
let left = parseInt(menuElement.style.left) || 0;
let top = parseInt(menuElement.style.top) || 0;
// 调整水平位置
if (left + rect.width > windowWidth) {
left = Math.max(10, windowWidth - rect.width - 10);
}
// 调整垂直位置
if (top + rect.height > windowHeight) {
top = Math.max(10, windowHeight - rect.height - 10);
}
// 应用新位置
menuElement.style.left = left + 'px';
menuElement.style.top = top + 'px';
}
// 隐藏菜单
function hideMenu() {
if (menuElement) {
menuElement.classList.remove('visible');
isMenuVisible = false;
// 移除项目动画类
menuElement.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('animate-in');
item.style.animationDelay = '';
});
// 延迟隐藏
setTimeout(() => {
if (!isMenuVisible) {
menuElement.style.display = 'none';
currentLink = null;
selectedText = '';
}
}, 100);
}
}
// 处理菜单点击
function handleMenuClick(e) {
const action = this.getAttribute('data-action');
switch(action) {
// 链接操作
case 'copy-link-text':
copyLinkText();
break;
case 'copy-link-url':
copyLinkUrl();
break;
case 'copy-text-url':
copyTextAndUrl();
break;
// 格式化复制
case 'copy-markdown':
copyAsMarkdown();
break;
case 'copy-html':
copyAsHtml();
break;
case 'copy-bbcode':
copyAsBBCode();
break;
// 文本操作
case 'copy-selected-text':
copySelectedText();
break;
// 网页操作
case 'open-new-tab':
openNewTab();
break;
case 'copy-page-url':
copyPageUrl();
break;
case 'view-source':
viewSource();
break;
}
hideMenu();
}
// ========== 功能函数 ==========
// 获取链接文字
function getLinkText(link) {
let text = link.innerText || link.textContent || '';
text = text.replace(/\s+/g, ' ').trim();
if (!text) {
text = link.getAttribute('title') ||
link.getAttribute('aria-label') ||
link.querySelector('img')?.getAttribute('alt') || '';
}
return text;
}
// 1. 复制链接文字
function copyLinkText() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const text = getLinkText(currentLink);
if (text) {
GM_setClipboard(text);
showNotification('复制成功', '已复制链接文字');
} else {
showNotification('错误', '链接没有文字内容', 'error');
}
}
// 2. 复制链接地址
function copyLinkUrl() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const url = currentLink.href;
if (url) {
GM_setClipboard(url);
showNotification('复制成功', '已复制链接地址');
} else {
showNotification('错误', '链接没有地址', 'error');
}
}
// 3. 复制文字和链接
function copyTextAndUrl() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
const content = `${text} ${url}`;
GM_setClipboard(content);
showNotification('复制成功', '已复制文字和链接');
} else {
showNotification('错误', '无法复制文字和链接', 'error');
}
}
// 4. 复制为Markdown格式
function copyAsMarkdown() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
const content = `[${text}](${url})`;
GM_setClipboard(content);
showNotification('复制成功', '已复制为Markdown格式');
} else {
showNotification('错误', '无法生成Markdown格式', 'error');
}
}
// 5. 复制为HTML格式
function copyAsHtml() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
const content = `<a href="${url}">${text}</a>`;
GM_setClipboard(content);
showNotification('复制成功', '已复制为HTML格式');
} else {
showNotification('错误', '无法生成HTML格式', 'error');
}
}
// 6. 复制为BBCode格式
function copyAsBBCode() {
if (!currentLink) {
showNotification('错误', '没有找到链接', 'error');
return;
}
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
const content = `${text}`;
GM_setClipboard(content);
showNotification('复制成功', '已复制为BBCode格式');
} else {
showNotification('错误', '无法生成BBCode格式', 'error');
}
}
// 7. 复制选中文字
function copySelectedText() {
if (selectedText) {
GM_setClipboard(selectedText);
showNotification('复制成功', '已复制选中文字');
} else {
showNotification('错误', '没有选中文字', 'error');
}
}
// 8. 新标签页打开链接
function openNewTab() {
if (currentLink) {
GM_openInTab(currentLink.href, { active: true });
showNotification('新标签页', '正在打开链接...');
} else {
showNotification('错误', '没有找到链接', 'error');
}
}
// 9. 复制页面地址
function copyPageUrl() {
const url = window.location.href;
GM_setClipboard(url);
showNotification('复制成功', '已复制页面地址');
}
// 10. 查看源代码
function viewSource() {
const sourceUrl = `view-source:${window.location.href}`;
GM_openInTab(sourceUrl, { active: true });
showNotification('源代码', '正在打开源代码页面...');
}
// 显示通知
function showNotification(title, message, type = 'success') {
// 移除旧通知
const oldNotification = document.querySelector('.menu-notification');
if (oldNotification) {
oldNotification.remove();
}
// 创建新通知
const notification = document.createElement('div');
notification.className = `menu-notification notification-${type}`;
const icon = type === 'success' ? '✓' : '✗';
notification.innerHTML = `
<div class="notification-icon">${icon}</div>
<div class="notification-content">
<div class="notification-title">${title}</div>
<div class="notification-message">${message}</div>
</div>
`;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => notification.classList.add('show'), 10);
// 自动隐藏
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 200);
}, 1500);
// 点击隐藏
notification.addEventListener('click', function() {
this.classList.remove('show');
setTimeout(() => {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
}, 200);
});
}
// 初始化脚本
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
修复了一下存在的问题
// ==UserScript==
// @name 右键链接增强
// @namespace http://tampermonkey.net/
// @version 1.1
// @description右键链接快速复制文字、地址及多种格式,智能识别选中文本
// @AuThor MRBANK
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_openInTab
// @grant GM_notification
// @Icon https://img.icons8.com/color/96/000000/copy-link.png
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// 设计配置
const DESIGN = {
menuWidth: 240,
borderRadius: 8,
animationSpeed: 100,
colors: {
primary: '#3b82f6',
success: '#10b981',
error: '#ef4444',
background: '#ffffff',
backgroundDark: '#1f2937',
hover: '#f3f4f6',
hoverDark: '#374151',
border: '#e5e7eb',
borderDark: '#374151',
text: '#111827',
textDark: '#f9fafb',
subtitle: '#6b7280',
subtitleDark: '#9ca3af',
hintBg: 'rgba(0,0,0,0.07)',
hintBgDark: 'rgba(255,255,255,0.1)',
},
shadows: {
menu: '0 6px 16px rgba(0, 0, 0, 0.12)',
notification: '0 4px 12px rgba(0, 0, 0, 0.1)'
},
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
};
// 状态变量
let currentLink = null;
let selectedText = '';
let menuElement = null;
let isMenuVisible = false;
// 检测暗色模式
function isDarkMode() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
// 初始化
function init() {
injectStyles();
createMenu();
attachEventListeners();
console.log('链接右键智能增强已加载 v1.1');
}
// 注入CSS样式
function injectStyles() {
const style = document.createElement('style');
style.textContent = `
#link-menu {
position: fixed;
/* fix 1: 使用最大 z-index 避免被任何元素遮挡 */
z-index: 2147483646;
background: ${DESIGN.colors.background};
border-radius: ${DESIGN.borderRadius}px;
box-shadow: ${DESIGN.shadows.menu};
font-family: ${DESIGN.fontFamily};
width: ${DESIGN.menuWidth}px;
opacity: 0;
transform: scale(0.95);
transition: opacity ${DESIGN.animationSpeed}ms ease-out, transform ${DESIGN.animationSpeed}ms ease-out;
border: 1px solid ${DESIGN.colors.border};
overflow: hidden;
display: none;
padding: 6px 0;
}
/* fix 2: 暗色模式适配 */
@media (prefers-color-scheme: dark) {
#link-menu {
background: ${DESIGN.colors.backgroundDark};
border-color: ${DESIGN.colors.borderDark};
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
}
#link-menu.visible {
opacity: 1;
transform: scale(1);
}
.menu-group {
padding: 4px 0;
border-bottom: 1px solid ${DESIGN.colors.border};
}
@media (prefers-color-scheme: dark) {
.menu-group {
border-bottom-color: ${DESIGN.colors.borderDark};
}
}
.menu-group:last-child {
border-bottom: none;
}
.group-title {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
padding: 6px 12px 4px;
font-weight: 500;
letter-spacing: 0.5px;
text-transform: uppercase;
}
@media (prefers-color-scheme: dark) {
.group-title {
color: ${DESIGN.colors.subtitleDark};
}
}
.menu-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.15s ease;
font-size: 13px;
color: ${DESIGN.colors.text};
text-decoration: none !important;
/* fix 3: 防止长内容撑破菜单宽度 */
white-space: nowrap;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
.menu-item {
color: ${DESIGN.colors.textDark};
}
}
.menu-item:hover {
background-color: ${DESIGN.colors.hover};
}
@media (prefers-color-scheme: dark) {
.menu-item:hover {
background-color: ${DESIGN.colors.hoverDark};
}
}
.item-icon {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
color: ${DESIGN.colors.primary};
}
.item-label {
flex: 1;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
}
.item-hint {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
margin-left: 8px;
/* fix 4: 使用半透明背景适配亮/暗两种主题 */
background: ${DESIGN.colors.hintBg};
padding: 2px 6px;
border-radius: 3px;
flex-shrink: 0;
}
@media (prefers-color-scheme: dark) {
.item-hint {
color: ${DESIGN.colors.subtitleDark};
background: ${DESIGN.colors.hintBgDark};
}
}
/* 通知样式 */
.menu-notification {
position: fixed;
/* fix 5: 通知使用最高 z-index */
z-index: 2147483647;
/* fix 6: 改为右下角,减少与顶部导航栏冲突 */
bottom: 20px;
right: 16px;
background: ${DESIGN.colors.background};
border-radius: 6px;
box-shadow: ${DESIGN.shadows.notification};
padding: 10px 12px;
transform: translateX(120%);
transition: transform 0.25s ease;
border-left: 3px solid ${DESIGN.colors.primary};
max-width: 260px;
display: flex;
align-items: flex-start;
gap: 8px;
}
@media (prefers-color-scheme: dark) {
.menu-notification {
background: ${DESIGN.colors.backgroundDark};
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
}
.menu-notification.show {
transform: translateX(0);
}
.notification-success {
border-left-color: ${DESIGN.colors.success};
}
.notification-error {
border-left-color: ${DESIGN.colors.error};
}
.notification-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
font-weight: bold;
font-size: 12px;
margin-top: 1px;
}
.notification-content {
flex: 1;
}
.notification-title {
font-weight: 600;
font-size: 12px;
color: ${DESIGN.colors.text};
margin-bottom: 2px;
}
@media (prefers-color-scheme: dark) {
.notification-title {
color: ${DESIGN.colors.textDark};
}
}
.notification-message {
font-size: 11px;
color: ${DESIGN.colors.subtitle};
line-height: 1.3;
}
@media (prefers-color-scheme: dark) {
.notification-message {
color: ${DESIGN.colors.subtitleDark};
}
}
/* 动画效果 */
@keyframes lm-itemFadeIn {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.lm-animate-in {
animation: lm-itemFadeIn 0.15s ease forwards;
opacity: 0;
}
`;
document.head.appendChild(style);
}
// 创建菜单
function createMenu() {
const existingMenu = document.getElementById('link-menu');
if (existingMenu) existingMenu.remove();
menuElement = document.createElement('div');
menuElement.id = 'link-menu';
// fix 7: <a> 标签使用文本节点,避免 innerHTML 解析问题
menuElement.innerHTML = `
<div class="menu-group">
<div class="group-title">链接操作</div>
<div class="menu-item" data-action="copy-link-text">
<div class="item-icon">📝</div>
<div class="item-label">复制链接文字</div>
</div>
<div class="menu-item" data-action="copy-link-url">
<div class="item-icon">🔗</div>
<div class="item-label">复制链接地址</div>
</div>
<div class="menu-item" data-action="copy-text-url">
<div class="item-icon">📄</div>
<div class="item-label">文字 + 链接</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">格式化复制</div>
<div class="menu-item" data-action="copy-markdown">
<div class="item-icon">📋</div>
<div class="item-label">Markdown 格式</div>
<div class="item-hint">[文字](链接)</div>
</div>
<div class="menu-item" data-action="copy-html">
<div class="item-icon">🌐</div>
<div class="item-label">HTML 格式</div>
<div class="item-hint html-tag-hint"></div>
</div>
<div class="menu-item" data-action="copy-bbcode">
<div class="item-icon">📋</div>
<div class="item-label">BBCode 格式</div>
<div class="item-hint">标签</div>
</div>
</div>
<!-- 文本操作分组 - 默认隐藏 -->
<div class="menu-group" id="text-group" style="display: none;">
<div class="group-title">文本操作</div>
<div class="menu-item" data-action="copy-selected-text">
<div class="item-icon">📋</div>
<div class="item-label">复制选中文字</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">网页操作</div>
<div class="menu-item" data-action="open-new-tab">
<div class="item-icon">➕</div>
<div class="item-label">新标签页打开</div>
</div>
<div class="menu-item" data-action="copy-page-url">
<div class="item-icon">📄</div>
<div class="item-label">复制页面地址</div>
</div>
<div class="menu-item" data-action="view-source">
<div class="item-icon">📄</div>
<div class="item-label">查看源代码</div>
<div class="item-hint">Ctrl+U</div>
</div>
</div>
`;
// fix 7: 用 textContent 安全写入含 HTML 标签的提示文字
const htmlHint = menuElement.querySelector('.html-tag-hint');
if (htmlHint) htmlHint.textContent = '<a>标签';
document.body.appendChild(menuElement);
menuElement.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', handleMenuClick);
});
}
// 添加事件监听器
function attachEventListeners() {
document.addEventListener('contextmenu', function (e) {
const target = e.target;
const link = target.closest('a');
if (link) {
e.preventDefault();
e.stopPropagation();
selectedText = window.getSelection().toString().trim();
currentLink = link;
updateMenuItems(selectedText);
showMenu(e.clientX, e.clientY);
return false;
}
}, true);
document.addEventListener('click', function (e) {
if (menuElement && !menuElement.contains(e.target) && isMenuVisible) {
hideMenu();
}
}, true);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && isMenuVisible) {
hideMenu();
e.preventDefault();
}
if (e.ctrlKey && e.shiftKey && e.key === 'C' && currentLink) {
e.preventDefault();
copyLinkText();
}
if (e.ctrlKey && e.shiftKey && e.key === 'L' && currentLink) {
e.preventDefault();
copyLinkUrl();
}
});
window.addEventListener('resize', function () {
if (isMenuVisible) {
repositionMenu();
}
});
}
// 更新菜单项
function updateMenuItems(text) {
if (!menuElement) return;
const textGroup = menuElement.querySelector('#text-group');
if (textGroup) {
textGroup.style.display = (text && text.length > 0) ? 'block' : 'none';
}
}
// 显示菜单
function showMenu(x, y) {
if (!menuElement) return;
// 先清理上一次的动画 class,再重置为初始状态
menuElement.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('lm-animate-in');
item.style.animationDelay = '';
});
menuElement.style.display = 'block';
menuElement.style.left = x + 'px';
menuElement.style.top = y + 'px';
// fix 8: 用双 rAF 替代 setTimeout,确保 display:block 已完成布局再触发 transition
requestAnimationFrame(() => {
repositionMenu();
requestAnimationFrame(() => {
menuElement.classList.add('visible');
isMenuVisible = true;
const items = menuElement.querySelectorAll('.menu-item');
items.forEach((item, index) => {
item.style.animationDelay = `${index * 0.012}s`;
item.classList.add('lm-animate-in');
});
});
});
}
// 重新定位菜单
function repositionMenu() {
if (!menuElement) return;
const rect = menuElement.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
let left = parseInt(menuElement.style.left) || 0;
let top = parseInt(menuElement.style.top) || 0;
if (left + rect.width > windowWidth) {
left = Math.max(10, windowWidth - rect.width - 10);
}
if (top + rect.height > windowHeight) {
top = Math.max(10, windowHeight - rect.height - 10);
}
menuElement.style.left = left + 'px';
menuElement.style.top = top + 'px';
}
// 隐藏菜单
function hideMenu() {
if (!menuElement) return;
menuElement.classList.remove('visible');
isMenuVisible = false;
// fix 9: 立即同步清理动画状态,避免下次打开时 delay 残留
menuElement.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('lm-animate-in');
item.style.animationDelay = '';
});
setTimeout(() => {
if (!isMenuVisible) {
menuElement.style.display = 'none';
currentLink = null;
selectedText = '';
}
}, DESIGN.animationSpeed + 20);
}
// 处理菜单点击
function handleMenuClick(e) {
const action = this.getAttribute('data-action');
switch (action) {
case 'copy-link-text': copyLinkText(); break;
case 'copy-link-url': copyLinkUrl(); break;
case 'copy-text-url': copyTextAndUrl(); break;
case 'copy-markdown': copyAsMarkdown(); break;
case 'copy-html': copyAsHtml(); break;
case 'copy-bbcode': copyAsBBCode(); break;
case 'copy-selected-text':copySelectedText();break;
case 'open-new-tab': openNewTab(); break;
case 'copy-page-url': copyPageUrl(); break;
case 'view-source': viewSource(); break;
}
hideMenu();
}
// ========== 功能函数 ==========
function getLinkText(link) {
let text = link.innerText || link.textContent || '';
text = text.replace(/\s+/g, ' ').trim();
if (!text) {
text = link.getAttribute('title') ||
link.getAttribute('aria-label') ||
link.querySelector('img')?.getAttribute('alt') || '';
}
return text;
}
function copyLinkText() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const text = getLinkText(currentLink);
if (text) {
GM_setClipboard(text);
showNotification('复制成功', '已复制链接文字');
} else {
showNotification('错误', '链接没有文字内容', 'error');
}
}
function copyLinkUrl() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const url = currentLink.href;
if (url) {
GM_setClipboard(url);
showNotification('复制成功', '已复制链接地址');
} else {
showNotification('错误', '链接没有地址', 'error');
}
}
function copyTextAndUrl() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
GM_setClipboard(`${text} ${url}`);
showNotification('复制成功', '已复制文字和链接');
} else {
showNotification('错误', '无法复制文字和链接', 'error');
}
}
function copyAsMarkdown() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
GM_setClipboard(`[${text}](${url})`);
showNotification('复制成功', '已复制为 Markdown 格式');
} else {
showNotification('错误', '无法生成 Markdown 格式', 'error');
}
}
function copyAsHtml() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
GM_setClipboard(`<a href="${url}">${text}</a>`);
showNotification('复制成功', '已复制为 HTML 格式');
} else {
showNotification('错误', '无法生成 HTML 格式', 'error');
}
}
function copyAsBBCode() {
if (!currentLink) { showNotification('错误', '没有找到链接', 'error'); return; }
const text = getLinkText(currentLink);
const url = currentLink.href;
if (text && url) {
GM_setClipboard(`${text}`);
showNotification('复制成功', '已复制为 BBCode 格式');
} else {
showNotification('错误', '无法生成 BBCode 格式', 'error');
}
}
function copySelectedText() {
if (selectedText) {
GM_setClipboard(selectedText);
showNotification('复制成功', '已复制选中文字');
} else {
showNotification('错误', '没有选中文字', 'error');
}
}
function openNewTab() {
if (currentLink) {
GM_openInTab(currentLink.href, { active: true });
showNotification('新标签页', '正在打开链接...');
} else {
showNotification('错误', '没有找到链接', 'error');
}
}
function copyPageUrl() {
GM_setClipboard(window.location.href);
showNotification('复制成功', '已复制页面地址');
}
function viewSource() {
GM_openInTab(`view-source:${window.location.href}`, { active: true });
showNotification('源代码', '正在打开源代码页面...');
}
// 显示通知
// fix 10: 多条通知垂直堆叠,避免互相遮挡
function showNotification(title, message, type = 'success') {
const oldNotifications = document.querySelectorAll('.menu-notification');
oldNotifications.forEach(n => {
n.classList.remove('show');
setTimeout(() => n.parentNode && n.parentNode.removeChild(n), 200);
});
const notification = document.createElement('div');
notification.className = `menu-notification notification-${type}`;
notification.style.fontFamily = DESIGN.fontFamily;
const icon = type === 'success' ? '✓' : '✗';
// fix 11: 通知内容全部用 textContent,无注入风险
const iconEl = document.createElement('div');
iconEl.className = 'notification-icon';
iconEl.textContent = icon;
const contentEl = document.createElement('div');
contentEl.className = 'notification-content';
const titleEl = document.createElement('div');
titleEl.className = 'notification-title';
titleEl.textContent = title;
const msgEl = document.createElement('div');
msgEl.className = 'notification-message';
msgEl.textContent = message;
contentEl.appendChild(titleEl);
contentEl.appendChild(msgEl);
notification.appendChild(iconEl);
notification.appendChild(contentEl);
document.body.appendChild(notification);
requestAnimationFrame(() => {
requestAnimationFrame(() => notification.classList.add('show'));
});
const dismiss = () => {
notification.classList.remove('show');
setTimeout(() => notification.parentNode && notification.parentNode.removeChild(notification), 200);
};
setTimeout(dismiss, 1800);
notification.addEventListener('click', dismiss);
}
// 初始化脚本
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
xouou 发表于 2026-2-7 22:55
没有反应
链接!!只支持在链接上右键显示
没有反应 禁止复制和粘贴那种能直接复制么?比如12315这种 功能很好 支持原创 谢谢 感谢分享 感谢分享,试了一下,挺好用的 好东西啊 铁子。学习了,拿去用了 好东西,感谢分享
1.背景颜色,有些浏览器不清楚
2.有时候或存在多个$$$$$$$$$$$$$$$