// ==UserScript== // @name 超星作业代码助手 - 悬浮粘贴工具 // @namespace http://tampermonkey.net/ // @version 1.402 // @description 解除超星作业粘贴限制,在页面添加悬浮输入框方便粘贴代码,目前仅通过湖南农业大学的高级算法设计课的作业复制粘贴的测试 // @author lanshi // @supportURL https://github.com/lanshi17/Study_Pass_Remove_copy-paste_restriction_for_code_questions/issues // @license MIT // @match *://mooc1-api.chaoxing.com/ // @match *://mooc1.chaoxing.com/mooc-ans/mooc2/* // @match *://*.chaoxing.com/mooc-ans/mooc2/* // @grant none // ==/UserScript== (function () { 'use strict'; const HELPER_ID = 'floating-code-helper'; const QUESTION_SELECTOR = '.questionLi[typename="程序题"]'; const EDITOR_SELECTOR = `${QUESTION_SELECTOR}, .codeEditorBoxDiv, .CodeMirror`; const editorBindings = new Map(); let currentActiveEditor = null; let container; let inputArea; let statusDiv; let statusTimer; let scanTimer; // 创建悬浮窗元素 function createFloatingWindow() { container = document.createElement("div"); container.id = HELPER_ID; container.style.cssText = ` position: fixed; bottom: 20px; right: 20px; width: 300px; max-width: calc(100vw - 40px); box-sizing: border-box; background-color: #fff; border: 1px solid #ccc; box-shadow: 0 0 10px rgba(0,0,0,0.2); z-index: 99999; font-family: Arial, sans-serif; font-size: 14px; padding: 10px; border-radius: 6px; `; container.innerHTML = `
📝 代码粘贴助手
`; document.body.appendChild(container); inputArea = container.querySelector("#code-input"); statusDiv = container.querySelector("#helper-status"); container.querySelector("#paste-btn").addEventListener("click", pasteCodeToEditor); // 保留输入框原生粘贴,避免触发页面冒泡阶段的粘贴限制。 inputArea.addEventListener("paste", event => event.stopPropagation()); } // 粘贴函数 function pasteCodeToEditor() { const code = inputArea.value; if (!code) { showStatus("⚠️ 输入不能为空!", true); return; } // 点击时重新检查,避免动态翻页后向已移除的编辑器写入。 initEditors(); if (!currentActiveEditor) { showStatus("❌ 当前没有活动的编辑器,请先点击某个代码框", true); return; } insertCode(currentActiveEditor, code); } // 显示状态信息 function showStatus(msg, isError = false) { clearTimeout(statusTimer); statusDiv.textContent = msg; statusDiv.style.color = isError ? "red" : "green"; statusDiv.style.display = "block"; statusTimer = setTimeout(() => { statusDiv.style.display = "none"; }, 3000); } // 每个实例仅绑定一次;扫描时清理已移除或被替换的实例。 function initEditors() { clearTimeout(scanTimer); const foundEditors = new Set(); let pendingEditors = false; document.querySelectorAll(QUESTION_SELECTOR).forEach(question => { const questionId = question.id.replace(/^question/, ""); const editorBox = Array.from(question.querySelectorAll(".codeEditorBoxDiv")) .find(box => box.dataset.businessId === questionId); if (!editorBox) return; const editor = window.codeEditors?.[questionId]; const wrapper = editor?.getWrapperElement?.(); if (!wrapper?.isConnected || !editorBox.contains(wrapper)) { pendingEditors = true; return; } foundEditors.add(editor); if (editorBindings.has(editor)) return; const onFocus = () => { currentActiveEditor = editor; }; const onPaste = event => { if (event.defaultPrevented || editor.getOption("readOnly")) return; const clipboard = event.clipboardData; if (!clipboard || !Array.from(clipboard.types).includes("text/plain")) return; const text = clipboard.getData("text/plain"); if (!text) return; // 在 CodeMirror 处理 paste 前插入;不在 beforeChange 内修改文档。 event.preventDefault(); event.stopImmediatePropagation(); currentActiveEditor = editor; insertCode(editor, text); }; editor.on("focus", onFocus); wrapper.addEventListener("paste", onPaste, true); editorBindings.set(editor, { wrapper, onFocus, onPaste }); }); for (const [editor, binding] of editorBindings) { if (foundEditors.has(editor)) continue; editor.off("focus", binding.onFocus); binding.wrapper.removeEventListener("paste", binding.onPaste, true); editorBindings.delete(editor); } if (!foundEditors.has(currentActiveEditor)) { currentActiveEditor = foundEditors.values().next().value || null; } // 注册到 window.codeEditors 不一定产生 DOM 变化,因此仅对待加载项重试。 if (pendingEditors) scheduleScan(1000); } function insertCode(editor, text) { if (editor.getOption("readOnly")) { showStatus("⚠️ 当前编辑器为只读,无法插入代码", true); return; } try { // 保留缩进和首尾换行,选中内容时按普通粘贴行为替换,并支持撤销。 editor.getDoc().replaceSelection(text, "end", "code-helper"); editor.focus(); showStatus("✅ 已成功插入代码"); } catch (error) { console.error("粘贴失败:", error); showStatus("❌ 插入代码失败:" + error.message, true); } } function scheduleScan(delay = 100) { clearTimeout(scanTimer); scanTimer = setTimeout(initEditors, delay); } function containsEditor(node) { return node.nodeType === Node.ELEMENT_NODE && (node.matches(EDITOR_SELECTOR) || node.querySelector(EDITOR_SELECTOR)); } // 页面加载及DOM变动监听 function setup() { if (document.getElementById(HELPER_ID)) return; createFloatingWindow(); initEditors(); // 监控后续可能新增的编辑器(比如翻页等情况) const observer = new MutationObserver(mutations => { // 忽略面板提示和代码逐字渲染,仅合并处理编辑器结构的增删。 const editorsChanged = mutations.some(mutation => !container.contains(mutation.target) && [...mutation.addedNodes, ...mutation.removedNodes].some(containsEditor)); if (editorsChanged) scheduleScan(); }); observer.observe(document.body, { childList: true, subtree: true }); } // 等待页面加载完毕后执行 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", setup, { once: true }); } else { setup(); } })();