From f7d9199500da6ddb872be361a7610fe77d6daf78 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Mon, 27 Apr 2026 00:28:51 +0800 Subject: [PATCH 01/10] assessmentsAverage: Minor WEIGHT label styling fixes --- src/plugins/built-in/assessmentsAverage/utils.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index d833cc98..2740b524 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -113,17 +113,14 @@ function createWeightLabel( ? `${Number(weighting) % 1 === 0 ? Number(weighting) : weighting}%` : "N/A"; } - - // Stack weight under Max/native stats — absolute right:0 overlapped the max column (#414). + statsContainer.style.display = "flex"; - statsContainer.style.flexDirection = "column"; - statsContainer.style.alignItems = "flex-end"; - statsContainer.style.gap = "2px"; - statsContainer.style.justifyContent = "center"; + statsContainer.style.alignItems = "center"; + statsContainer.style.justifyContent = "space-between"; + statsContainer.style.width = "100%"; - weightLabel.style.position = "relative"; - weightLabel.style.inset = "unset"; - weightLabel.style.transform = "none"; + weightLabel.style.flex = "none"; + weightLabel.style.width = "fit-content"; statsContainer.appendChild(weightLabel); } From 678a958351ec71a14a65d75b3ebe334f5065f047 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Mon, 4 May 2026 18:32:32 +0800 Subject: [PATCH 02/10] assessmentsAverage: Add ability to override/set weighting per assessment. --- .../built-in/assessmentsAverage/index.ts | 265 ++++++++------- .../built-in/assessmentsAverage/utils.ts | 310 +++++++++++++++++- 2 files changed, 434 insertions(+), 141 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/index.ts b/src/plugins/built-in/assessmentsAverage/index.ts index ed7140f1..c99ffad1 100644 --- a/src/plugins/built-in/assessmentsAverage/index.ts +++ b/src/plugins/built-in/assessmentsAverage/index.ts @@ -12,6 +12,7 @@ import { clearStuck, getClassByPattern, initStorage, + injectWeightingsTab, letterToNumber, parseAssessments, processAssessments, @@ -20,6 +21,7 @@ import { interface weightingsStorage { weightings: Record; assessments: Record; + weightingOverrides: Record; } const settings = defineSettings({ @@ -37,6 +39,8 @@ class AssessmentsAveragePluginClass extends BasePlugin { const instance = new AssessmentsAveragePluginClass(); +let overrideListenerController: AbortController | null = null; + const assessmentsAveragePlugin: Plugin = { id: "assessments-average", name: "Assessment Averages", @@ -58,143 +62,150 @@ const assessmentsAveragePlugin: Plugin = { ); await parseAssessments(api); - - const sampleAssessmentItem = document.querySelector( - "[class*='AssessmentItem__AssessmentItem___']", + await renderSubjectAverage(api); + overrideListenerController?.abort(); + overrideListenerController = new AbortController(); + document.addEventListener( + "betterseqta:overrideChanged", + () => renderSubjectAverage(api), + { signal: overrideListenerController.signal }, ); - if (!sampleAssessmentItem) return; - - const assessmentItemClass = - Array.from(sampleAssessmentItem.classList).find((c) => - c.startsWith("AssessmentItem__AssessmentItem___"), - ) || ""; - - const metaContainerClass = getClassByPattern( - sampleAssessmentItem, - "AssessmentItem__metaContainer___", - ); - const metaClass = getClassByPattern( - sampleAssessmentItem, - "AssessmentItem__meta___", - ); - const simpleResultClass = getClassByPattern( - sampleAssessmentItem, - "AssessmentItem__simpleResult___", - ); - const titleClass = getClassByPattern( - sampleAssessmentItem, - "AssessmentItem__title___", - ); - - const thermoscoreElement = document.querySelector( - "[class*='Thermoscore__Thermoscore___']", - ); - if (!thermoscoreElement) return; - - const thermoscoreClass = - Array.from(thermoscoreElement.classList).find((c) => - c.startsWith("Thermoscore__Thermoscore___"), - ) || ""; - const fillClass = getClassByPattern( - thermoscoreElement, - "Thermoscore__fill___", - ); - const textClass = getClassByPattern( - thermoscoreElement, - "Thermoscore__text___", - ); - - const assessmentsList = document.querySelector( - "#main > .assessmentsWrapper .assessments [class*='AssessmentList__items___']", - ); - if (!assessmentsList) return; - - const state = await ReactFiber.find( - "[class*='AssessmentList__items___']", - ).getState(); - const marks = state["marks"]; - if (!marks || !marks.length) return; - - const assessmentItems = Array.from( - assessmentsList.querySelectorAll( - `[class*='AssessmentItem__AssessmentItem___']`, - ), - ).filter( - (item) => - !item - .querySelector(`[class*='AssessmentItem__title___']`) - ?.textContent?.includes("Subject Average"), - ); - - const { weightedTotal, totalWeight, hasInaccurateWeighting, count } = - await processAssessments(api, assessmentItems); - - if (!count || totalWeight === 0) return; - - const avg = weightedTotal / totalWeight; - const rounded = Math.ceil(avg / 5) * 5; - const numberToLetter = Object.entries(letterToNumber).reduce( - (acc, [k, v]) => { - acc[v] = k; - return acc; - }, - {} as Record, - ); - - const letterAvg = numberToLetter[rounded] ?? "N/A"; - const display = api.settings.lettergrade - ? letterAvg - : `${avg.toFixed(2)}%`; - - const existing = assessmentsList.querySelector( - `[class*='AssessmentItem__title___']`, - ); - if (existing?.textContent === "Subject Average") return; - - let warningHTML = ""; - if (hasInaccurateWeighting) { - warningHTML = /* html */ ` -
- ⚠ Some weightings unavailable -
- `; - } - - assessmentsList.insertBefore( - stringToHTML(/* html */ ` -
-
-
-
-
Subject Average
- ${warningHTML} -
-
-
-
-
-
${display}
-
-
-
- `).firstChild!, - assessmentsList.firstChild, - ); - - applySubjectColourToOverallResult(); - - const observer = new MutationObserver(() => { - applySubjectColourToOverallResult(); - }); const wrapper = document.querySelector(".assessmentsWrapper"); if (wrapper) { + const observer = new MutationObserver(() => { + applySubjectColourToOverallResult(); + }); observer.observe(wrapper, { childList: true, subtree: true }); setTimeout(() => observer.disconnect(), 10000); } }); + api.seqta.onMount("[class*='SelectedAssessment__']", () => { + injectWeightingsTab(api); + }); }, }; +let renderInFlight = false; +async function renderSubjectAverage(api: any) { + if (renderInFlight) return; + renderInFlight = true; + + try { + const assessmentsList = document.querySelector( + "#main > .assessmentsWrapper .assessments [class*='AssessmentList__items___']", + ); + if (!assessmentsList) return; + + // Remove existing subject average before re-rendering + Array.from( + assessmentsList.querySelectorAll(`[class*='AssessmentItem__title___']`), + ) + .find((el) => el.textContent === "Subject Average") + ?.closest("[class*='AssessmentItem__AssessmentItem___']") + ?.remove(); + + const sampleAssessmentItem = document.querySelector( + "[class*='AssessmentItem__AssessmentItem___']", + ); + if (!sampleAssessmentItem) return; + const assessmentItemClass = + Array.from(sampleAssessmentItem.classList).find((c) => + c.startsWith("AssessmentItem__AssessmentItem___"), + ) || ""; + const metaContainerClass = getClassByPattern( + sampleAssessmentItem, + "AssessmentItem__metaContainer___", + ); + const metaClass = getClassByPattern( + sampleAssessmentItem, + "AssessmentItem__meta___", + ); + const simpleResultClass = getClassByPattern( + sampleAssessmentItem, + "AssessmentItem__simpleResult___", + ); + const titleClass = getClassByPattern( + sampleAssessmentItem, + "AssessmentItem__title___", + ); + const thermoscoreElement = document.querySelector( + "[class*='Thermoscore__Thermoscore___']", + ); + if (!thermoscoreElement) return; + const thermoscoreClass = + Array.from(thermoscoreElement.classList).find((c) => + c.startsWith("Thermoscore__Thermoscore___"), + ) || ""; + const fillClass = getClassByPattern( + thermoscoreElement, + "Thermoscore__fill___", + ); + const textClass = getClassByPattern( + thermoscoreElement, + "Thermoscore__text___", + ); + const state = await ReactFiber.find( + "[class*='AssessmentList__items___']", + ).getState(); + const marks = state["marks"]; + if (!marks || !marks.length) return; + const assessmentItems = Array.from( + assessmentsList.querySelectorAll( + `[class*='AssessmentItem__AssessmentItem___']`, + ), + ).filter( + (item) => + !item + .querySelector(`[class*='AssessmentItem__title___']`) + ?.textContent?.includes("Subject Average"), + ); + const { weightedTotal, totalWeight, hasInaccurateWeighting, count } = + await processAssessments(api, assessmentItems); + if (!count || totalWeight === 0) return; + const avg = weightedTotal / totalWeight; + const rounded = Math.ceil(avg / 5) * 5; + const numberToLetter = Object.entries(letterToNumber).reduce( + (acc, [k, v]) => { + acc[v] = k; + return acc; + }, + {} as Record, + ); + const letterAvg = numberToLetter[rounded] ?? "N/A"; + const display = api.settings.lettergrade ? letterAvg : `${avg.toFixed(2)}%`; + let warningHTML = ""; + if (hasInaccurateWeighting) { + warningHTML = /* html */ ` +
+ ⚠ Some weightings unavailable +
+ `; + } + assessmentsList.insertBefore( + stringToHTML(/* html */ ` +
+
+
+
+
Subject Average
+ ${warningHTML} +
+
+
+
+
+
${display}
+
+
+
+ `).firstChild!, + assessmentsList.firstChild, + ); + applySubjectColourToOverallResult(); + } finally { + renderInFlight = false; + } +} function applySubjectColourToOverallResult() { const selectedAssessmentItem = document.querySelector( "[class*='AssessmentItem__AssessmentItem___'][class*='selected___']", diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index 2740b524..c27a5ec4 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -17,6 +17,9 @@ export async function initStorage(api: any) { if (!api.storage.assessments) { api.storage.assessments = {}; } + if (!api.storage.weightingOverrides) { + api.storage.weightingOverrides = {}; + } } export function clearStuck(api: any) { @@ -82,12 +85,24 @@ function createWeightLabel( const statsContainer = assessmentItem.querySelector( `[class*='AssessmentItem__stats___']`, ) as HTMLElement; + if (!statsContainer) return; - if ( - !statsContainer || - statsContainer.querySelector(".betterseqta-weight-label") - ) + const displayText = + weighting && weighting !== "processing" + ? `${Number(weighting) % 1 === 0 ? Number(weighting) : weighting}%` + : "N/A"; + + const existingLabel = statsContainer.querySelector( + ".betterseqta-weight-label", + ) as HTMLElement | null; + + if (existingLabel) { + const textNodes = Array.from(existingLabel.childNodes).filter( + (node) => node.nodeType === Node.TEXT_NODE, + ); + if (textNodes.length) textNodes[0].textContent = displayText; return; + } const label = statsContainer.querySelector( `[class*='Label__Label___']`, @@ -106,14 +121,7 @@ function createWeightLabel( const textNodes = Array.from(weightLabel.childNodes).filter( (node) => node.nodeType === Node.TEXT_NODE, ); - - if (textNodes.length) { - textNodes[0].textContent = - weighting && weighting !== "processing" - ? `${Number(weighting) % 1 === 0 ? Number(weighting) : weighting}%` - : "N/A"; - } - + if (textNodes.length) textNodes[0].textContent = displayText; statsContainer.style.display = "flex"; statsContainer.style.alignItems = "center"; statsContainer.style.justifyContent = "space-between"; @@ -225,7 +233,8 @@ async function fetchPDFAsArrayBuffer(url: string): Promise { export async function extractPDFText(url: string): Promise { try { if (isFirefox) { - const { lib: pdfLibUrl, worker: pdfWorkerUrl } = getPdfjsPageContextUrls(); + const { lib: pdfLibUrl, worker: pdfWorkerUrl } = + getPdfjsPageContextUrls(); const escJsSingleQuoted = (s: string) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); const pdfLibInj = escJsSingleQuoted(pdfLibUrl); @@ -547,9 +556,13 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { if (!title) continue; const assessmentID = api.storage.assessments?.[title]; - const weighting = assessmentID + const autoWeighting = assessmentID ? api.storage.weightings?.[assessmentID] : undefined; + const override = assessmentID + ? api.storage.weightingOverrides?.[assessmentID] + : undefined; + const weighting = override ?? autoWeighting; createWeightLabel(assessmentItem, weighting); @@ -584,3 +597,272 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { count, }; } + +// Add this above injectWeightingsTab in utils.ts +function resolveTabSetClasses(): Record { + const patterns = [ + "TabSet__tabsheet___", + "TabSet__hidden___", + "TabSet__selected___", + "TabSet__disappearToLeft___", + "TabSet__disappearToRight___", + "TabSet__appearFromRight___", + "TabSet__appearFromLeft___", + ]; + + const resolved: Record = {}; + + // First pass: scan live DOM elements (fast, covers currently-applied classes) + const allClasses = Array.from( + document.querySelectorAll('[class*="TabSet__"]'), + ).flatMap((el) => Array.from(el.classList)); + + for (const pattern of patterns) { + const found = allClasses.find((c) => c.startsWith(pattern)); + if (found) resolved[pattern] = found; + } + + // Second pass: scan stylesheets for any classes not yet in the DOM + // (e.g. animation classes that haven't been applied yet) + const missing = patterns.filter((p) => !resolved[p]); + if (missing.length > 0) { + try { + for (const sheet of Array.from(document.styleSheets)) { + if (missing.every((p) => resolved[p])) break; + try { + for (const rule of Array.from(sheet.cssRules ?? [])) { + if (!(rule instanceof CSSStyleRule)) continue; + const selectorClasses = + rule.selectorText.match(/\.([\w-]+)/g) ?? []; + for (const pattern of missing) { + if (!resolved[pattern]) { + const match = selectorClasses.find((c) => + c.slice(1).startsWith(pattern), + ); + if (match) resolved[pattern] = match.slice(1); + } + } + } + } catch { + // Cross-origin stylesheet — skip + } + } + } catch {} + } + + // Fallback: use the base pattern as-is so the function doesn't crash, + // though styles won't apply if the hash is truly unknown. + for (const pattern of patterns) { + if (!resolved[pattern]) resolved[pattern] = pattern; + } + + return resolved; +} + +function buildWeightingsTabContent(api: any, sheet: HTMLElement) { + const titleEl = document.querySelector( + "[class*='AssessmentItem__AssessmentItem___'][class*='selected___'] [class*='AssessmentItem__title___']", + ); + const title = titleEl?.textContent?.trim(); + const assessmentID = title ? api.storage.assessments?.[title] : undefined; + + const rawWeight = assessmentID + ? api.storage.weightings?.[assessmentID] + : undefined; + + const weightingUnavailable = rawWeight === "N/A"; + + const autoWeight = + rawWeight && rawWeight !== "processing" && rawWeight !== "N/A" + ? rawWeight + : undefined; + + const override = assessmentID + ? api.storage.weightingOverrides?.[assessmentID] + : undefined; + + const statusNote = !assessmentID + ? "" + : rawWeight === "processing" + ? "Weighting is still being detected." + : weightingUnavailable + ? "No weighting was found in the marksheet. Set one manually." + : "Overrides the auto-detected value."; + + sheet.innerHTML = ` + +
+

