由于不希望学生可以复制粘贴代码,这样会失去实测的目的,但偶尔自己也需要复制粘贴测试学生提供的代码,于是用篡改猴组件给前端页面注入JS脚本来实现这个功能。由于代码编辑器是高级的monaco[1],所以需要使用monaco的API来设置代码,以保持数据的一致性,用法也是常规的editor->model->get/setValue。唯一需要知道的是如何引用已经存在的editor,凭借经验,大多数会放在window对象下,于是盲猜是window.monaco或者window.editor。最终确定是window.monaco.editor。注意:在篡改猴的JS脚本中,需要使用unsafeWindow来访问正常的window对象。
// ==UserScript==
// @name 清览题库|清览测验复制粘贴
// @namespace https://app.qingline.net/student/examing*
// @version 0.2
// @description 自动屏蔽弹窗;允许复制题目;允许复制代码;允许粘贴代码
// @author tpu01yzx
// @connect *
// @match https://app.qingline.net/student/examing*
// @match https://app.qingline.net/student/training?exam_id=*
// @run-at document-end
// @license MIT
// @grant unsafeWindow
// ==/UserScript==
(function () {
console.log('清览题库|清览测验复制粘贴 => start');
'use strict';
window.addEventListener('load', function () {
setTimeout(() => {
init();
}, 1000);
});
function getQuesText() {
const $ = (s) => [...document.querySelectorAll(s)];
const head = $(".stem")[0]
.textContent
.trim()
.replace(/^([0-9]+)/, "")
.replace(/[。?]/, "")
.replace(/(\s+?)/g, " ");
const body = $(".info_content")[0].textContent.trim();
const test = $(".test_case_content")[0].textContent.trim();
return head + "\n" + body + "\n" + test;
}
function getCodeText() {
return "//Copied from app.qingline.net \n" + document.getElementsByClassName("inputarea")[0].value;
}
function AddButton(p, t, func, tips, cn) {
// 创建一个新的按钮元素
const newButton = document.createElement('button');
newButton.type = 'button';
//newButton.style.marginRight = "10px";
newButton.className = 'submit_btn ant-btn '+ cn;
// 创建一个新的span元素并将其添加到按钮中
const newSpan = document.createElement('span');
newSpan.textContent = t;
newButton.appendChild(newSpan);
// 添加新按钮作为fixed_con元素的第一个子元素
p.insertBefore(newButton, p.firstChild);
//fixedCon.appendChild(newButton);
newButton.addEventListener('click', function () {
//const textToCopy = getQuesText();
//console.log(textToCopy);
//navigator.clipboard.writeText(textToCopy)
var msg;
try{
func();
msg = tips || "成功";
}catch(e){
msg = tips + "失败";
}
//console.log('Text copied to clipboard');
// 创建一个新的元素来显示复制成功的消息
const copySuccessMessage = document.createElement('div');
copySuccessMessage.textContent = msg;
copySuccessMessage.style.position = 'fixed';
copySuccessMessage.style.bottom = '10px';
copySuccessMessage.style.right = '10px';
copySuccessMessage.style.padding = '10px';
copySuccessMessage.style.backgroundColor = '#3d64ff';
copySuccessMessage.style.color = 'white';
copySuccessMessage.style.borderRadius = '5px';
document.body.appendChild(copySuccessMessage);
// 一段时间后隐藏消息
setTimeout(function () {
copySuccessMessage.style.display = 'none';
}, 1000);
});
}
function init() {
const cn = 'MyFunctionButton';
//console.log('清览题库|清览测验禁用复制和全屏 => init');
// 选择fixed_con元素
var fixedCon = document.getElementsByClassName("right_bottom_btns");
if(fixedCon && fixedCon.length > 0) {
fixedCon = fixedCon[0];
//console.log("found fixCon.");
var myButton = document.getElementsByClassName(cn);
if(myButton == null || myButton.length <= 0){
console.log("found no myButton.");
AddButton(fixedCon, '复制题目', function() {
const textToCopy = getQuesText();
//console.log(textToCopy);
navigator.clipboard.writeText(textToCopy);
}, '复制成功~', cn);
AddButton(fixedCon, '投影模式', function() {
const applyProjectionStyleToElements = (es) => {
// 定义递归函数
const applyStyle = (el) => {
if (el.style) {
el.style.backgroundColor = "#002b36"; // 深蓝色背景
el.style.color = "#FFFFFF"; // 白色字体
el.style.fontSize = "24px"; // 字体大小
el.style.fontWeight = "bold"; // 加粗
}
// 遍历子元素并递归调用
Array.from(el.children).forEach(child => applyStyle(child));
};
// 对传入的主元素应用样式
if(es && es.length > 0) {
var i;
for(i = 0; i < es.length; i++) {
var element = es[i];
//console.log(element);
applyStyle(element);
}
}
};
const elements = [
document.querySelector('.ques_all'),
//document.querySelector('.info_content'),
];
//console.log(elements);
applyProjectionStyleToElements(elements);
const _window = unsafeWindow ? unsafeWindow : window;
const editor = _window.monaco.editor;
// 定义投影模式主题
editor.defineTheme('projectionTheme', {
base: 'vs-dark', // 基于暗色主题
inherit: true, // 继承默认规则
rules: [
{ token: '', foreground: 'FFFFFF', background: '002b36' }, // 默认颜色
{ token: 'keyword', foreground: 'FFD700' }, // 关键字 - 金黄色
{ token: 'number', foreground: '87CEFA' }, // 数字 - 浅蓝色
{ token: 'string', foreground: '90EE90' }, // 字符串 - 浅绿色
{ token: 'comment', foreground: 'B0C4DE' }, // 注释 - 浅灰蓝
],
colors: {
'editor.background': '#002b36', // 编辑器背景色
'editor.foreground': '#FFFFFF', // 编辑器前景色
}
});
// 应用主题
editor.setTheme('projectionTheme');
//const lines = document.querySelector(".monaco-editor");
//const fontSize = 32; // 字体大小
//const lineHeight = Math.ceil(fontSize * 1.5); // 动态计算行高
//lines.style.fontSize = `${fontSize}px`;
//lines.style.lineHeight = `${lineHeight}px`;
//const editorDomElement = editor.getDomNode();
//editorDomElement.style.fontSize = fontSize + 'px';
//editorDomElement.style.lineHeight = lineHeight;
// 通知编辑器布局需要更新
editor.layout();
}, '启用投影模式', cn);
AddButton(fixedCon, '切换题目', function() {
// 获取左右元素
const leftElement = document.querySelector('.training_wrap_left');
const rightElement = document.querySelector('.training_wrap_right');
// 静态变量,用于记录初始状态
if (!leftElement.dataset.originalWidth) {
leftElement.dataset.originalWidth = getComputedStyle(leftElement).width;
rightElement.dataset.originalWidth = getComputedStyle(rightElement).width;
}
// 切换状态
if (leftElement.style.width === '0px' || leftElement.style.display === 'none') {
// 恢复到原始状态
leftElement.style.width = leftElement.dataset.originalWidth;
leftElement.style.display = 'block';
rightElement.style.width = rightElement.dataset.originalWidth;
} else {
// 隐藏左侧,右侧扩展到全宽
leftElement.style.width = '0';
leftElement.style.display = 'none';
rightElement.style.width = '100%';
}
}, '切换成功~', cn);
AddButton(fixedCon, '复制代码', function() {
//const textToCopy = getCodeText();
const _window = unsafeWindow ? unsafeWindow : window;
const models = _window.monaco.editor.getModels();
const spmodel = models[models.length - 1];
const textToCopy = spmodel.getValue();
console.log(textToCopy);
navigator.clipboard.writeText(textToCopy);
}, '复制成功~', cn);
AddButton(fixedCon, '粘贴代码', async function() {
const _window = unsafeWindow ? unsafeWindow : window;
const models = _window.monaco.editor.getModels();
const spmodel = models[models.length - 1];
const textToPaste = await navigator.clipboard.readText();
console.log("textToPaste:" + textToPaste);
spmodel.setValue(textToPaste);
//document.getElementsByClassName("inputarea")[0].value = textToPaste;
}, '粘贴成功~', cn);
} else {
//console.log("found my button.");
}
} else {
console.log("no fixCon found.");
}
// 检查是否存在弹窗
const modal = document.querySelector('.ant-modal-body');
if (modal) {
const modalRoot = document.querySelector('.ant-modal-root');
if(modalRoot) {
const modalTitle = modalRoot.querySelector(".ant-modal-confirm-title");
if(modalTitle) {
console.log('modalTitle.textContent:' + modalTitle.textContent);
if(modalTitle.textContent == '为了给您最好的使用体验,请您使用谷歌浏览器。') {
modalRoot.remove();
}
}
}
}
setTimeout(function() {
init();
}, 3000);
}
})();
参考文献:
[1] https://microsoft.github.io/monaco-editor/
[2] https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.ITextModel.html