nodeseek 自动代码高亮 (Highlight.js + 复制按钮)

自动检测并高亮 nodeseek 内的代码块(支持自动语言识别),并添加一个“复制”按钮。

// ==UserScript==
// @name        nodeseek 自动代码高亮 (Highlight.js + 复制按钮)
// @namespace   http://tampermonkey.net/
// @version     4.1
// @description 自动检测并高亮 nodeseek 内的代码块(支持自动语言识别),并添加一个“复制”按钮。
// @author      三七
// @match       https://*.nodeseek.com/*
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @connect     cdn.jsdelivr.net
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置部分 ---
    const HLJS_CSS_URL = "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/base16/humanoid-light.min.css";
    const HLJS_JS_URL = "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js";

    // --- 复制按钮的样式 (完全可自定义) ---
    GM_addStyle(`
        pre > code.hljs {
            position: relative;
        }
        .copy-code-button {
            position: absolute;
            top: 0.5em;
            right: 0.5em;
            padding: 4px 8px;
            font-size: 12px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            color: #ccc;
            background-color: #444;
            border: 1px solid #666;
            border-radius: 4px;
            cursor: pointer;
            opacity: 0;
            transition: opacity 0.2s ease-in-out, background-color 0.2s, color 0.2s, border-color 0.2s;
            z-index: 10;
        }
        pre:hover .copy-code-button {
            opacity: 1;
        }
        .copy-code-button:hover {
            background-color: #555;
            color: #fff;
        }
        .copy-code-button.copied {
            background-color: #28a745; /* 成功时用绿色 */
            color: white;
            border-color: #28a745;
        }
    `);

    // --- 核心逻辑 (loadScript 和 debounce 保持不变) ---

    let hljsLoaded = false;
    let hljsLoading = false;

    function debounce(func, delay) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), delay);
        };
    }

    function loadScript(url, callback) {
        if (hljsLoaded) {
            if (callback) callback();
            return;
        }
        if (hljsLoading) {
            document.addEventListener('hljs-loaded', callback, { once: true });
            return;
        }
        hljsLoading = true;
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(response) {
                const script = document.createElement('script');
                script.textContent = response.responseText;
                document.head.appendChild(script).remove();
                hljsLoaded = true;
                hljsLoading = false;
                console.log(`[Auto Highlighter] Loaded JS: ${url}`);
                document.dispatchEvent(new Event('hljs-loaded'));
                if (callback) callback();
            },
            onerror: function(response) {
                console.error(`[Auto Highlighter] Failed to load script ${url}:`, response);
                hljsLoading = false;
            }
        });
    }

    // --- 添加复制按钮的函数 (已修复) ---
    function addCopyButtons() {
        document.querySelectorAll('pre > code.hljs').forEach(codeBlock => {
            if (codeBlock.querySelector('.copy-code-button')) {
                return;
            }

            const button = document.createElement('button');
            button.className = 'copy-code-button';
            button.textContent = '复制'; // [汉化] 默认文字

            button.addEventListener('click', (event) => {
                // [BUG 修复]
                // 1. 克隆 codeBlock 节点,这样我们不会影响原始 DOM
                const codeClone = codeBlock.cloneNode(true);
                // 2. 从克隆的节点中移除按钮
                const buttonInClone = codeClone.querySelector('.copy-code-button');
                if (buttonInClone) {
                    buttonInClone.remove();
                }
                // 3. 现在从干净的克隆节点获取文本
                const codeToCopy = codeClone.innerText;

                navigator.clipboard.writeText(codeToCopy).then(() => {
                    button.textContent = '已复制!'; // [汉化] 成功提示
                    button.classList.add('copied');
                    setTimeout(() => {
                        button.textContent = '复制'; // [汉化] 恢复默认
                        button.classList.remove('copied');
                    }, 2000);
                }).catch(err => {
                    console.error('复制失败: ', err);
                    button.textContent = '错误'; // [汉化] 失败提示
                });
            });

            codeBlock.appendChild(button);
        });
    }


    function findAndHighlight() {
        const codeBlocks = document.querySelectorAll('pre > code:not(.hljs)');
        if (codeBlocks.length > 0) {
            console.log(`[Auto Highlighter] Found ${codeBlocks.length} unhighlighted code blocks.`);
            loadScript(HLJS_JS_URL, () => {
                if (typeof hljs !== 'undefined') {
                    console.log('[Auto Highlighter] Highlighting now...');
                    codeBlocks.forEach(element => {
                        hljs.highlightElement(element);
                    });
                    console.log('[Auto Highlighter] Highlighting complete.');
                    addCopyButtons();
                } else {
                    console.warn('[Auto Highlighter] hljs not available for highlighting.');
                }
            });
        }
        addCopyButtons();
    }

    // --- 启动逻辑 ---

    GM_addStyle('@import url("' + HLJS_CSS_URL + '");');
    const debouncedFindAndHighlight = debounce(findAndHighlight, 300);
    setTimeout(findAndHighlight, 100);
    window.addEventListener('load', findAndHighlight);

    const observer = new MutationObserver(() => {
        debouncedFindAndHighlight();
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();