Greasy Fork is available in English.
Calculate grade totals for Canvas courses that have it disabled.
当前为
// ==UserScript==
// @name Canvas Grade Calculator
// @description Calculate grade totals for Canvas courses that have it disabled.
// @namespace https://github.com/uncenter/canvas-grade-calculator
// @match https://*.instructure.com/courses/*/grades
// @grant none
// @homepageURL https://github.com/uncenter/canvas-grade-calculator
// @version 0.2.2
// @author uncenter
// @license MIT
// ==/UserScript==
function calculate() {
if (!document.querySelector('.ic-app-main-content')) {
console.error('Not on Canvas!');
return;
}
if (!document.querySelector('#grade-summary-content')) {
console.error('Not on grades page!');
return;
}
const weights = {};
for (const element of document.querySelectorAll(
'[aria-label="Assignment Weights"] > table tbody > tr'
)) {
const group = element.querySelector('th').textContent;
if (group === 'Total') continue;
const weight =
Number.parseFloat(element.querySelector('td').textContent.slice(0, 2)) /
100;
weights[group] = weight;
}
const assignments = [];
for (const element of document.querySelectorAll(
'#grades_summary tr.assignment_graded.student_assignment'
)) {
let earned, available, title, group;
const a = element.querySelector('th.title');
title = a.querySelector('a').textContent;
group = a.querySelector('div.context').textContent;
const grades = element.querySelector(
'td.assignment_score > div > span.tooltip > span.grade'
);
earned = Number.parseFloat(
[...grades.childNodes]
.find(
(node) =>
node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== ''
)
.textContent.trim()
);
if (typeof earned !== 'number' || Number.isNaN(earned)) continue;
available = Number.parseFloat(
grades.nextElementSibling.textContent.replace('/', '').trim()
);
assignments.push({ earned, available, title, group });
}
if (assignments.length === 0) {
console.warn('No graded assignments found!');
return;
}
/* eslint-disable unicorn/prevent-abbreviations, unicorn/no-array-reduce */
const totalsPerGroup = assignments.reduce(
(acc, { group, earned, available }) => {
acc[group] = acc[group] || { totalEarned: 0, totalAvailable: 0 };
acc[group].totalEarned += earned;
acc[group].totalAvailable += available;
return acc;
},
{}
);
/* eslint-enable */
const weightedPerGroup = {};
for (const category in totalsPerGroup) {
const { totalEarned, totalAvailable } = totalsPerGroup[category];
weightedPerGroup[category] =
(totalEarned / totalAvailable) * 100 || undefined;
}
let grade = 0;
if (Object.entries(weights).length === 0) {
console.log('Categories are not weighted.');
const scores = Object.values(weightedPerGroup).filter(
(x) => x !== undefined
);
grade = scores.reduce((total, score) => total + score, 0) / scores.length;
} else {
for (const category in weightedPerGroup) {
grade += weightedPerGroup[category] * weights[category];
}
}
console.log(
`${grade.toFixed(2)}% across ${assignments.length} graded assignments.`
);
(
document.querySelector('#student-grades-final') ||
document.querySelector('.student_assignment.final_grade')
).outerHTML = `<div class="student_assignment final_grade">Total: <span class="grade">${grade.toFixed(
2
)}%</span></div>`;
}
if (document.querySelector('#student-grades-final')) {
const observer = new MutationObserver(calculate);
observer.observe(document.querySelector('#grades_summary'), {
childList: true,
subtree: true,
});
calculate();
}