湖南大学CG平台代码块复制

在湖南大学CG平台中为每个代码块添加复制按钮

// ==UserScript==
// @name         湖南大学CG平台代码块复制
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  在湖南大学CG平台中为每个代码块添加复制按钮
// @author       淼畔
// @match        https://cg.hnu.edu.cn/*
// @grant        none
// @license      MIT
// @icon         https://cg.hnu.edu.cn/images/cgicon.png
// ==/UserScript==

(function () {
	'use strict';

	// 存储已处理的代码块
	const processedPre = new WeakSet();

	// 初始处理可见的代码块
	document.querySelectorAll('pre').forEach(pre => {
		if (isElementVisible(pre)) {
			addButtonForPre(pre);
		}
	});

	// 设置监控 DOM 变化的观察器
	const observer = new MutationObserver(mutations => {
		mutations.forEach(mutation => {
			if (mutation.type === 'childList') {
				// 处理新增节点
				mutation.addedNodes.forEach(node => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						if (node.tagName === 'PRE' && isElementVisible(node)) {
							addButtonForPre(node);
						}
						node.querySelectorAll('pre').forEach(pre => {
							if (isElementVisible(pre)) {
								addButtonForPre(pre);
							}
						});
					}
				});
			}
			else if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
				document.querySelectorAll('pre').forEach(pre => {
					if (isElementVisible(pre)) {
						addButtonForPre(pre);
					}
				});
			}
		});
	});

	observer.observe(document.body, {
		childList: true,
		subtree: true,
		attributes: true,
		attributeFilter: ['style']
	});

	// 为单个代码块添加按钮
	function addButtonForPre(pre) {
		if (processedPre.has(pre)) return;
		processedPre.add(pre);

		// 查找合适的容器元素
		let container = findSuitableContainer(pre);

		// 检查字符长度
		const text = container.textContent.trim();
		const shouldNewLine = text.length > 15;

		let btnContainer = container;
		if (shouldNewLine) {
			const newContainer = document.createElement('div');
			newContainer.style.marginTop = '4px';
			pre.parentNode.insertBefore(newContainer, pre);
			btnContainer = newContainer;
		}

		// 添加复制按钮
		createCopyButton(btnContainer, pre);
	}

    // 获取pre元素的文本内容,处理<br>换行,并替换特殊空格和非期望字符
    function getPreTextContent(pre) {
        // 克隆节点以避免修改原始DOM
        const clone = pre.cloneNode(true);

        // 替换所有<br>为换行符
        const brs = clone.querySelectorAll('br');
        brs.forEach(br => {
            br.parentNode.replaceChild(document.createTextNode('\n'), br);
        });

        // 获取文本内容并处理非期望字符
        let text = clone.textContent.trim();

        // 替换所有类型的空格字符(除换行符外)和控制字符
        text = text.replace(/[^\S\n]|[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, ' ');

        // 合并连续的空格为单个空格(不包括换行符)
        text = text.replace(/(?!\n) +(?!\n)/g, ' ');

        return text;
    }

	// 智能查找合适的容器元素
	function findSuitableContainer(pre) {
		// 默认使用前一个元素
		let candidate = pre.previousElementSibling;

		// 向前搜索最多5个元素
		for (let i = 0; i < 5 && candidate; i++) {
			const isValid = (
				candidate &&
				candidate.tagName !== 'BR' && // 跳过 <br> 元素
				!isEffectivelyEmpty(candidate) // 跳过空元素
			);

			if (isValid) {
				return candidate;
			}
			candidate = candidate.previousElementSibling;
		}

		// 如果没有找到合适元素,创建新容器
		const container = document.createElement('div');
		container.style.margin = '8px 0';
		pre.parentNode.insertBefore(container, pre);
		return container;
	}

	// 检查元素是否实质为空
	function isEffectivelyEmpty(element) {
		if (!element) return true;

		// 检查可见子元素
		const hasVisibleChildren = Array.from(element.children).some(child => {
			return isElementVisible(child) && !isEffectivelyEmpty(child);
		});

		// 检查文本内容
		const text = element.textContent.trim();

		return !hasVisibleChildren && text === '';
	}

	// 创建复制按钮
	function createCopyButton(container, preElement) {
		// 避免重复添加
		if (container.querySelector('.hnu-copy-btn')) return;

		const btn = document.createElement('button');
		btn.className = 'hnu-copy-btn';
		btn.textContent = '复制';
		btn.style.cssText = `
            font-size: 12px;
            padding: 2px 8px;
            background: #f5f5f5;
            border: 1px solid #ddd;
            border-radius: 3px;
            cursor: pointer;
            color: #333;
            transition: all 0.3s;
            position: relative;
            z-index: 1000;
        `;

		container.appendChild(btn);

		btn.addEventListener('click', function () {
			const originalText = btn.textContent;
			copyToClipboard(getPreTextContent(preElement));

			btn.textContent = "✓ 复制成功";
			btn.style.background = "#edfae9";
			btn.style.borderColor = "#4CAF50";
			btn.style.color = "#4CAF50";

			setTimeout(() => {
				btn.textContent = originalText;
				btn.style.background = "#f5f5f5";
				btn.style.borderColor = "#ddd";
				btn.style.color = "#333";
			}, 400);
		});
	}

	// 判断元素是否可见
	function isElementVisible(el) {
		if (!el) return false;
		if (el.style && el.style.display === 'none') return false;

		const computedStyle = window.getComputedStyle(el);
		if (computedStyle.display === 'none') return false;
		if (computedStyle.visibility === 'hidden') return false;

		let parent = el.parentElement;
		while (parent && parent !== document.body) {
			const parentStyle = window.getComputedStyle(parent);
			if (parentStyle.display === 'none' || parentStyle.visibility === 'hidden') {
				return false;
			}
			parent = parent.parentElement;
		}

		return true;
	}

	// 复制到剪贴板
	function copyToClipboard(text) {
		const textArea = document.createElement('textarea');
		textArea.value = text;
		textArea.style.position = 'fixed';
		textArea.style.opacity = '0'; // 透明但可操作
		document.body.appendChild(textArea);
		textArea.select();
		document.execCommand('copy');
		document.body.removeChild(textArea);
	}
})();

QingJ © 2025

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