Add average grade display and Fix CORS violation

Add average grade display and also fix the CORS violation caused by pdfjs trying to load PDFs from URLs that Firefox extensions can't access.

fixed by instead:
- Fetching the PDF as an ArrayBuffer directly from the URL
- Passing the ArrayBuffer to pdfjs using { data: arrayBuffer } instead of passing a URL
This commit is contained in:
StroepWafel
2026-01-28 16:36:16 +10:30
parent db3f0e0d81
commit f1afa74ee6
@@ -125,10 +125,12 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
); );
if (!assessmentsList) return; if (!assessmentsList) return;
const gradeElements = document.querySelectorAll( // Get marks from React state to match with DOM elements
"[class*='Thermoscore__text___']", const state = await ReactFiber.find(
); "[class*='AssessmentList__items___']",
if (!gradeElements.length) return; ).getState();
const marks = state["marks"];
if (!marks || !marks.length) return;
// Parse and average grades // Parse and average grades
const letterToNumber: Record<string, number> = { const letterToNumber: Record<string, number> = {
@@ -162,19 +164,59 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
return letterToNumber[str] ?? 0; return letterToNumber[str] ?? 0;
} }
let total = 0; // Get all assessment items (excluding the average we might have added)
const assessmentItems = Array.from(
assessmentsList.querySelectorAll(`[class*='AssessmentItem__AssessmentItem___']`),
).filter(
(item) =>
!item.querySelector(`[class*='AssessmentItem__title___']`)?.textContent?.includes("Subject Average"),
);
// Match marks to assessment items and calculate weighted average
let weightedTotal = 0;
let totalWeight = 0;
let hasInaccurateWeighting = false;
let count = 0; let count = 0;
gradeElements.forEach((el) => {
const grade = parseGrade(el.textContent || ""); for (let i = 0; i < marks.length && i < assessmentItems.length; i++) {
if (grade > 0) { const mark = marks[i];
total += grade; const assessmentItem = assessmentItems[i];
count++; const gradeElement = assessmentItem.querySelector(
`[class*='Thermoscore__text___']`,
);
if (!gradeElement) continue;
const grade = parseGrade(gradeElement.textContent || "");
if (grade <= 0) continue;
const assessmentID = String(mark.id);
const weighting = api.storage.weightings[assessmentID];
// Check if weighting is unavailable or still processing
if (!weighting || weighting === "N/A" || weighting === "processing") {
hasInaccurateWeighting = true;
// Fall back to equal weighting if unavailable
weightedTotal += grade;
totalWeight += 1;
} else {
const weight = parseFloat(weighting);
if (!isNaN(weight) && weight > 0) {
weightedTotal += grade * weight;
totalWeight += weight;
} else {
// Invalid weight, use equal weighting
weightedTotal += grade;
totalWeight += 1;
hasInaccurateWeighting = true;
}
} }
}); count++;
}
if (!count) return; if (!count || totalWeight === 0) return;
const avg = total / count; const avg = weightedTotal / totalWeight;
const rounded = Math.ceil(avg / 5) * 5; const rounded = Math.ceil(avg / 5) * 5;
const numberToLetter = Object.entries(letterToNumber).reduce( const numberToLetter = Object.entries(letterToNumber).reduce(
(acc, [k, v]) => { (acc, [k, v]) => {
@@ -195,6 +237,16 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
); );
if (existing?.textContent === "Subject Average") return; if (existing?.textContent === "Subject Average") return;
// Build warning message if needed
let warningHTML = "";
if (hasInaccurateWeighting) {
warningHTML = /* html */ `
<div style="margin-top: 4px; font-size: 11px; color: rgba(255, 255, 255, 0.6); opacity: 0.8; line-height: 1.3;">
⚠ Some weightings unavailable
</div>
`;
}
// Use the dynamic class names in the HTML template // Use the dynamic class names in the HTML template
const averageElement = stringToHTML(/* html */ ` const averageElement = stringToHTML(/* html */ `
<div class="${assessmentItemClass}"> <div class="${assessmentItemClass}">
@@ -202,12 +254,13 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
<div class="${metaClass}"> <div class="${metaClass}">
<div class="${simpleResultClass}"> <div class="${simpleResultClass}">
<div class="${titleClass}">Subject Average</div> <div class="${titleClass}">Subject Average</div>
${warningHTML}
</div> </div>
</div> </div>
</div> </div>
<div class="${thermoscoreClass}"> <div class="${thermoscoreClass}">
<div class="${fillClass}" style="width: ${avg.toFixed(2)}%"> <div class="${fillClass}" style="width: ${avg.toFixed(2)}%">
<div class="${textClass}" title="${display}">${display}</div> <div class="${textClass}" title="${hasInaccurateWeighting ? display + ' (some weightings unavailable)' : display}">${display}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -219,7 +272,11 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
}; };
async function extractPDFText(url: string): Promise<string> { async function extractPDFText(url: string): Promise<string> {
const loadingTask = pdfjs.getDocument(url); // Fetch PDF as ArrayBuffer to avoid blob URL CSP issues in Firefox extensions
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const loadingTask = pdfjs.getDocument({ data: arrayBuffer });
const pdf = await loadingTask.promise; const pdf = await loadingTask.promise;
let text = ""; let text = "";