Weighting Override

+

+ Set the weighting for this assessment manually. + ${statusNote} +

+
+ + ${autoWeight != null ? `${autoWeight}%` : "none"} +
+
+ + +
+
+ +
+ ${!assessmentID ? `

Assessment not yet indexed — try refreshing.

` : ""} +
+ `; + + if (!assessmentID) return; + + const input = sheet.querySelector( + "#betterseqta-weight-override", + ) as HTMLInputElement; + const statusEl = sheet.querySelector( + ".betterseqta-save-status", + ) as HTMLElement; + + const save = () => { + const raw = input.value.trim(); + if (raw === "") { + const { [assessmentID]: _, ...rest } = api.storage.weightingOverrides; + api.storage.weightingOverrides = rest; + } else { + const val = parseFloat(raw); + if (isNaN(val) || val < 0) { + input.style.borderColor = "rgba(255,80,80,0.6)"; + statusEl.textContent = "Invalid. Must be 0 or greater"; + statusEl.style.color = "rgba(255,80,80,0.8)"; + return; + } + input.style.borderColor = "rgba(128,128,128,0.3)"; + api.storage.weightingOverrides = { + ...api.storage.weightingOverrides, + [assessmentID]: String(val), + }; + } + statusEl.textContent = "Saved"; + statusEl.style.color = ""; + setTimeout(() => (statusEl.textContent = ""), 2000); + document.dispatchEvent(new CustomEvent("betterseqta:overrideChanged")); + }; + + input.addEventListener("blur", save); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + input.blur(); + save(); + } + }); + input.addEventListener("input", () => { + input.style.borderColor = "rgba(128,128,128,0.3)"; + if (statusEl.textContent === "Invalid. Must be 0 or greater.") + statusEl.textContent = ""; + }); +} + +export function injectWeightingsTab(api: any) { + const tabList = document.querySelector( + '[class*="TabSet__tabs___"]', + ) as HTMLElement; + const container = document.querySelector( + '[class*="TabSet__tabContainer___"]', + ) as HTMLElement; + if (!tabList || !container) return; + if (tabList.querySelector(".betterseqta-weightings-tab")) return; + + const cls = resolveTabSetClasses(); + + const prefix = (tabList.querySelector("li") as HTMLElement).id.replace( + /-tab-\d+$/, + "", + ); + const newIndex = tabList.querySelectorAll("li").length; + + const newTab = document.createElement("li"); + newTab.id = `${prefix}-tab-${newIndex}`; + newTab.className = ""; + newTab.setAttribute("aria-selected", "false"); + newTab.setAttribute("aria-controls", `${prefix}-tabsheet-${newIndex}`); + newTab.classList.add("betterseqta-weightings-tab"); + newTab.textContent = "Weightings"; + tabList.appendChild(newTab); + + const newSheet = document.createElement("div"); + newSheet.id = `${prefix}-tabsheet-${newIndex}`; + newSheet.setAttribute("aria-labelledby", `${prefix}-tab-${newIndex}`); + newSheet.className = [ + cls["TabSet__tabsheet___"], + cls["TabSet__hidden___"], + cls["TabSet__disappearToRight___"], + ].join(" "); + container.appendChild(newSheet); + + let populated = false; + newTab.addEventListener("click", () => { + if (!populated) { + buildWeightingsTabContent(api, newSheet); + populated = true; + } + }); + + const allTabs = Array.from(tabList.querySelectorAll("li")); + const allSheets = Array.from( + container.querySelectorAll('[class*="tabsheet"]'), + ); + + allTabs.forEach((tab, i) => { + tab.addEventListener("click", () => { + const currentIndex = allTabs.findIndex((t) => + t.className.includes("TabSet__selected___"), + ); + if (i === currentIndex) return; + const goingRight = i > currentIndex; + + allTabs.forEach((t) => { + t.className = ""; + t.setAttribute("aria-selected", "false"); + }); + + allSheets[currentIndex].className = [ + cls["TabSet__tabsheet___"], + cls["TabSet__hidden___"], + goingRight + ? cls["TabSet__disappearToLeft___"] + : cls["TabSet__disappearToRight___"], + ].join(" "); + + allSheets[i].className = [ + cls["TabSet__tabsheet___"], + cls["TabSet__selected___"], + goingRight + ? cls["TabSet__appearFromRight___"] + : cls["TabSet__appearFromLeft___"], + ].join(" "); + + tab.className = cls["TabSet__selected___"]; + tab.setAttribute("aria-selected", "true"); + }); + }); +} \ No newline at end of file From 260afac294b18a2b4886b540d93047e40cf5d533 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Mon, 4 May 2026 18:36:16 +0800 Subject: [PATCH 03/10] assessmentsAverage: Fix display of missing weighting, and minor change to override section. --- src/plugins/built-in/assessmentsAverage/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index c27a5ec4..9522014d 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -88,7 +88,7 @@ function createWeightLabel( if (!statsContainer) return; const displayText = - weighting && weighting !== "processing" + weighting && weighting !== "processing" && weighting !== "N/A" ? `${Number(weighting) % 1 === 0 ? Number(weighting) : weighting}%` : "N/A"; @@ -698,7 +698,7 @@ function buildWeightingsTabContent(api: any, sheet: HTMLElement) {

Weighting Override

- Set the weighting for this assessment manually. + Set the weighting for this assessment. ${statusNote}

From 999f12e9585fec28b010da24308e859fb117f55d Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Mon, 4 May 2026 22:39:53 +0800 Subject: [PATCH 04/10] assessmentsAverage: Add changes to changelog --- src/seqta/utils/Openers/OpenWhatsNewPopup.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts index 71c51032..371cc03b 100644 --- a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts +++ b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts @@ -34,6 +34,11 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) { const text = stringToHTML(/* html */ `
+

