Greasy Fork

Greasy Fork is available in English.

万能复制插件(预览 + 历史 + 美观提示)

Ctrl+点击或长按复制网页内容,附带来源 + 高亮 + 音效 + 弹窗预览 + 历史记录

当前为 2025-07-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         万能复制插件(预览 + 历史 + 美观提示)
// @namespace    http://tampermonkey.net/
// @version      10.0
// @description  Ctrl+点击或长按复制网页内容,附带来源 + 高亮 + 音效 + 弹窗预览 + 历史记录
// @author       You
// @match        *://*/*
// @grant        GM_setClipboard
// @grant        GM_registerMenuCommand
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// ==/UserScript==

(function () {
    'use strict';

    const allowTags = ['div', 'span', 'p', 'pre', 'code', 'td', 'input', 'textarea', 'li'];
    const ignoreTags = ['button', 'a', 'script', 'style', 'footer', 'nav'];
    const copyHistory = [];
    let lastRightClickedEl = null;

    function playSound() {
        const audio = new Audio("data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YYAAAAA=");
        audio.play().catch(() => {});
    }

    function highlight(el) {
        const prev = el.style.backgroundColor;
        el.style.transition = 'background-color 0.3s';
        el.style.backgroundColor = '#ffe08a';
        setTimeout(() => el.style.backgroundColor = prev, 1500);
    }

    function showTip(text) {
        Swal.fire({
            toast: true,
            position: 'top-end',
            showConfirmButton: false,
            timer: 1800,
            background: '#28a745',
            color: 'white',
            icon: 'success',
            title: text
        });
    }

    function preview(text) {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: white;
            color: black;
            padding: 10px;
            max-width: 400px;
            max-height: 200px;
            overflow: auto;
            border: 1px solid #aaa;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
            border-radius: 6px;
            z-index: 100000;
            font-family: monospace;
            white-space: pre-wrap;
        `;
        panel.textContent = text;
        document.body.appendChild(panel);
        setTimeout(() => panel.remove(), 5000);
    }

    function getText(el) {
        const tag = el.tagName.toLowerCase();
        if (tag === 'input' || tag === 'textarea') return el.value.trim();
        return el.innerText?.trim() || el.textContent?.trim() || '';
    }

    function isCopyable(el) {
        const tag = el.tagName.toLowerCase();
        if (ignoreTags.includes(tag)) return false;
        return allowTags.includes(tag);
    }

    function appendSource(text) {
        const url = window.location.href;
        const title = document.title;
        return `${text}\n\n【来源】${title}\n${url}`;
    }

    function copyWithSource(text) {
        if (!text || !text.trim()) return;
        const finalText = appendSource(text.trim());

        if (navigator.clipboard) {
            navigator.clipboard.writeText(finalText).then(() => {
                playSound();
                preview(finalText);
                showTip('✅ 已复制(附带来源)');
            }).catch(() => {
                GM_setClipboard(finalText);
                showTip('✅ 兼容复制成功');
            });
        } else {
            GM_setClipboard(finalText);
            showTip('✅ 复制成功');
        }

        copyHistory.unshift(finalText);
        if (copyHistory.length > 10) copyHistory.pop();
    }

    document.addEventListener('click', (e) => {
        if (!e.ctrlKey) return;
        let el = e.target;
        while (el && el !== document.body) {
            if (isCopyable(el)) {
                const text = getText(el);
                if (text) {
                    copyWithSource(text);
                    highlight(el);
                    break;
                }
            }
            el = el.parentElement;
        }
    });

    let timer = null;
    document.addEventListener('mousedown', (e) => {
        let el = e.target;
        while (el && el !== document.body) {
            if (isCopyable(el)) {
                timer = setTimeout(() => {
                    const text = getText(el);
                    if (text) {
                        copyWithSource(text);
                        highlight(el);
                    }
                }, 600);
                break;
            }
            el = el.parentElement;
        }
    });
    document.addEventListener('mouseup', () => clearTimeout(timer));
    document.addEventListener('mouseleave', () => clearTimeout(timer));

    document.addEventListener('contextmenu', (e) => {
        lastRightClickedEl = e.target;
    });

    GM_registerMenuCommand('📋 复制右键目标内容', () => {
        if (!lastRightClickedEl) return showTip('无记录');
        let el = lastRightClickedEl;
        while (el && el !== document.body) {
            if (isCopyable(el)) {
                const text = getText(el);
                if (text) {
                    copyWithSource(text);
                    highlight(el);
                    return;
                }
            }
            el = el.parentElement;
        }
        showTip('无可复制内容');
    });

    GM_registerMenuCommand('📑 查看复制历史', () => {
        const html = copyHistory.slice(0, 5).map((item, idx) => {
            return `<div style="text-align:left;"><b>${idx + 1}.</b><br><pre style="white-space:pre-wrap;font-size:13px;background:#f8f9fa;padding:6px;border:1px solid #ccc;border-radius:4px;">${item}</pre></div>`;
        }).join('<hr>');
        Swal.fire({
            title: '📋 最近复制记录',
            html: html || '暂无记录',
            width: 600,
            confirmButtonText: '关闭',
            customClass: {
                popup: 'swal2-noanimation',
            }
        });
    });
})();