HKUST-GPT-CodeEnhancer-Pro

增强HKUST GPT的代码显示(自动语法高亮+一键复制)

// ==UserScript==
// @name         HKUST-GPT-CodeEnhancer-Pro
// @namespace    http://tampermonkey.net/
// @version      1.0.4
// @description  增强HKUST GPT的代码显示(自动语法高亮+一键复制)
// @author       PrimoPan
// @match        https://gpt.hkust-gz.edu.cn/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js
// @resource     prismCSS https://unpkg.com/prism-theme-night-owl@latest/build/style.css
// @grant        GM_addStyle
// @grant        GM_getResourceText
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        codeSelector: 'pre > code[class^="language-"]',
        copyBtnClass: 'gpt-copy-btn-pro',
        styles: {
            base: `
                position: absolute;
                right: 10px;
                top: 10px;
                background-color: rgba(51, 51, 51, 0.8);
                color:#fff !important;
                border-radius:4px;
                padding:.5em .8em;
                font-size:.85rem;
                cursor:pointer;
                border:none;
                transition:.15s all ease-out;
                z-index:1000`,
            hover: 'background-color:#444!important',
            copied: 'background-color:#27ae60!important'
        },
        observerConfig: {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        }
    };

    //============= Core Functions ===============//

    function initCore() {
        injectPrismTheme();
        document.querySelectorAll(CONFIG.codeSelector).forEach(codeBlock => {
            if (!codeBlock.closest('.processed')) processCodeBlock(codeBlock);
        });
        Prism.highlightAll();
        setupObserver();
        console.log('[CodePro] Initialized');
    }

    function processCodeBlock(codeEl) {
        const wrapper = wrapWithDiv(codeEl.parentElement);
        if (!wrapper.querySelector('.' + CONFIG.copyBtnClass)) {
            attachCopyButton(wrapper);
            codeEl.classList.add('processed');
        }
    }

    function wrapWithDiv(preElement) {
        const wrapper = document.createElement('div');
        Object.assign(wrapper.style, {
            position: 'relative',
            marginTop: '25px',
            backgroundColor: '#2d2d2d',
            borderRadius: '6px'
        });

        preElement.parentNode.insertBefore(wrapper, preElement);
        wrapper.appendChild(preElement);
        return wrapper;
    }

    function attachCopyButton(wrapper) {
        const btn = createCopyButton();
        btn.onclick = handleCopy.bind(null, btn); // Bind the button to the handleCopy function
        wrapper.appendChild(btn);
    }

    function createCopyButton() {
        const btn = document.createElement('button');
        btn.className = CONFIG.copyBtnClass + ' fresh';
        Object.assign(btn.style, styleStringToObject(CONFIG.styles.base));

        //交互效果:
        btn.onmouseenter = () => { Object.assign(btn.style, styleStringToObject(CONFIG.styles.hover)); };
        btn.onmouseleave = () => { Object.assign(btn.style, styleStringToObject(CONFIG.styles.base)); };

        btn.updateText = (text) => {
            btn.textContent = text || '📋 Copy';
            return btn;
        };

        btn.updateText(); // Initialize button text
        return btn;
    }

    async function handleCopy(btn) {
        const codeText = btn.parentNode.querySelector('code').innerText.trim();
        try {
            await navigator.clipboard.writeText(codeText);
            btn.updateText("✅ Copied!");
            setTimeout(() => btn.updateText(), 1500);
        } catch (err) {
            alert('[⚠️ Error] Please copy manually: ' + codeText);
            btn.updateText("❌ Failed");
            setTimeout(() => btn.updateText(), 2000);
        }
    }

    function styleStringToObject(cssStr) {
        return cssStr.split(/;(?!base64)/)
            .filter(s => s.trim())
            .map(pair => pair.split(':').map(p => p.trim()))
            .reduce((obj, [key, val]) => (obj[key] = val, obj), {});
    }

    function injectPrismTheme() {
        try {
            const themeCss = `${GM_getResourceText("prismCSS")} \n/* Custom Overrides */\n pre { background: #2d2d2d !important; }`;
            GM_addStyle(`${themeCss}\n\n${generateCustomStyles()}`);
        } catch (e) {
            console.error('[CSS Error]' + e);
        }
    }

    function generateCustomStyles() {
        return `
            .gpt-copy-btn-pro:hover { opacity: .95 !important; }
            .gpt-copy-btn-pro.fresh::after {
                content: "";
                position: absolute;
                right: -5%;
                top: -30%;
                width: .8em;
                height: .8em;
                border-radius: 50%;
                animation: pulseAnim 1.5s infinite;
            }
            @keyframes pulseAnim {
                50% { box-shadow: 0 0 0.5em #aaa; }
            }
        `;
    }

    let domObserver;

    function setupObserver() {
        domObserver?.disconnect();
        domObserver = new MutationObserver(mutationsHandler);
        domObserver.observe(document.body, CONFIG.observerConfig);
    }

    function mutationsHandler(muts) {
        if (!muts.some(mutationFilter)) return;
        requestIdleCallback(() => initCore(), { timeout: 300 });
    }

    function mutationFilter(mutation) {
        return Array.from(mutation.addedNodes).some(node => {
            // Case 1: The added node itself is a code block element
            if (node.nodeType === Node.ELEMENT_NODE && node.matches(CONFIG.codeSelector)) {
                return true;
            }

            // Case 2: The added container contains code blocks in its descendants
            if (typeof node.querySelector === 'function' && !!node.querySelector(CONFIG.codeSelector)) {
                return true;
            }

            // Edge case: Handle text nodes wrapped in spans/divs etc.
            if (/^(#text|SPAN|DIV)$/.test(node.nodeName) && checkAncestorCodeBlock(node.parentElement)) {
                return true;
            }

            return false;
        });
    }

    // Helper function to check parent chain for existing code blocks
    function checkAncestorCodeBlock(element, depth = 3) {
        while (depth-- > 0 && element) {
            if (element.matches?.(CONFIG.codeSelector)) {
                console.log('Found ancestor:', element);
                return true;
            }
            element = element.parentElement;
        }
        return false;
    }

    // Initialize the script
    initCore();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址