3.6.4 - Assessment weighting override & fixes

+
  • Added the ability to override/add weightings to assessments (on assessment page).
  • +
  • Fixed the display of weightings that could not automatically be discovered.
  • +
  • Fixed the formatting of the weighting tag that was broken due to a SEQTA update.
  • +

    3.6.3 - Assessment overview fix

  • Fixed assessments overview failing to load.
  • From f35520029f09d3f8b24dc7493284ec2802117aba Mon Sep 17 00:00:00 2001 From: Jaxx7594 Date: Mon, 4 May 2026 22:53:05 +0800 Subject: [PATCH 05/10] assessmentAverage: Remove remnant comment --- src/plugins/built-in/assessmentsAverage/utils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index 9522014d..ae6cbc19 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -598,7 +598,6 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { }; } -// Add this above injectWeightingsTab in utils.ts function resolveTabSetClasses(): Record { const patterns = [ "TabSet__tabsheet___", @@ -644,7 +643,7 @@ function resolveTabSetClasses(): Record { } } } catch { - // Cross-origin stylesheet — skip + // Cross-origin stylesheet } } } catch {} @@ -865,4 +864,4 @@ export function injectWeightingsTab(api: any) { tab.setAttribute("aria-selected", "true"); }); }); -} \ No newline at end of file +} From 2aecd63850cb68bd65b0af8c16ae0aa551f0ec02 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Tue, 5 May 2026 17:44:58 +0930 Subject: [PATCH 06/10] feat: dont inject weightings page in assements without results --- src/plugins/built-in/assessmentsAverage/utils.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index ae6cbc19..b282a1b1 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -791,6 +791,19 @@ export function injectWeightingsTab(api: any) { if (!tabList || !container) return; if (tabList.querySelector(".betterseqta-weightings-tab")) return; + const selectedTitle = document + .querySelector( + "[class*='AssessmentItem__AssessmentItem___'][class*='selected___'] [class*='AssessmentItem__title___']", + ) + ?.textContent?.trim(); + const selectedAssessmentID = selectedTitle + ? api.storage.assessments?.[selectedTitle] + : undefined; + + // Only inject for assessments that exist in the marks/task dataset. + // This avoids showing the tab on PENDING/UPCOMING "details-only" assessments. + if (!selectedAssessmentID) return; + const cls = resolveTabSetClasses(); const prefix = (tabList.querySelector("li") as HTMLElement).id.replace( From f721bf6609a9b9da5c367b22306bf20ed4ceaf80 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Tue, 5 May 2026 16:32:12 +0800 Subject: [PATCH 07/10] Revert "feat: dont inject weightings page in assements without results" This reverts commit 2aecd63850cb68bd65b0af8c16ae0aa551f0ec02. Reverting so that I can solve the indexing issue. Only marked assessments are getting indexed, which is incorrect behaviour that slipped testing when the plugin was first made. --- src/plugins/built-in/assessmentsAverage/utils.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index b282a1b1..ae6cbc19 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -791,19 +791,6 @@ export function injectWeightingsTab(api: any) { if (!tabList || !container) return; if (tabList.querySelector(".betterseqta-weightings-tab")) return; - const selectedTitle = document - .querySelector( - "[class*='AssessmentItem__AssessmentItem___'][class*='selected___'] [class*='AssessmentItem__title___']", - ) - ?.textContent?.trim(); - const selectedAssessmentID = selectedTitle - ? api.storage.assessments?.[selectedTitle] - : undefined; - - // Only inject for assessments that exist in the marks/task dataset. - // This avoids showing the tab on PENDING/UPCOMING "details-only" assessments. - if (!selectedAssessmentID) return; - const cls = resolveTabSetClasses(); const prefix = (tabList.querySelector("li") as HTMLElement).id.replace( From b0857054ebf35c26e4be01da512dfcb7c6088e68 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Tue, 5 May 2026 17:56:06 +0800 Subject: [PATCH 08/10] assessmentsAverage: Fix unmarked/upcoming assessment indexing and weight display --- .../built-in/assessmentsAverage/index.ts | 36 ++++---- .../built-in/assessmentsAverage/utils.ts | 83 ++++++++++++------- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/index.ts b/src/plugins/built-in/assessmentsAverage/index.ts index c99ffad1..b3438ea6 100644 --- a/src/plugins/built-in/assessmentsAverage/index.ts +++ b/src/plugins/built-in/assessmentsAverage/index.ts @@ -7,7 +7,6 @@ import { import { type Plugin } from "@/plugins/core/types"; import stringToHTML from "@/seqta/utils/stringToHTML"; import { waitForElm } from "@/seqta/utils/waitForElm"; -import ReactFiber from "@/seqta/utils/ReactFiber.ts"; import { clearStuck, getClassByPattern, @@ -128,6 +127,22 @@ async function renderSubjectAverage(api: any) { sampleAssessmentItem, "AssessmentItem__title___", ); + + const assessmentItems = Array.from( + assessmentsList.querySelectorAll( + `[class*='AssessmentItem__AssessmentItem___']`, + ), + ).filter( + (item) => + !item + .querySelector(`[class*='AssessmentItem__title___']`) + ?.textContent?.includes("Subject Average"), + ); + + const { weightedTotal, totalWeight, hasInaccurateWeighting, count } = + await processAssessments(api, assessmentItems); + if (!count || totalWeight === 0) return; + const thermoscoreElement = document.querySelector( "[class*='Thermoscore__Thermoscore___']", ); @@ -144,24 +159,7 @@ async function renderSubjectAverage(api: any) { thermoscoreElement, "Thermoscore__text___", ); - const state = await ReactFiber.find( - "[class*='AssessmentList__items___']", - ).getState(); - const marks = state["marks"]; - if (!marks || !marks.length) return; - const assessmentItems = Array.from( - assessmentsList.querySelectorAll( - `[class*='AssessmentItem__AssessmentItem___']`, - ), - ).filter( - (item) => - !item - .querySelector(`[class*='AssessmentItem__title___']`) - ?.textContent?.includes("Subject Average"), - ); - const { weightedTotal, totalWeight, hasInaccurateWeighting, count } = - await processAssessments(api, assessmentItems); - if (!count || totalWeight === 0) return; + const avg = weightedTotal / totalWeight; const rounded = Math.ceil(avg / 5) * 5; const numberToLetter = Object.entries(letterToNumber).reduce( diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index ae6cbc19..e841e8a9 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -82,10 +82,24 @@ function createWeightLabel( assessmentItem: Element, weighting: string | undefined, ) { - const statsContainer = assessmentItem.querySelector( + let statsContainer = assessmentItem.querySelector( `[class*='AssessmentItem__stats___']`, - ) as HTMLElement; - if (!statsContainer) return; + ) as HTMLElement | null; + + if (!statsContainer) { + const statsClass = getClassByPattern(document, "AssessmentItem__stats___"); + statsContainer = document.createElement("div"); + statsContainer.className = statsClass; + statsContainer.style.justifyContent = "flex-end"; + const thermoscore = assessmentItem.querySelector(`[class*='Thermoscore__Thermoscore___']`); + if (thermoscore) { + thermoscore.insertAdjacentElement("afterend", statsContainer); + } else { + assessmentItem.appendChild(statsContainer); + } + } else { + statsContainer.style.justifyContent = "space-between"; + } const displayText = weighting && weighting !== "processing" && weighting !== "N/A" @@ -104,31 +118,42 @@ function createWeightLabel( return; } - const label = statsContainer.querySelector( + statsContainer.style.display = "flex"; + statsContainer.style.alignItems = "center"; + statsContainer.style.width = "100%"; + + // Try to clone an existing label from the stats container first, + // fall back to building from scratch if none exists + const existingNativeLabel = statsContainer.querySelector( `[class*='Label__Label___']`, - ) as HTMLElement; + ) as HTMLElement | null; - if (!label) return; + const weightLabel = existingNativeLabel + ? (existingNativeLabel.cloneNode(true) as HTMLElement) + : (() => { + const labelClass = getClassByPattern(document, "Label__Label___"); + const innerTextClass = getClassByPattern(document, "Label__innerText___"); + const el = document.createElement("label"); + el.className = labelClass; + el.innerHTML = `
    Weight
    `; + return el; + })(); - const weightLabel = label.cloneNode(true) as HTMLElement; weightLabel.classList.add("betterseqta-weight-label"); + weightLabel.style.flex = "none"; + weightLabel.style.width = "fit-content"; - const innerTextDiv = weightLabel.querySelector( - `[class*='Label__innerText___']`, - ); + const innerTextDiv = weightLabel.querySelector(`[class*='Label__innerText___']`); if (innerTextDiv) innerTextDiv.textContent = "Weight"; const textNodes = Array.from(weightLabel.childNodes).filter( (node) => node.nodeType === Node.TEXT_NODE, ); - if (textNodes.length) textNodes[0].textContent = displayText; - statsContainer.style.display = "flex"; - statsContainer.style.alignItems = "center"; - statsContainer.style.justifyContent = "space-between"; - statsContainer.style.width = "100%"; - - weightLabel.style.flex = "none"; - weightLabel.style.width = "fit-content"; + if (textNodes.length) { + textNodes[0].textContent = displayText; + } else { + weightLabel.appendChild(document.createTextNode(displayText)); + } statsContainer.appendChild(weightLabel); } @@ -525,7 +550,11 @@ export async function parseAssessments(api: any) { "[class*='AssessmentList__items___']", ).getState(); - const marks = state["marks"]; + const marks = [ + ...(state["marks"] ?? []), + ...(state["upcoming"] ?? []), + ...(state["pending"] ?? []), + ]; if (!marks) return; await Promise.all(marks.map((mark: any) => handleWeightings(mark, api))); @@ -538,15 +567,6 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { let count = 0; for (const assessmentItem of assessmentItems) { - const gradeElement = assessmentItem.querySelector( - `[class*='Thermoscore__text___']`, - ); - - if (!gradeElement) continue; - - const grade = parseGrade(gradeElement.textContent || ""); - if (grade <= 0) continue; - const titleEl = assessmentItem.querySelector( `[class*='AssessmentItem__title___']`, ); @@ -566,6 +586,13 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { createWeightLabel(assessmentItem, weighting); + const gradeElement = assessmentItem.querySelector( + `[class*='Thermoscore__text___']`, + ); + if (!gradeElement) continue; + const grade = parseGrade(gradeElement.textContent || ""); + if (grade <= 0) continue; + if ( weighting === null || weighting === undefined || From da5bc7ab112ece82c1455623b01658c841805694 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Tue, 5 May 2026 18:10:13 +0800 Subject: [PATCH 09/10] assessmentsAverage: Fix weight display upon setting override --- src/plugins/built-in/assessmentsAverage/utils.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index e841e8a9..3478d7ba 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -83,24 +83,29 @@ function createWeightLabel( weighting: string | undefined, ) { let statsContainer = assessmentItem.querySelector( - `[class*='AssessmentItem__stats___']`, + `[class*='AssessmentItem__stats___'], .betterseqta-stats-container`, ) as HTMLElement | null; if (!statsContainer) { const statsClass = getClassByPattern(document, "AssessmentItem__stats___"); statsContainer = document.createElement("div"); statsContainer.className = statsClass; - statsContainer.style.justifyContent = "flex-end"; + statsContainer.classList.add("betterseqta-stats-container"); const thermoscore = assessmentItem.querySelector(`[class*='Thermoscore__Thermoscore___']`); if (thermoscore) { thermoscore.insertAdjacentElement("afterend", statsContainer); } else { assessmentItem.appendChild(statsContainer); } - } else { - statsContainer.style.justifyContent = "space-between"; } + const hasNativeLabel = !!statsContainer.querySelector( + `[class*='Label__Label___']:not(.betterseqta-weight-label)`, + ); + statsContainer.style.justifyContent = hasNativeLabel + ? "space-between" + : "flex-end"; + const displayText = weighting && weighting !== "processing" && weighting !== "N/A" ? `${Number(weighting) % 1 === 0 ? Number(weighting) : weighting}%` From aa5d193e5519820ca46df661be57f96f4b905979 Mon Sep 17 00:00:00 2001 From: Jaxon Lewis-Wilson Date: Tue, 5 May 2026 18:13:27 +0800 Subject: [PATCH 10/10] assessmentsAverage: Fix inaccurate weight when a weight == N/A N/A weights were automatically set to a weight of 1 for some reason. I removed it from the calculations completely with this commit. --- src/plugins/built-in/assessmentsAverage/utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/built-in/assessmentsAverage/utils.ts b/src/plugins/built-in/assessmentsAverage/utils.ts index 3478d7ba..bf7514a6 100644 --- a/src/plugins/built-in/assessmentsAverage/utils.ts +++ b/src/plugins/built-in/assessmentsAverage/utils.ts @@ -605,8 +605,7 @@ export async function processAssessments(api: any, assessmentItems: Element[]) { weighting === "processing" ) { hasInaccurateWeighting = true; - weightedTotal += grade; - totalWeight += 1; + continue } else { const weight = parseFloat(weighting);