hack のためのネタ帳, etc,,,

以下のコードは、主に Moodle 2.9 を対象としています。

JavaScript

小テストの先頭にラベル等を配置し、HTML 編集モードにして以下のようなコードを埋め込んでおくと解答の文字数を表示できる。
<script>
(function(){
  function countChars() {
    if (location.href.match('/mod/quiz/review.php')) {
      [].forEach.call(document.querySelectorAll(".answer"),function(e){
        let d = document.createElement("div");
        d.textContent = "文字数:"+ e.textContent.length;
        e.parentElement.insertBefore(d, e.nextSibling);
      });
    }
  }
  function onDOMContentLoaded(f) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', f);
    } else {
      f();
    }
  }
  onDOMContentLoaded(countChars);
})();
</script>

for ScratchPad

「課題モジュール」の「すべての提出を表示/評定する」の画面を CSV 化する。
javascript:(function(){
  function copyToClipboard(s) {
    let t = document.createElement("textarea");
    document.body.appendChild(t);
    t.value = s;
    t.focus();
    t.select();
    setTimeout(()=>t.parentNode.removeChild(t),0);
    return document.execCommand("copy");
  }
  function copyDialog(s) {
    let d = document.createElement("div");
    Object.assign(d.style, {position: "absolute", top: 0, left:0, width: "100%", height: "100%", border: "1px solid red", background: "white", zIndex: Number.MAX_SAFE_INTEGER});
    document.body.insertBefore(d, document.body.firstChild);

    d.appendChild(document.createTextNode("Copy to Clipboard"));

    let t = document.createElement("textarea");
    Object.assign(t.style, {width: "100%", height: "20em"});
    d.appendChild(t);
    t.value = s;
    t.focus();
    t.select();

    let copy = document.createElement("input");
    Object.assign(copy, {type: "button", value: "copy"});
    copy.addEventListener("click", ()=>{copyToClipboard(s);d.parentNode.removeChild(d);});
    d.appendChild(copy);

    let cancel = document.createElement("input");
    Object.assign(cancel, {type: "button", value: "cancel"});
    cancel.addEventListener("click", ()=>d.parentNode.removeChild(d));
    d.appendChild(cancel);
  }

  let rows = document.querySelector(".paging").parentNode.querySelector("table tbody").rows;
  let s = [].map.call(rows, function({cells}){
    let ret = [2,3,7,8].map(i=>cells[i].textContent);
    if (ret[2].match(/([0-9]+).*?([0-9]+).*?([0-9]+).*?([0-9]+):([0-9]+)/)) ret[2] = `${RegExp.$1}/${RegExp.$2}/${RegExp.$3} ${RegExp.$4}:${RegExp.$5}`;
    return ret;
  }).join("\n");
  
  copyDialog(s);
})();
2020-04-30: update
(function(){
  let t = [].map.call(document.querySelectorAll("#users-list li a[target='_blank']"),e=>e.textContent).sort().map((e,i)=>`${i}\t${e}`).join("\n");
  console.log(t);
})();

orig

「自動出欠」活動の「出欠表」タブで出席者の一覧を得る

2020-04-23: 初版

2020-11-30: 出席人数のカウントを追加

2020-12-14: 出席状況数値ラベル、出席登録時刻、備考の追加

