// ==UserScript==
// @name quizii上传试卷工具——补完试卷
// @namespace http://tampermonkey.net/
// @version 2.2.2
// @description 预传试卷框架后,逐题更新
// @author jin junwei
// @match http://121.42.229.71:8200/overlook?q=*
// @match http://121.42.229.71:8100/typesetting/item/*
// @match http://121.42.229.71:8100/input/upload
// @match http://121.42.229.71:8100/input/review/*
// @match http://192.168.0.145:8200/overlook?q=*
// @match http://192.168.0.145:8100/typesetting/item/*
// @match http://192.168.0.145:8100/input/upload
// @match http://192.168.0.145:8100/input/review/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// ==/UserScript==
'use strict';
// upload页面,保存表单
(function() {
if( !/\/input\/upload/.test(location.href) ){ return; }
// 按钮的容器
let referenceElement=document.querySelector("body > div.container > div.content > div.clearfix > form")
// 悬浮按钮,保存表单
let newElement = document.createElement('div');
newElement.innerHTML = '<div class="btn btn-success" style="float:right;margin:0 -2em 0 0" title="油猴脚本,保存表单到浏览器">保存</div>';
newElement = newElement.firstChild
referenceElement.appendChild(newElement); // 插入到页面
// 功能脚本
newElement.onclick = async function() {
await GM_setValue("uploadFormObj",{
priority:document.querySelector("#priority").value,
source_type:document.querySelector("#source_type").value,
source_id:document.querySelector("#source_id").value
});
console.log("保存upload表单到浏览器")
}
// 悬浮按钮,载入表单
newElement = document.createElement('div');
newElement.innerHTML = '<div class="btn btn-success" style="float:right;margin:0 -8em 0 0" title="油猴脚本,从浏览器载入表单">载入</div>';
newElement = newElement.firstChild
referenceElement.appendChild(newElement); // 插入到页面
// 功能脚本
newElement.onclick = async function() {
let uploadFormObj = GM_getValue("uploadFormObj");
if( !uploadFormObj ){ alert("请先保存upload表单到浏览器");return;}
for( let elId in uploadFormObj ){
document.getElementById(elId).value = uploadFormObj[elId];
}
console.log("从浏览器载入upload表单");
}
})();
// review页面,保存表单
(function() {
if( !/\/input\/review\//.test(location.href) ){ return; }
// 按钮的容器
let referenceElement=document.querySelector("form > div > div:nth-child(15) > div");
// 悬浮按钮,保存表单
let newElement = document.createElement('div');
newElement.innerHTML = '<div class="btn btn-success" style="margin:0 0 0 2em" title="油猴脚本,保存表单到浏览器">保存</div>';
newElement = newElement.firstChild
referenceElement.appendChild(newElement); // 插入到页面
// 功能脚本
newElement.onclick = async function() {
let reviewFormList = [];
// 试卷类型
reviewFormList.push(["doc_type",document.querySelector("#doc_type").querySelector('input[name="type"]:checked').value]);
// 下拉框
let selectIds =["edu", "grade", "semester", "year", "month", "province", "city", "county", "school", "edition", "book"];
selectIds.forEach( id=>reviewFormList.push([id, document.getElementById(id).value]) );
// "单元信息"
reviewFormList.push(["unit_info", document.getElementById("unit_info").value]);
await GM_setValue("reviewFormList",reviewFormList);
console.log("保存review表单到浏览器")
}
// 悬浮按钮,载入表单
newElement = document.createElement('div');
newElement.innerHTML = '<div class="btn btn-success" style="margin:0 0 0 1em" title="油猴脚本,从浏览器载入表单">载入</div>';
newElement = newElement.firstChild
referenceElement.appendChild(newElement); // 插入到页面
// 功能脚本
newElement.onclick = async function() {
let reviewFormList = GM_getValue("reviewFormList");
if( !reviewFormList ){ alert("请先保存review表单到浏览器");return;}
for( let [id, value] of reviewFormList ){
// 试卷类型
if( id === "doc_type" ){
document.getElementById(id).querySelector('input[value="'+value+'"]').click();
continue
}
// 等待
await new Promise(r => setTimeout(r, 300));
let el = document.getElementById(id);
document.getElementById(id).value=value;
console.log(id+", "+value);
// 下拉框
if( el.tagName==="SELECT" ){
$('#'+id).change();
}
}
console.log("从浏览器载入review表单");
}
function getElementPath(el){
const names = [];
while (el.parentNode){
if( el.tagName==="BODY" ){
names.unshift(el.tagName);
break;
}else if (el.id){
names.unshift('#'+el.id);
break;
}else{
if (el==el.ownerDocument.documentElement) names.unshift(el.tagName);
else{
for (var c=1,e=el;e.previousElementSibling;e=e.previousElementSibling,c++);
names.unshift(el.tagName+":nth-child("+c+")");
}
el=el.parentNode;
}
}
return names.join(" > ");
}
})();
// http://121.42.229.71:8200/overlook?q=
(function() {
if( !/\/overlook\?q=/.test(location.href) ){ return; }
// 检查 id重题
let qEls = [...document.querySelectorAll("q")];
let qIds = qEls.map( qEl=>qEl.id );
let j=-1
for( let i in qIds ){
j = qIds.indexOf(qIds[i], i+1);
if( j>-1 ){
alert("发现id相同的习题,进入聚类系统修改")
qEls[j].parentElement.nextElementSibling.firstElementChild.click()
return;
}
}
if( !document.querySelector("#book-content").textContent.includes("【预传习题】") ){ return; }
// 悬浮按钮的容器
let referenceElement=function (referenceElement){
let newElement = document.createElement('div');
newElement.innerHTML = '<span></span>';
newElement = newElement.firstChild
// 插入元素
referenceElement.parentElement.insertBefore(newElement, referenceElement.nextElementSibling);
return newElement;
}(document.querySelector('#item-status-stats'));
// 预传习题数
const total=qEls.length;
const count = qEls.filter(qEl=>qEl.textContent.includes("【预传习题】")).length;
// 悬浮按钮,导入补完试卷
let newElement = document.createElement('div');
newElement.innerHTML = '<div class="btn btn-success btn-sm" title="油猴脚本,导入预测试卷对应的补完文件,*.quizii补完.json.txt">补完试卷 '+count+"/"+total+'</div>';
newElement = newElement.firstChild
referenceElement.appendChild(newElement); // 插入到页面
// 功能脚本
newElement.onclick = onclick;
async function onclick() {
async function waitTabFocus(){
await new Promise(r => setTimeout(r, 2000));
while(!document.hasFocus()) {
await new Promise(r => setTimeout(r, 200));
}
}
// 习题列表
const exElementList = [...document.querySelectorAll("q")]
// 逐题修改习题
let isFirst=true
for(let i=0; i<exElementList.length; i++){
let exEl = exElementList[i]
if(exEl.innerText.includes("【预传习题】")){
let search = "?total="+exElementList.length+"&idx="+i+"&pId="+location.search.split("q=")[1].split("&")[0];
if (isFirst){
search = search + "&load=true";
isFirst=false;
}
let href = exEl.parentElement.nextElementSibling.children[1].href+search;
let iframeElement = openInNewTab(href);
await waitTabFocus();
}
}
}
function openInNewTab(url) {
var win = window.open(url, '_blank');
win.focus();
}
})();
// http://121.42.229.71:8100/typesetting/item/*
// 导入"*.quizii补完.json.txt"
(function() {
if( !/\/typesetting\/item\//.test(location.href) ){return;}
if( !location.search && Date.now()<GM_getValue("closeBefore", 0) ){ GM_setValue("closeBefore", 0); close(); return; }
if( !location.search.startsWith("?total=") ){return;}
if( document.getElementById("show-dups") ){
insertButtonPreviewFirstSimilarity();
}
insertButtonSubmitAndClose()
const idx = parseInt(location.search.split("idx=")[1].split("&")[0]);
const total = parseInt(location.search.split("total=")[1].split("&")[0]);
const pId = location.search.split("pId=")[1].split("&")[0];
if(location.search.indexOf("load=true")>-1){
const element = openFileInput(idx,total,pId);
element.click();
//document.body.removeChild(element);
return
}else{
const exData = checkAndGetExData(idx, total, pId);
updateEx(exData);
}
function checkAndGetExData(idx, total, pId){
const paperData = JSON.parse(localStorage.getItem("paperData"));
// check, todo
if(paperData.pId!==pId){
alert("试卷id不一致,请重新载入*.quizii补完.json.txt文件!");
return;
}
if(paperData.exDataList.length!==total){
alert("习题总数不一致,请检查习题数!");
return;
}
console.log("读取了试卷习题数据,pId="+paperData.pId+"; 保存time="+Date(paperData.time))
const exData = paperData['exDataList'][idx];
return exData;
}
function getImageDict(question_html){
}
async function updateEx(exData) {
while(!editor || !editor.edit.doc.body.innerText.includes("【预传习题】")) {
await new Promise(r => setTimeout(r, 300));
}
const previewEl = document.querySelector("#preview");
while(previewEl.disabled) {
await new Promise(r => setTimeout(r, 200));
}
// await waitElementCreated(editor.edit.doc, "q");
// 按顺序替换图片
const imgEls = [...document.querySelectorAll("#q-preview img")];
let exString = JSON.stringify(exData);
let count = 0;
exString=exString.replace(/【预传图片\-(\d+)\-?(\d+)?】\//g, (match, ind, width)=>{
count += 1;
let imgEl = imgEls[ind];
if( width ){
imgEl.style["width"]=width+"px";
imgEl.width=width;
}
let imgHtml = JSON.stringify(imgEl.outerHTML);
return imgHtml.substring(1, imgHtml.length-1);
});
if( imgEls.length!==count ){alert("图片个数不一致");return;}
exData=JSON.parse(exString);
// 解答题 题干
const stemEl = editor.edit.doc.querySelector("q > div > stem")
if(stemEl){stemEl.innerHTML = exData.stem;}
const subqEls = [...editor.edit.doc.querySelectorAll("q subq")];
if(subqEls.length!==exData.qs.length){alert("问题个数不一致!");return;}
subqEls.forEach((subqEl,i)=>{
const qData = exData.qs[i];
// 选择题、填空题、子问题 题干
const subStemEl =subqEl.querySelector("stem");
if(subStemEl){subStemEl.innerHTML = qData.desc;}
// 选择题,选项
const subOptEls = [...subqEl.querySelectorAll("opt")];
if(subOptEls.length!==qData.opts.length){alert("选项个数不一致!");return;}
subOptEls.forEach((el,j)=>{
// 清理多余的选项内容
[...el.childNodes].slice(2).forEach(el=>el.remove())
el.childNodes[1].innerHTML=qData.opts[j];
});
// 答案、解析
aeditors[i].edit.doc.body.innerHTML = qData.ans;
eeditors[i].edit.doc.body.innerHTML = qData.exp;
});
const btnEl = document.querySelector("#preview");
// const btnEl = document.querySelector("#submit");
while(btnEl.disabled) {
await new Promise(r => setTimeout(r, 200));
}
btnEl.click();
btnEl.disabled = true;
// 完成后关闭页面
console.log("等待保存完成,并且关闭页面")
while(btnEl.disabled) {
await new Promise(r => setTimeout(r, 200));
}
// close();
}
function insertButtonSubmitAndClose(){
const liEl = document.createElement('li');
const element = document.createElement('a');
element.innerText="保存并关闭"
liEl.appendChild(element)
let paperEl = document.querySelector("#typesetting_pager > li:nth-child(2)")
if (!paperEl){
paperEl = document.querySelector("#typesetting_pager > li:nth-child(1)")
}
paperEl.parentElement.insertBefore(liEl, paperEl.nextElementSibling);
element.onclick = async function (){
const btnEl = document.querySelector("#submit");
btnEl.click();
GM_setValue("closeBefore", Date.now()+3000); // 载入新页面时关闭
// 小标题的最后一题不会自动刷新页面
setTimeout(close, 200);
};
return element
}
function insertButtonPreviewFirstSimilarity(){
const liEl = document.createElement('li');
const element = document.createElement('a');
element.innerText="复制并预览首个相似题"
liEl.appendChild(element)
let paperEl = document.querySelector("#typesetting_pager > li:nth-child(2)")
if (!paperEl){
paperEl = document.querySelector("#typesetting_pager > li:nth-child(1)")
}
paperEl.parentElement.insertBefore(liEl, paperEl.nextElementSibling);
element.onclick = previewFirstSimilarity;
return element
}
async function waitElementCreated(parentEl, selector){
let el;
while(!(el=parentEl.querySelector(selector))) {
// console.log(document.querySelector(selector).innerHTML);
await new Promise(r => setTimeout(r, 100));
}
return el
}
function previewFirstSimilarity() {
let qEl = document.querySelector("#q-dups q");
if( !qEl ){
alert("请点击左下角的Similarities按钮")
return;
}
qEl = qEl.cloneNode(true)
// mathjax unrender
qEl.querySelectorAll(".MathJax").forEach(el=>el.remove());
qEl.querySelectorAll("script")
.forEach(el=>el.innerText = "\\( "+htmlEntities(el.innerText) +" \\)")
editor.html(qEl.outerHTML);
for (var i = 0; i < qnum; ++i) {
let subqEl = qEl.querySelectorAll("subq")[i]
if( subqEl.querySelector(".answer .dd") ){
aeditors[i].html(subqEl.querySelector(".answer .dd").innerHTML);
}
if( subqEl.querySelector(".exp .dd") ){
eeditors[i].html(subqEl.querySelector(".exp .dd").innerHTML);
}
}
qEl=undefined;
document.querySelector("#preview").click()
};
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
function openFileInput(idx, total, pId){
const element = document.createElement('input');
element.setAttribute('type', 'file' );
element.setAttribute('accept', '.json.txt');
//element.style.display = 'none';
//document.body.appendChild(element);
let paperEl = document.querySelector("#mathright > h4 > div.pull-right > span");
if( !paperEl ){
let parerName = prompt( "无法获取习题所在试卷的名称,请手动输入" );
// 插入试卷名称
paperEl=document.querySelector("#mathright > h4 > div.pull-right");
paperEl.innerHTML='<span class="label label-success">'+parerName+'</span>';
paperEl=paperEl.firstElementChild;
}
paperEl.parentElement.insertBefore(element, paperEl.nextElementSibling);
const filename = paperEl.innerText + ".quizii补完.json.txt"
GM_setClipboard(filename); // 复制文件名到粘贴板
element.onchange = function (event) {
if(filename !== event.target.files[0].name) {
alert("文件名不匹配,请导入:"+filename);
return;
}
const reader = new FileReader();
reader.readAsText(event.target.files[0]);
reader.onload = function(event){
// 习题列表
console.log("读取了文件:"+filename);
const exDataList = JSON.parse(event.target.result);
// 拆分解答题的子问题
exDataList.filter(o=> o.type===1003 )
.forEach(splitSubq)
console.log("拆分了解答题的子问题");
// 保存到localStorage
localStorage.setItem("paperData", JSON.stringify({pId:pId, time:Date.now(), exDataList:exDataList}));
console.log("exDataList保存到了localStorage的paperData");
// callback
const exData = checkAndGetExData(idx, total, pId);
updateEx(exData);
}
};
return element
}
function splitSubq(exObj){
// 拆分子问题(1)...(2)...
let preQObj = exObj.qs[0];
exObj.qs.length = 0;
let i = 1;
while(true){
let separator = "("+(i++)+")";
if(preQObj.desc.indexOf(separator)<0){
if(exObj.qs.length === 0){exObj.qs.push(preQObj);}
return exObj;
}
let qObj = {desc:"", ans:"", exp:"",opts:[],context:""};
[preQObj.desc, qObj.desc] = splitOnce(preQObj.desc, separator);
[preQObj.ans, qObj.ans] = splitOnce(preQObj.ans, separator);
[preQObj.exp, qObj.exp] = splitOnce(preQObj.exp, separator);
// 防止丢失不规范的内容
if( exObj.qs.length === 0 ){
if( qObj.exp.length ===0 ) {
qObj.exp = preQObj.exp
}
if( qObj.ans.length ===0 ) {
qObj.ans = preQObj.ans
}
}
exObj.qs.push(qObj);
preQObj = qObj
}
}
function splitOnce(str, separator){
let splitAt = str.indexOf(separator);
if (splitAt===-1){return [str, ""];}
return [str.substring(0,splitAt), str.substring(splitAt+separator.length)];
}
})();