月度归档:2024年09月

给清览平台增加允许复制粘贴功能

由于不希望学生可以复制粘贴代码,这样会失去实测的目的,但偶尔自己也需要复制粘贴测试学生提供的代码,于是用篡改猴组件给前端页面注入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();
            return head + "\n" + body;
        }

        function getCodeText() {
            return "//Copied from app.qingline.net \n" + document.getElementsByClassName("inputarea")[0].value;
        }

        function AddButton(p, t, func, tips, tips2) {
            // 创建一个新的按钮元素
            const newButton = document.createElement('button');
            newButton.type = 'button';
            //newButton.style.marginRight = "10px";
            newButton.className = 'submit_btn ant-btn';

            // 创建一个新的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() {
            console.log('清览题库|清览测验禁用复制和全屏 => init');
            // 选择fixed_con元素
            var fixedCon = document.getElementsByClassName("right_bottom_btns");
            if(fixedCon == null || fixedCon.length <= 0) {
                setTimeout(function() {
                    init();
                }, 1000);
                return;
            }

            fixedCon = fixedCon[0];
            AddButton(fixedCon, '复制题目', function() {
                const textToCopy = getQuesText();
                //console.log(textToCopy);
                navigator.clipboard.writeText(textToCopy);
            }, '复制成功~');

            AddButton(fixedCon, '复制代码', function() {
                //const textToCopy = getCodeText();
                const _window = unsafeWindow ? unsafeWindow : window
                const textToCopy = _window.monaco.editor.getModels()[0].getValue();
                //console.log(textToCopy);
                navigator.clipboard.writeText(textToCopy);
            }, '复制成功~');

            AddButton(fixedCon, '粘贴代码', async function() {
                const textToPaste = await navigator.clipboard.readText();
                const _window = unsafeWindow ? unsafeWindow : window
                _window.monaco.editor.getModels()[0].setValue(textToPaste);
                //document.getElementsByClassName("inputarea")[0].value = textToPaste;
            }, '粘贴成功~');

            // 检查是否存在弹窗
            const modal = document.querySelector('.ant-modal-body');
            console.log(modal);
            if (modal) {
                const modalRoot = document.querySelector('.ant-modal-root');
                if(modalRoot) modalRoot.remove();
            }
        }
    })();

参考文献:
[1] https://microsoft.github.io/monaco-editor/
[2] https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.ITextModel.html