您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculates the average grade level from the past 15 (or fewer) quizzes available on ReadTheory
// ==UserScript== // @name ReadTheory Average Grade Level Calculator (Past 15 Quizzes) // @namespace https://greasyfork.org/en/users/567951-stuart-saddler // @version 1.12 // @description Calculates the average grade level from the past 15 (or fewer) quizzes available on ReadTheory // @author Stuart Saddler // @icon https://images-na.ssl-images-amazon.com/images/I/41Y-bktG5oL.png // @license MIT // @match *://readtheory.org/app/teacher/reports/* // @grant none // @run-at document-idle // ==/UserScript== (function () { "use strict"; const NUM_RECENT = 15; // Map words to numbers; also allow raw digits like "5" or "5.0" function gradeTextToNumber(gradeText) { if (!gradeText) return 0; const txt = String(gradeText).toLowerCase().trim(); const map = { "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, "thirteen": 13, "fourteen": 14, "fifteen": 15 }; if (map[txt] != null) return map[txt]; const num = parseFloat(txt.replace(/[^0-9.]/g, "")); return Number.isFinite(num) ? num : 0; } function calculateAverage(nums) { const valid = nums.filter(n => Number.isFinite(n) && n > 0); if (valid.length === 0) return "0.0"; const total = valid.reduce((a, b) => a + b, 0); return (total / valid.length).toFixed(1); } function findGradeColumnIndex(table) { // Look only at header row cells const ths = table.querySelectorAll("thead th, tr:first-child th"); let idx = -1; ths.forEach((th, i) => { if (String(th.textContent || "").toLowerCase().includes("grade level")) { idx = i; } }); return idx; // 0-based } function getRecentGradeCells(table, gradeColIdx) { // Pull the first NUM_RECENT body rows from the grade column const rows = Array.from(table.querySelectorAll("tbody tr")).slice(0, NUM_RECENT); const tds = rows.map(r => r.querySelectorAll("td")[gradeColIdx]).filter(Boolean); return tds; } function ensureFloatingWindow() { let el = document.getElementById("average-grade-floating-window"); if (!el) { el = document.createElement("div"); el.id = "average-grade-floating-window"; Object.assign(el.style, { position: "fixed", bottom: "100px", right: "20px", padding: "10px", backgroundColor: "#f1f1f1", border: "2px solid #333", zIndex: "9999", boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.1)", fontSize: "16px", color: "#333", maxWidth: "320px", borderRadius: "6px", lineHeight: "1.3" }); document.body.appendChild(el); } return el; } function calculateAndDisplayAverage() { try { const table = document.querySelector("table"); if (!table) { console.warn("No table found yet."); return; } const colIdx = findGradeColumnIndex(table); if (colIdx === -1) { console.error("Could not find the 'Grade Level' column."); return; } const gradeCells = getRecentGradeCells(table, colIdx); if (gradeCells.length === 0) { console.warn("No grade cells found yet."); return; } const grades = gradeCells.map(td => gradeTextToNumber(td.textContent)); const avg = calculateAverage(grades); const box = ensureFloatingWindow(); box.textContent = "Average Grade Level (Last " + grades.filter(g => g > 0).length + " Quizzes): " + avg; console.log("Average grade level:", avg); } catch (e) { console.error("Average grade calculation failed:", e); } } // Observe content changes and recalc once the table is present let initObserverStarted = false; function startInitObserver() { if (initObserverStarted) return; initObserverStarted = true; const obs = new MutationObserver(function (ml, ob) { const table = document.querySelector("table"); const hasHeader = table && table.querySelector("th"); if (hasHeader) { ob.disconnect(); calculateAndDisplayAverage(); } }); obs.observe(document.body, { childList: true, subtree: true }); } // Recalculate when student selection changes; avoid stacking observers let dropdownListenerAttached = false; function watchStudentDropdown() { if (dropdownListenerAttached) return; const selects = Array.from(document.querySelectorAll("select")); if (selects.length === 0) return; const handler = function () { // Give the page a moment to swap in new rows setTimeout(calculateAndDisplayAverage, 1200); }; selects.forEach(sel => sel.addEventListener("change", handler)); document.addEventListener("change", function (ev) { if (ev.target && ev.target.tagName === "SELECT") { setTimeout(calculateAndDisplayAverage, 1200); } }); dropdownListenerAttached = true; } // Watch a specific panel attribute if present let idObserver; function watchStudentIDAttribute() { const panel = document.querySelector(".quiz-history-panel"); if (!panel) return; if (idObserver) idObserver.disconnect(); idObserver = new MutationObserver(function (ml) { for (const m of ml) { if (m.type === "attributes" && m.attributeName === "studentid") { setTimeout(calculateAndDisplayAverage, 600); break; } } }); idObserver.observe(panel, { attributes: true, attributeFilter: ["studentid"] }); } function init() { startInitObserver(); watchStudentDropdown(); watchStudentIDAttribute(); // First attempt after idle calculateAndDisplayAverage(); } if (document.readyState === "complete" || document.readyState === "interactive") { setTimeout(init, 0); } else { window.addEventListener("load", init); document.addEventListener("DOMContentLoaded", init); } })();