(function(){
  var s = 
    [].reduce.call(document.querySelectorAll(".generaltable tbody tr"),(r,e)=>{
      let c2 = e.querySelector(".c2").textContent;
      let radioname = e.querySelector("input").name;
      let time = e.querySelector(".c11").textContent;
      let remarks = e.querySelector("input[name^='remarks']").value;
      let attend = document.querySelector("form[name='takeattend']").elements[radioname].value;
      let reg = /[PLE]/; // PLEXY=出遅早欠未
      let key={P:1,L:2,E:"",X:3,Y:""};
      if (attend.match(reg)) r.push(`${c2}\t${attend}\t${key[attend]}\t${time}\t${remarks}`);
      return r;
    },[]).join("\n");
  console.log(`${s}`);
  console.log(s.split("\n").length);
  return "";
})();
「ナビゲーション」→「現在のコース」→「...」→「参加者」に連番を振る
(function(){
  let i = 1;
  [].forEach.call(document.querySelectorAll("#participants tbody tr td:first-of-type"),e=>e.insertBefore(document.createTextNode(i++), e.firstChild));
})();
小テスト作文問題の手動採点で文字数を表示する。
(function(){
  let e = document.querySelectorAll(".qtype_essay_response");
  [].forEach.call(e, e=>e.parentNode.appendChild(document.createTextNode(`文字数:${e.textContent.length}`)));
})();
小テスト作文問題の手動採点で文字数について人数、平均、パーセンタイル、標準偏差、標準誤差、信頼区間と、各解答の文字数、順位、パーセンタイルを表示する。
(function(){
  function quantile(x, probs) {
    return probs.map(v=>{
      let i = (x.length - 1) * v;
      return (x[Math.floor(i)] + x[Math.ceil(i)]) / 2;
    });
  }
  function round(x, p) {
    let y = 10**(p|0);
    return Math.round(x*y)/y;
  }
  function fmti(x, w, pad) {
    w = Math.max(w|0, `${x}`.length);
    pad = pad == null ? " " : `${pad}`;
    return `${Array(w).fill(pad).join("")}${x}`.slice(-w);
  }
  function fmtf(x, w, p, pad) {
    x = round(x,p);
    let rpart = `${x}`.replace(/^[-+]?[0-9]+/,"").length;
    pad = pad == null ? " " : `${pad}`;
    if (pad != " " && rpart == 0) {x = `${x}.`; rpart++}
    console.log(p - rpart + 1, p, rpart - 1);
    if (rpart - 1 < p) x += Array(p - rpart + 1).fill(pad).join("").slice(0, p - rpart + 1);
    return fmti(x, w, pad);
  }
  
  [].forEach.call(document.querySelectorAll(".nchars-container, .stat"), e=>e.parentNode.removeChild(e));
  let e = document.querySelectorAll(".qtype_essay_response");
  [].forEach.call(e, e=>{
    let nchars = document.createElement("span");
    nchars.classList.add("nchars");
    nchars.textContent = e.textContent.length;
    let div = document.createElement("div");
    div.classList.add("nchars-container");
    div.textContent = "文字数: ";
    div.appendChild(nchars);
    e.parentNode.appendChild(div);
  });
  let spans = document.querySelectorAll(".nchars");
  let n    = spans.length;
  let s    = [].reduce.call(spans, (r,e)=>r+parseInt(e.textContent)   ,0) / n;
  let varp = [].reduce.call(spans, (r,e)=>r+parseInt(e.textContent)**2,0) / n - s**2;
  let vars = varp * n / (n - 1);
  let sdp  = Math.sqrt(varp);
  let sds  = Math.sqrt(vars);
  let se   = sds / Math.sqrt(n);
  let conf95 = se * 1.959964;
  let ncharss = [].map.call(spans, e=>parseInt(e.textContent)).sort((a,b)=>b-a); // 降順sort
  let stat = document.createElement("pre");
  stat.classList.add("stat")
  stat.textContent = ""
    + `n   = ${fmti(n,3)}   [${[0, 0.25, 0.5, 0.75, 1].map(v=>fmtf((n-1)*v+1,6,2)).join(", ") }]\n`
    + `s   = ${fmtf(s,5,1)} [${quantile(ncharss, [0, 0.25, 0.5, 0.75, 1]).map(v=>fmtf(v,6,2)).join(", ")}]\n`
    + `sdp = ${fmtf(sdp,5,1)}\n`
    + `sds = ${fmtf(sds,5,1)}\n`
    + `se  = ${fmtf(se,5,1)}\n`
    + `95% conf = ${fmtf(s,5,1)} ± ${round(conf95,1)} [${fmtf(s-conf95,5,1)}, ${fmtf(s+conf95,5,1)}]\n`
    + "";
  Object.assign(stat.style, {position: "fixed", zIndex: 999});
  document.body.insertBefore(stat, document.body.firstChild);
  [].forEach.call(spans, e=>{
    let nchar   = parseInt(e.textContent);
    let ord     = ncharss.indexOf(nchars) + 1;
    let percent = round((n - ord) * 100 / n,1);
    let txt     = document.createTextNode(` : ${ord} : ${percent}%`);
    e.parentNode.appendChild(txt);
  });
})();
小テストの提出一覧画面で、checkbox の後ろに index を付与して、提出数の確認を容易にします。
[].forEach.call(document.querySelectorAll("#attempts input[type='checkbox']"),(e, i)=>{
  let t = document.createTextNode(i+1);
  e.parentNode.insertBefore(t, e.nextSibling);
});
ワークショップの評価フェーズにて、相互評価の未提出者と未提出数の一覧を得ます。
(function(){
  let never={};
  [].reduce.call(document.querySelectorAll(".grading-report tbody tr"), (r,e)=>{
    let tds = e.querySelectorAll("td");
    if (tds.length == 4) {
      r = tds[0].textContent;
    }
    if (tds[tds.length -1].classList.contains("null")) {
      never[r] = (never[r] ?? 0) + 1;
    }
    return r;
  },null);
  let a = Object.keys(never).map(k=>`${k}\t:\t${never[k]}`);
  console.log(a.join("\n"));
})();
SHIFT+PageUp/PageDown で前後の解答に移動します。
/**
 * Moodle 2 hack for report.php&mode=grading: jump prev/next answer
 * Jump prev/next answer with SHIFT+PageUp/PageDown key.
 */
(function(){
  function getY(e) {
    return e ? e.offsetTop + getY(e.offsetParent) : 0;
  }
  function scrollY() {
    return Math.round(window.scrollY)
  }
  function jump(dir) {
    let header = document.querySelector("header");
    let hh = getComputedStyle(header).position == "fixed" ? header.clientHeight : 0;
    let scrollpos = scrollY() + hh;
    let h4s = [].map.call(document.querySelectorAll("form > div > h4"), e=>e);

    function prev() {return h4s.reverse().find(e=>getY(e)<scrollpos)||h4s[h4s.length-1];}
    function next() {return h4s          .find(e=>scrollpos<getY(e))||h4s[h4s.length-1];}
    let e = dir < 0 ? prev() : next();
    
    e.nextSibling.querySelector(".editor_atto_content").focus();
    window.scroll(0, getY(e) - hh);
  }
  document.addEventListener("keydown",event=>{
    if (event.key.match(/Page(Up|Down)/) && event.getModifierState("Shift")) {
      event.preventDefault();
      jump(event.key == "PageUp" ? -1: +1);
    }
  });
})();

関連

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

管理人/副管理人のみ編集できます