diff --git a/lib/publish.js b/lib/publish.js index 8da79994..0d5bea50 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -5,26 +5,44 @@ const path = require('path'); function getLatestVersion(files) { console.log('Files passed to getLatestVersion:', files); + const versions = files.map(file => { - const match = file.match(/@(\d+\.\d+\.\d+)-/); + const match = file.match(/@([\d\.]+)-/); console.log('Matching file:', file, 'Version found:', match ? match[1] : 'None'); - return match ? match[1] : null; + + if (!match) return null; + + const fullVersion = match[1]; // Original version (e.g., 3.4.5.1) + const semverVersion = fullVersion.split('.').slice(0, 3).join('.'); // Trim to 3.4.5 + + return { fullVersion, semverVersion }; }).filter(Boolean); - console.log('Extracted versions:', versions); - const latestVersion = semver.maxSatisfying(versions, '*'); - console.log('Latest version:', latestVersion); + console.log('Extracted versions:', versions.map(v => v.semverVersion)); + + // Find latest version using the trimmed semver format + const latestSemver = semver.maxSatisfying(versions.map(v => v.semverVersion), '*'); + console.log('Latest SemVer-compatible version:', latestSemver); + + // Get the full version that matches the latest SemVer version + const latestVersion = versions.find(v => v.semverVersion === latestSemver)?.fullVersion || null; + + console.log('Final selected latest version:', latestVersion); return latestVersion; } function getLatestFiles(browser) { const pattern = `dist/betterseqtaplus@*-*${browser}.zip`; console.log('Glob pattern:', pattern); + const files = glob.sync(pattern); console.log('Files found for browser', browser, ':', files); + const latestVersion = getLatestVersion(files); - const latestFile = files.find(file => file.includes(latestVersion)); + // Find the exact file by matching the original full version + const latestFile = files.find(file => file.includes(`@${latestVersion}-`)); + console.log('Latest file for browser', browser, ':', latestFile); return latestFile; } diff --git a/package.json b/package.json index 50c66299..ba2f81b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "betterseqtaplus", - "version": "3.4.5", + "version": "3.4.6", "type": "module", "description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!", "browserslist": "> 0.5%, last 2 versions, not dead", diff --git a/src/SEQTA.ts b/src/SEQTA.ts index a789d868..21aa1854 100644 --- a/src/SEQTA.ts +++ b/src/SEQTA.ts @@ -1,46 +1,49 @@ // Third-party libraries -import Color from 'color' -import Sortable from 'sortablejs' -import browser from 'webextension-polyfill' -import { animate, stagger } from 'motion' +import Color from "color" +import Sortable from "sortablejs" +import browser from "webextension-polyfill" +import { animate, stagger } from "motion" // Internal utilities and functions -import { delay } from '@/seqta/utils/delay' -import stringToHTML from '@/seqta/utils/stringToHTML' -import { MessageHandler } from '@/seqta/utils/listeners/MessageListener' -import { initializeSettingsState, settingsState } from '@/seqta/utils/listeners/SettingsState' -import { StorageChangeHandler } from '@/seqta/utils/listeners/StorageChanges' -import { eventManager } from '@/seqta/utils/listeners/EventManager' +import { delay } from "@/seqta/utils/delay" +import stringToHTML from "@/seqta/utils/stringToHTML" +import { MessageHandler } from "@/seqta/utils/listeners/MessageListener" +import { + initializeSettingsState, + settingsState, +} from "@/seqta/utils/listeners/SettingsState" +import { StorageChangeHandler } from "@/seqta/utils/listeners/StorageChanges" +import { eventManager } from "@/seqta/utils/listeners/EventManager" // UI and theme management -import RegisterClickListeners from './seqta/utils/listeners/ClickListeners' -import { AddBetterSEQTAElements } from '@/seqta/ui/AddBetterSEQTAElements' -import { enableCurrentTheme } from '@/seqta/ui/themes/enableCurrent' -import loading, { AppendLoadingSymbol } from '@/seqta/ui/Loading' -import { SettingsResizer } from '@/seqta/ui/SettingsResizer' -import { updateAllColors } from '@/seqta/ui/colors/Manager' -import pageState from '@/pageState.js?url' +import RegisterClickListeners from "./seqta/utils/listeners/ClickListeners" +import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements" +import { enableCurrentTheme } from "@/seqta/ui/themes/enableCurrent" +import loading, { AppendLoadingSymbol } from "@/seqta/ui/Loading" +import { SettingsResizer } from "@/seqta/ui/SettingsResizer" +import { updateAllColors } from "@/seqta/ui/colors/Manager" +import pageState from "@/pageState.js?url" // JSON content -import MenuitemSVGKey from '@/seqta/content/MenuItemSVGKey.json' -import ShortcutLinks from '@/seqta/content/links.json' +import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json" +import ShortcutLinks from "@/seqta/content/links.json" // Icons and fonts -import IconFamily from '@/resources/fonts/IconFamily.woff' -import LogoLight from '@/resources/icons/betterseqta-light-icon.png' -import LogoLightOutline from '@/resources/icons/betterseqta-light-outline.png' -import icon48 from '@/resources/icons/icon-48.png?base64' -import assessmentsicon from '@/seqta/icons/assessmentsIcon' -import coursesicon from '@/seqta/icons/coursesIcon' -import kofi from '@/resources/kofi.png' +import IconFamily from "@/resources/fonts/IconFamily.woff" +import LogoLight from "@/resources/icons/betterseqta-light-icon.png" +import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png" +import icon48 from "@/resources/icons/icon-48.png?base64" +import assessmentsicon from "@/seqta/icons/assessmentsIcon" +import coursesicon from "@/seqta/icons/coursesIcon" +import kofi from "@/resources/kofi.png" // Stylesheets -import iframeCSS from '@/css/iframe.scss?raw' -import injectedCSS from '@/css/injected.scss?inline' -import documentLoadCSS from '@/css/documentload.scss?inline' -import renderSvelte from '@/interface/main' -import Settings from '@/interface/pages/settings.svelte' -import { settingsPopup } from './interface/hooks/SettingsPopup' +import iframeCSS from "@/css/iframe.scss?raw" +import injectedCSS from "@/css/injected.scss?inline" +import documentLoadCSS from "@/css/documentload.scss?inline" +import renderSvelte from "@/interface/main" +import Settings from "@/interface/pages/settings.svelte" +import { settingsPopup } from "./interface/hooks/SettingsPopup" let SettingsClicked = false export let MenuOptionsOpen = false @@ -52,19 +55,22 @@ let hasSEQTAText = false // This check is placed outside of the document load event due to issues with EP (https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/84) if (document.childNodes[1]) { - hasSEQTAText = document.childNodes[1].textContent?.includes('Copyright (c) SEQTA Software') ?? false + hasSEQTAText = + document.childNodes[1].textContent?.includes( + "Copyright (c) SEQTA Software", + ) ?? false init() } async function init() { CheckForMenuList() - const hasSEQTATitle = document.title.includes('SEQTA Learn') + const hasSEQTATitle = document.title.includes("SEQTA Learn") if (hasSEQTAText && hasSEQTATitle && !IsSEQTAPage) { IsSEQTAPage = true - console.info('[BetterSEQTA+] Verified SEQTA Page') - - const documentLoadStyle = document.createElement('style') + console.info("[BetterSEQTA+] Verified SEQTA Page") + + const documentLoadStyle = document.createElement("style") documentLoadStyle.textContent = documentLoadCSS document.head.appendChild(documentLoadStyle) @@ -73,26 +79,28 @@ async function init() { try { // wait until settingsState has been loaded from storage - await initializeSettingsState(); - + await initializeSettingsState() + if (settingsState.onoff) { - injectMainScript(); + injectMainScript() enableCurrentTheme() - if (typeof settingsState.assessmentsAverage == 'undefined') { + if (typeof settingsState.assessmentsAverage == "undefined") { settingsState.assessmentsAverage = true } // TEMP FIX for bug! -> this is a hack to get the injected.css file to have HMR in development mode as this import system is currently broken with crxjs - if (import.meta.env.MODE === 'development') { - import('./css/injected.scss') + if (import.meta.env.MODE === "development") { + import("./css/injected.scss") } else { - const injectedStyle = document.createElement('style') + const injectedStyle = document.createElement("style") injectedStyle.textContent = injectedCSS document.head.appendChild(injectedStyle) - } + } } - console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.') + console.info( + "[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.", + ) main() } catch (error: any) { console.error(error) @@ -109,21 +117,24 @@ export function enableAnimatedBackground() { CreateBackground() } else { RemoveBackground() - document.getElementById('container')!.style.background = 'var(--background-secondary)' + document.getElementById("container")!.style.background = + "var(--background-secondary)" } } async function HideMenuItems(): Promise { try { - let stylesheetInnerText: string = '' - for (const [menuItem, { toggle }] of Object.entries(settingsState.menuitems)) { + let stylesheetInnerText: string = "" + for (const [menuItem, { toggle }] of Object.entries( + settingsState.menuitems, + )) { if (!toggle) { stylesheetInnerText += SetDisplayNone(menuItem) console.info(`[BetterSEQTA+] Hiding ${menuItem} menu item`) } } - const menuItemStyle: HTMLStyleElement = document.createElement('style') + const menuItemStyle: HTMLStyleElement = document.createElement("style") menuItemStyle.innerText = stylesheetInnerText document.head.appendChild(menuItemStyle) } catch (error) { @@ -132,41 +143,43 @@ async function HideMenuItems(): Promise { } export function OpenWhatsNewPopup() { - const background = document.createElement('div') - background.id = 'whatsnewbk' - background.classList.add('whatsnewBackground') + const background = document.createElement("div") + background.id = "whatsnewbk" + background.classList.add("whatsnewBackground") - const container = document.createElement('div') - container.classList.add('whatsnewContainer') + const container = document.createElement("div") + container.classList.add("whatsnewContainer") var header: any = stringToHTML( - /* html */ + /* html */ `

What's New

BetterSEQTA+ V${browser.runtime.getManifest().version}

-
` + `, ).firstChild - let imagecont = document.createElement('div') - imagecont.classList.add('whatsnewImgContainer') + let imagecont = document.createElement("div") + imagecont.classList.add("whatsnewImgContainer") - let video = document.createElement('video') - let source = document.createElement('source') + let video = document.createElement("video") + let source = document.createElement("source") - source.setAttribute('src', 'https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.mp4') + source.setAttribute( + "src", + "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.mp4", + ) video.autoplay = true video.muted = true video.loop = true video.appendChild(source) - video.classList.add('whatsnewImg') + video.classList.add("whatsnewImg") imagecont.appendChild(video) - let textcontainer = document.createElement('div') - textcontainer.classList.add('whatsnewTextContainer') + let textcontainer = document.createElement("div") + textcontainer.classList.add("whatsnewTextContainer") - let text = stringToHTML( - /* html */ ` -
+ let text = stringToHTML(/* html */ ` +

3.4.5 - News, Bug Fixes, and improvements!

  • Added alternative news sources
  • Notifications now open direct messages
  • @@ -191,7 +204,7 @@ export function OpenWhatsNewPopup() {
  • Fixed theme application in the creator
  • Performance improvements
  • Other minor bug fixes
  • - +

    3.4.3 - Minor Bug Fixes

  • Fixed a bug where timetable colours couldn't be changed
  • Other minor bug fixes
  • @@ -208,7 +221,7 @@ export function OpenWhatsNewPopup() {
  • Improved animation performance
  • Better Animations!
  • Minor style tweaks
  • - +

    3.4.0 - Major Performance Update

  • Completely rebuilt the extension popup using Svelte for dramatically improved performance
  • Added a brand new background store with search functionality and downloadable backgrounds
  • @@ -293,12 +306,12 @@ export function OpenWhatsNewPopup() {
  • Added HEAPS of animations
  • Fixed a bug where shortcuts don't show up
  • Other minor bugs fixed
  • - +

    3.1.1 - Minor Bug fixes

  • Fixed assessments overlapping
  • Fixed houses not displaying if they aren't a specific color
  • Fixed Chrome Webstore Link
  • - +

    3.1.0 - Design Improvements

  • Minor UI improvements
  • Added Animation Speed Slider
  • @@ -316,7 +329,7 @@ export function OpenWhatsNewPopup() {
  • Fixed BetterSEQTA+ not loading on some pages
  • Fixed text colour of notices being unreadable
  • Fixed pages not reloading when saving changes
  • - +

    2.0.2 - Minor bug fixes

  • Fixed indicator for current lesson
  • Fixed text colour for DM messages list in Light mode
  • @@ -324,10 +337,10 @@ export function OpenWhatsNewPopup() {

    Sleek New Layout

  • Updated with a new font and presentation, BetterSEQTA+ has never looked better.
  • - +

    New Updated Sidebar

  • Condensed appearance with new updated icons.
  • - +

    Independent Light Mode and Dark Mode

  • Dark mode and Light mode are now available to pick alongside your chosen Theme Colour. Your Theme Colour will now become an accent colour for the page. Light/Dark mode can be toggled with the new button, found in the top-right of the menu bar.
  • @@ -335,18 +348,12 @@ export function OpenWhatsNewPopup() {

    Create Custom Shortcuts

  • Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.
  • - `, - ).firstChild + `).firstChild - const kofi_url = browser.runtime.getURL(kofi) - - console.log(kofi_url) - - let footer = stringToHTML( - /* html */ ` + let footer = stringToHTML(/* html */ `
    - Report bugs and feedback: + Report bugs and feedback: @@ -366,14 +373,14 @@ export function OpenWhatsNewPopup() {
    `).firstChild - let exitbutton = document.createElement('div') - exitbutton.id = 'whatsnewclosebutton' + let exitbutton = document.createElement("div") + exitbutton.id = "whatsnewclosebutton" container.append(header) container.append(imagecont) @@ -384,109 +391,106 @@ export function OpenWhatsNewPopup() { background.append(container) - document.getElementById('container')!.append(background) + document.getElementById("container")!.append(background) - let bkelement = document.getElementById('whatsnewbk') - let popup = document.getElementsByClassName('whatsnewContainer')[0] + let bkelement = document.getElementById("whatsnewbk") + let popup = document.getElementsByClassName("whatsnewContainer")[0] if (settingsState.animations) { animate( [popup, bkelement as HTMLElement], { scale: [0, 1] }, { - type: 'spring', + type: "spring", stiffness: 220, - damping: 18 - } + damping: 18, + }, ) - + animate( - '.whatsnewTextContainer *', + ".whatsnewTextContainer *", { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.05, { startDelay: 0.1 }), duration: 0.5, - ease: [.22, .03, .26, 1] - } + ease: [0.22, 0.03, 0.26, 1], + }, ) } delete settingsState.justupdated - bkelement!.addEventListener('click', function (event) { + bkelement!.addEventListener("click", function (event) { // Check if the click event originated from the element itself and not any of its children if (event.target === bkelement) { DeleteWhatsNew() } - }); + }) - var closeelement = document.getElementById('whatsnewclosebutton') - closeelement!.addEventListener('click', function () { + var closeelement = document.getElementById("whatsnewclosebutton") + closeelement!.addEventListener("click", function () { DeleteWhatsNew() }) } function injectMainScript() { - const mainScript = document.createElement('script') + const mainScript = document.createElement("script") mainScript.src = browser.runtime.getURL(pageState) document.head.appendChild(mainScript) } export function hideSideBar() { - const sidebar = document.getElementById('menu') // The sidebar element to be closed - const main = document.getElementById('main') // The main content element that must be resized to fill the page + const sidebar = document.getElementById("menu") // The sidebar element to be closed + const main = document.getElementById("main") // The main content element that must be resized to fill the page const currentMenuWidth = window.getComputedStyle(sidebar!).width // Get the styles of the different elements const currentContentPosition = window.getComputedStyle(main!).position - if (currentMenuWidth != "0") { // Actually modify it to collapse the sidebar - sidebar!.style.width = "0"; + if (currentMenuWidth != "0") { + // Actually modify it to collapse the sidebar + sidebar!.style.width = "0" } else { - sidebar!.style.width = "100%"; + sidebar!.style.width = "100%" } if (currentContentPosition != "relative") { - main!.style.position = 'relative'; + main!.style.position = "relative" } else { - main!.style.position = 'absolute'; + main!.style.position = "absolute" } - } export function OpenAboutPage() { - const background = document.createElement('div') - background.id = 'whatsnewbk' - background.classList.add('whatsnewBackground') + const background = document.createElement("div") + background.id = "whatsnewbk" + background.classList.add("whatsnewBackground") - const container = document.createElement('div') - container.classList.add('whatsnewContainer') + const container = document.createElement("div") + container.classList.add("whatsnewContainer") var header: any = stringToHTML( - /* html */ + /* html */ `

    About

    BetterSEQTA+ V${browser.runtime.getManifest().version}

    -
    ` +
    `, ).firstChild - let text = stringToHTML( - /* html */ ` + let text = stringToHTML(/* html */ `
    - +

    BetterSEQTA+ is a fork of BetterSEQTA which was originally developed by Nulkem, which was discontinued. BetterSEQTA+ continued development of BetterSEQTA, while incorporating a plethora of features.

    We are currently working on fixing bugs and adding good features. If you want to make a feature request or report a bug, you can do so on GitHub (find icon below).

    Credits

    Nulkem created the original extension, was ported to Manifest V3 by MEGA-Dawg68, and is under active development by Crazypersonalph and SethBurkart123.

    - `, - ).firstChild + `).firstChild - let footer = stringToHTML( - /* html */ ` + let footer = stringToHTML(/* html */ `
    - Report bugs and feedback: + Report bugs and feedback: @@ -506,8 +510,8 @@ export function OpenAboutPage() {
    `).firstChild - let exitbutton = document.createElement('div') - exitbutton.id = 'whatsnewclosebutton' + let exitbutton = document.createElement("div") + exitbutton.id = "whatsnewclosebutton" container.append(header) container.append(text as ChildNode) @@ -516,68 +520,68 @@ export function OpenAboutPage() { background.append(container) - document.getElementById('container')!.append(background) + document.getElementById("container")!.append(background) - let bkelement = document.getElementById('whatsnewbk') - let popup = document.getElementsByClassName('whatsnewContainer')[0] + let bkelement = document.getElementById("whatsnewbk") + let popup = document.getElementsByClassName("whatsnewContainer")[0] if (settingsState.animations) { animate( [popup, bkelement as HTMLElement], { scale: [0, 1] }, { - type: 'spring', + type: "spring", stiffness: 220, - damping: 18 - } + damping: 18, + }, ) - + animate( - '.whatsnewTextContainer *', + ".whatsnewTextContainer *", { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.05, { startDelay: 0.1 }), duration: 0.5, - ease: [.22, .03, .26, 1] - } + ease: [0.22, 0.03, 0.26, 1], + }, ) } delete settingsState.justupdated - bkelement!.addEventListener('click', function (event) { + bkelement!.addEventListener("click", function (event) { // Check if the click event originated from the element itself and not any of its children if (event.target === bkelement) { DeleteWhatsNew() } - }); + }) - var closeelement = document.getElementById('whatsnewclosebutton') - closeelement!.addEventListener('click', function () { + var closeelement = document.getElementById("whatsnewclosebutton") + closeelement!.addEventListener("click", function () { DeleteWhatsNew() }) } export async function finishLoad() { try { - document.querySelector('.legacy-root')?.classList.remove('hidden'); - - const loadingbk = document.getElementById('loading'); - loadingbk?.classList.add('closeLoading'); - await delay(501); - loadingbk?.remove(); + document.querySelector(".legacy-root")?.classList.remove("hidden") + + const loadingbk = document.getElementById("loading") + loadingbk?.classList.add("closeLoading") + await delay(501) + loadingbk?.remove() } catch (err) { - console.error("Error during loading cleanup:", err); + console.error("Error during loading cleanup:", err) } - if (settingsState.justupdated && !document.getElementById('whatsnewbk')) { - OpenWhatsNewPopup(); + if (settingsState.justupdated && !document.getElementById("whatsnewbk")) { + OpenWhatsNewPopup() } } async function DeleteWhatsNew() { - const bkelement = document.getElementById('whatsnewbk') - const popup = document.getElementsByClassName('whatsnewContainer')[0] + const bkelement = document.getElementById("whatsnewbk") + const popup = document.getElementsByClassName("whatsnewContainer")[0] if (!settingsState.animations) { bkelement?.remove() @@ -587,40 +591,40 @@ async function DeleteWhatsNew() { animate( [popup, bkelement!], { opacity: [1, 0], scale: [1, 0] }, - { ease: [.22, .03, .26, 1] } + { ease: [0.22, 0.03, 0.26, 1] }, ).then(() => { bkelement?.remove() - }); + }) } export function CreateBackground() { - var bkCheck = document.getElementsByClassName('bg') + var bkCheck = document.getElementsByClassName("bg") if (bkCheck.length !== 0) { return } // Creating and inserting 3 divs containing the background applied to the pages - var bklocation = document.getElementById('container') - var menu = document.getElementById('menu') - var bk = document.createElement('div') - bk.classList.add('bg') + var bklocation = document.getElementById("container") + var menu = document.getElementById("menu") + var bk = document.createElement("div") + bk.classList.add("bg") bklocation!.insertBefore(bk, menu) - var bk2 = document.createElement('div') - bk2.classList.add('bg') - bk2.classList.add('bg2') + var bk2 = document.createElement("div") + bk2.classList.add("bg") + bk2.classList.add("bg2") bklocation!.insertBefore(bk2, menu) - var bk3 = document.createElement('div') - bk3.classList.add('bg') - bk3.classList.add('bg3') + var bk3 = document.createElement("div") + bk3.classList.add("bg") + bk3.classList.add("bg3") bklocation!.insertBefore(bk3, menu) } export function RemoveBackground() { - var bk = document.getElementsByClassName('bg') - var bk2 = document.getElementsByClassName('bg2') - var bk3 = document.getElementsByClassName('bg3') + var bk = document.getElementsByClassName("bg") + var bk2 = document.getElementsByClassName("bg2") + var bk3 = document.getElementsByClassName("bg3") if (bk.length == 0 || bk2.length == 0 || bk3.length == 0) return bk[0].remove() @@ -628,522 +632,592 @@ export function RemoveBackground() { bk3[0].remove() } -export async function waitForElm(selector: string, usePolling: boolean = false, interval: number = 100): Promise { +export async function waitForElm( + selector: string, + usePolling: boolean = false, + interval: number = 100, +): Promise { if (usePolling) { return new Promise((resolve) => { const checkForElement = () => { - const element = document.querySelector(selector); + const element = document.querySelector(selector) if (element) { - resolve(element); + resolve(element) } else { - setTimeout(checkForElement, interval); + setTimeout(checkForElement, interval) } - }; - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', checkForElement); - } else { - checkForElement(); } - }); + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", checkForElement) + } else { + checkForElement() + } + }) } else { return new Promise((resolve) => { const registerObserver = () => { - const { unregister } = eventManager.register(`${selector}`, { - customCheck: (element) => element.matches(selector) - }, async (element) => { - resolve(element); - await delay(1); - unregister(); // Remove the listener once the element is found - }); - return unregister; - }; - - let unregister = null; - - if (document.readyState === 'loading') { - // DOM is still loading, wait for it to be ready - document.addEventListener('DOMContentLoaded', () => { - unregister = registerObserver(); - }); - } else { - unregister = registerObserver(); + const { unregister } = eventManager.register( + `${selector}`, + { + customCheck: (element) => element.matches(selector), + }, + async (element) => { + resolve(element) + await delay(1) + unregister() // Remove the listener once the element is found + }, + ) + return unregister } - const querySelector = () => document.querySelector(selector); - const element = querySelector(); + let unregister = null + + if (document.readyState === "loading") { + // DOM is still loading, wait for it to be ready + document.addEventListener("DOMContentLoaded", () => { + unregister = registerObserver() + }) + } else { + unregister = registerObserver() + } + + const querySelector = () => document.querySelector(selector) + const element = querySelector() if (element) { - if (unregister) unregister(); - resolve(element); - return; + if (unregister) unregister() + resolve(element) + return } - - }); + }) } } export function GetCSSElement(file: string) { const cssFile = browser.runtime.getURL(file) - const fileref = document.createElement('link') - fileref.setAttribute('rel', 'stylesheet') - fileref.setAttribute('type', 'text/css') - fileref.setAttribute('href', cssFile) + const fileref = document.createElement("link") + fileref.setAttribute("rel", "stylesheet") + fileref.setAttribute("type", "text/css") + fileref.setAttribute("href", cssFile) return fileref } -function removeThemeTagsFromNotices () { +function removeThemeTagsFromNotices() { // Grabs an array of the notice iFrames - const userHTMLArray = document.getElementsByClassName('userHTML') + const userHTMLArray = document.getElementsByClassName("userHTML") // Iterates through the array, applying the iFrame css for (const item of userHTMLArray) { // Grabs the HTML of the body tag const item1 = item as HTMLIFrameElement - const body = item1.contentWindow!.document.querySelectorAll('body')[0] + const body = item1.contentWindow!.document.querySelectorAll("body")[0] if (body) { - // Replaces the theme tag with nothing + // Replaces the theme tag with nothing const bodyText = body.innerHTML - body.innerHTML = bodyText.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, '').replace(/ +/, ' ') + body.innerHTML = bodyText + .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "") + .replace(/ +/, " ") } } } async function updateIframesWithDarkMode(): Promise { - const cssLink = document.createElement('style'); - cssLink.classList.add('iframecss'); - const cssContent = document.createTextNode(iframeCSS); - cssLink.appendChild(cssContent); + const cssLink = document.createElement("style") + cssLink.classList.add("iframecss") + const cssContent = document.createTextNode(iframeCSS) + cssLink.appendChild(cssContent) - eventManager.register('iframeAdded', { - elementType: 'iframe', - customCheck: (element: Element) => !element.classList.contains('iframecss'), - }, (element) => { - const iframe = element as HTMLIFrameElement; - try { - applyDarkModeToIframe(iframe, cssLink); + eventManager.register( + "iframeAdded", + { + elementType: "iframe", + customCheck: (element: Element) => + !element.classList.contains("iframecss"), + }, + (element) => { + const iframe = element as HTMLIFrameElement + try { + applyDarkModeToIframe(iframe, cssLink) - if (element.classList.contains('cke_wysiwyg_frame')) { - (async () => { - await delay(100); - iframe.contentDocument?.body.setAttribute('spellcheck', 'true'); - })(); + if (element.classList.contains("cke_wysiwyg_frame")) { + ;(async () => { + await delay(100) + iframe.contentDocument?.body.setAttribute("spellcheck", "true") + })() + } + } catch (error) { + console.error("Error applying dark mode:", error) } - } catch (error) { - console.error('Error applying dark mode:', error); - } - }); + }, + ) } -function applyDarkModeToIframe(iframe: HTMLIFrameElement, cssLink: HTMLStyleElement): void { - const iframeDocument = iframe.contentDocument; - if (!iframeDocument) return; +function applyDarkModeToIframe( + iframe: HTMLIFrameElement, + cssLink: HTMLStyleElement, +): void { + const iframeDocument = iframe.contentDocument + if (!iframeDocument) return iframe.onload = () => { - applyDarkModeToIframe(iframe, cssLink); - }; - - if (settingsState.DarkMode) { - iframeDocument.documentElement.classList.add('dark') + applyDarkModeToIframe(iframe, cssLink) } - const head = iframeDocument.head; - if (head && !head.innerHTML.includes('iframecss')) { - head.innerHTML += cssLink.outerHTML; + if (settingsState.DarkMode) { + iframeDocument.documentElement.classList.add("dark") + } + + const head = iframeDocument.head + if (head && !head.innerHTML.includes("iframecss")) { + head.innerHTML += cssLink.outerHTML } } function SortMessagePageItems(messagesParentElement: any) { - let filterbutton = document.createElement('div') - filterbutton.classList.add('messages-filterbutton') - filterbutton.innerText = 'Filter' + let filterbutton = document.createElement("div") + filterbutton.classList.add("messages-filterbutton") + filterbutton.innerText = "Filter" let header = document.getElementsByClassName( - 'MessageList__MessageList___3DxoC', + "MessageList__MessageList___3DxoC", )[0].firstChild as HTMLElement header.append(filterbutton) messagesParentElement } async function LoadPageElements(): Promise { - await AddBetterSEQTAElements(); - const sublink: string | undefined = window.location.href.split('/')[4]; - - eventManager.register('messagesAdded', { - elementType: 'div', - className: 'messages', - }, handleMessages); + await AddBetterSEQTAElements() + const sublink: string | undefined = window.location.href.split("/")[4] - eventManager.register('noticesAdded', { - elementType: 'div', - className: 'notices', - }, CheckNoticeTextColour); + eventManager.register( + "messagesAdded", + { + elementType: "div", + className: "messages", + }, + handleMessages, + ) - eventManager.register('dashboardAdded', { - elementType: 'div', - className: 'dashboard', - }, handleDashboard); + eventManager.register( + "noticesAdded", + { + elementType: "div", + className: "notices", + }, + CheckNoticeTextColour, + ) - eventManager.register('documentsAdded', { - elementType: 'div', - className: 'documents', - }, handleDocuments); + eventManager.register( + "dashboardAdded", + { + elementType: "div", + className: "dashboard", + }, + handleDashboard, + ) - eventManager.register('reportsAdded', { - elementType: 'div', - className: 'reports', - }, handleReports); + eventManager.register( + "documentsAdded", + { + elementType: "div", + className: "documents", + }, + handleDocuments, + ) - eventManager.register('timetableAdded', { - elementType: 'div', - className: 'timetablepage', - }, handleTimetable); + eventManager.register( + "reportsAdded", + { + elementType: "div", + className: "reports", + }, + handleReports, + ) - eventManager.register('noticesAdded', { - elementType: 'div', - className: 'notice', - }, handleNotices); + eventManager.register( + "timetableAdded", + { + elementType: "div", + className: "timetablepage", + }, + handleTimetable, + ) + eventManager.register( + "noticesAdded", + { + elementType: "div", + className: "notice", + }, + handleNotices, + ) if (settingsState.assessmentsAverage) { - eventManager.register('assessmentsAdded', { - elementType: 'div', - className: 'assessmentsWrapper', - }, handleAssessments); + eventManager.register( + "assessmentsAdded", + { + elementType: "div", + className: "assessmentsWrapper", + }, + handleAssessments, + ) } - RegisterClickListeners(); + RegisterClickListeners() - await handleSublink(sublink); + await handleSublink(sublink) } function handleTimetableZoom(): void { - console.log('Initializing timetable zoom controls'); - + console.log("Initializing timetable zoom controls") + // Lazy initialize state variables only when function is first called - let timetableZoomLevel = 1; - let baseContainerHeight: number | null = null; - const originalEntryPositions = new Map(); - + let timetableZoomLevel = 1 + let baseContainerHeight: number | null = null + const originalEntryPositions = new Map< + Element, + { topRatio: number; heightRatio: number } + >() + // Create zoom controls - const zoomControls = document.createElement('div'); - zoomControls.className = 'timetable-zoom-controls'; - - const zoomIn = document.createElement('button'); - zoomIn.className = 'uiButton timetable-zoom iconFamily'; - zoomIn.innerHTML = ''; // Using unicode for zoom in icon - - const zoomOut = document.createElement('button'); - zoomOut.className = 'uiButton timetable-zoom iconFamily'; - zoomOut.innerHTML = ''; // Using unicode for zoom out icon - + const zoomControls = document.createElement("div") + zoomControls.className = "timetable-zoom-controls" - zoomControls.appendChild(zoomOut); - zoomControls.appendChild(zoomIn); + const zoomIn = document.createElement("button") + zoomIn.className = "uiButton timetable-zoom iconFamily" + zoomIn.innerHTML = "" // Using unicode for zoom in icon - const toolbar = document.getElementById('toolbar'); - toolbar?.appendChild(zoomControls); + const zoomOut = document.createElement("button") + zoomOut.className = "uiButton timetable-zoom iconFamily" + zoomOut.innerHTML = "" // Using unicode for zoom out icon + + zoomControls.appendChild(zoomOut) + zoomControls.appendChild(zoomIn) + + const toolbar = document.getElementById("toolbar") + toolbar?.appendChild(zoomControls) const initializePositions = () => { // Get the base container height from the first TD - const firstDayColumn = document.querySelector('.dailycal .content .days td') as HTMLElement; - if (!firstDayColumn) return false; - - baseContainerHeight = parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight; + const firstDayColumn = document.querySelector( + ".dailycal .content .days td", + ) as HTMLElement + if (!firstDayColumn) return false + + baseContainerHeight = + parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight // Store original ratios - const entries = document.querySelectorAll('.entriesWrapper .entry'); + const entries = document.querySelectorAll(".entriesWrapper .entry") entries.forEach((entry: Element) => { - const entryEl = entry as HTMLElement; - - // Calculate ratios relative to detected base height - if (baseContainerHeight === null) return; - const topRatio = parseInt(entryEl.style.top) / baseContainerHeight; - const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight; - - originalEntryPositions.set(entry, { topRatio, heightRatio }); - }); + const entryEl = entry as HTMLElement - return true; - }; + // Calculate ratios relative to detected base height + if (baseContainerHeight === null) return + const topRatio = parseInt(entryEl.style.top) / baseContainerHeight + const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight + + originalEntryPositions.set(entry, { topRatio, heightRatio }) + }) + + return true + } const updateZoom = () => { // Initialize positions if not already done if (baseContainerHeight === null && !initializePositions()) { - console.error('Failed to initialize positions'); - return; + console.error("Failed to initialize positions") + return } - console.debug(`Updating zoom level to: ${timetableZoomLevel}`); - + console.debug(`Updating zoom level to: ${timetableZoomLevel}`) + // Calculate new container height - if (baseContainerHeight === null) return; - const newContainerHeight = baseContainerHeight * timetableZoomLevel; + if (baseContainerHeight === null) return + const newContainerHeight = baseContainerHeight * timetableZoomLevel // Update all day columns (TDs) - const dayColumns = document.querySelectorAll('.dailycal .content .days td'); + const dayColumns = document.querySelectorAll(".dailycal .content .days td") dayColumns.forEach((td: Element) => { - (td as HTMLElement).style.height = `${newContainerHeight}px`; - }); + ;(td as HTMLElement).style.height = `${newContainerHeight}px` + }) // Update all entries using stored ratios - const entries = document.querySelectorAll('.entriesWrapper .entry'); + const entries = document.querySelectorAll(".entriesWrapper .entry") entries.forEach((entry: Element) => { - const entryEl = entry as HTMLElement; - const originalRatios = originalEntryPositions.get(entry); - + const entryEl = entry as HTMLElement + const originalRatios = originalEntryPositions.get(entry) + if (originalRatios) { // Calculate new positions from original ratios - const newTop = originalRatios.topRatio * newContainerHeight; - const newHeight = originalRatios.heightRatio * newContainerHeight; - + const newTop = originalRatios.topRatio * newContainerHeight + const newHeight = originalRatios.heightRatio * newContainerHeight + // Apply new values - entryEl.style.top = `${Math.round(newTop)}px`; - entryEl.style.height = `${Math.round(newHeight)}px`; + entryEl.style.top = `${Math.round(newTop)}px` + entryEl.style.height = `${Math.round(newHeight)}px` } - }); + }) // Update time column to match - const timeColumn = document.querySelector('.times'); + const timeColumn = document.querySelector(".times") if (timeColumn) { - const times = timeColumn.querySelectorAll('.time'); - const timeHeight = newContainerHeight / times.length; + const times = timeColumn.querySelectorAll(".time") + const timeHeight = newContainerHeight / times.length times.forEach((time: Element) => { - (time as HTMLElement).style.height = `${timeHeight}px`; - }); + ;(time as HTMLElement).style.height = `${timeHeight}px` + }) } - entries[Math.round((entries.length - 1) / 2)].scrollIntoView({ behavior: 'instant', block: 'center' }); - }; + entries[Math.round((entries.length - 1) / 2)].scrollIntoView({ + behavior: "instant", + block: "center", + }) + } - zoomIn.addEventListener('click', () => { + zoomIn.addEventListener("click", () => { if (timetableZoomLevel < 2) { - timetableZoomLevel += 0.2; - updateZoom(); + timetableZoomLevel += 0.2 + updateZoom() } - }); + }) - zoomOut.addEventListener('click', () => { + zoomOut.addEventListener("click", () => { if (timetableZoomLevel > 0.6) { - timetableZoomLevel -= 0.2; - updateZoom(); + timetableZoomLevel -= 0.2 + updateZoom() } - }); + }) } async function handleNotices(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; - if (!settingsState.animations) return; + if (!(node instanceof HTMLElement)) return + if (!settingsState.animations) return - node.style.opacity = '0'; + node.style.opacity = "0" // get index of node in relation to parent - const index = Array.from(node.parentElement!.children).indexOf(node); + const index = Array.from(node.parentElement!.children).indexOf(node) animate( node, { opacity: [0, 1], y: [50, 0], scale: [0.99, 1] }, { delay: 0.1 * index, - type: 'spring', + type: "spring", stiffness: 250, - damping: 20 - } - ); + damping: 20, + }, + ) } async function handleSublink(sublink: string | undefined): Promise { switch (sublink) { - case 'news': - await handleNewsPage(); - break; + case "news": + await handleNewsPage() + break case undefined: - window.location.replace(`${location.origin}/#?page=/${settingsState.defaultPage}`); - if (settingsState.defaultPage === 'home') loadHomePage() - if (settingsState.defaultPage === 'timetable') handleTimetable() - if (settingsState.defaultPage === 'documents') handleDocuments(document.querySelector('.documents')!) - if (settingsState.defaultPage === 'reports') handleReports(document.querySelector('.reports')!) - if (settingsState.defaultPage === 'messages') handleMessages(document.querySelector('.messages')!) + window.location.replace( + `${location.origin}/#?page=/${settingsState.defaultPage}`, + ) + if (settingsState.defaultPage === "home") loadHomePage() + if (settingsState.defaultPage === "timetable") handleTimetable() + if (settingsState.defaultPage === "documents") + handleDocuments(document.querySelector(".documents")!) + if (settingsState.defaultPage === "reports") + handleReports(document.querySelector(".reports")!) + if (settingsState.defaultPage === "messages") + handleMessages(document.querySelector(".messages")!) - finishLoad(); - break; - case 'home': - window.location.replace(`${location.origin}/#?page=/home`); - console.info('[BetterSEQTA+] Started Init') + finishLoad() + break + case "home": + window.location.replace(`${location.origin}/#?page=/home`) + console.info("[BetterSEQTA+] Started Init") if (settingsState.onoff) loadHomePage() - finishLoad(); - break; - + finishLoad() + break + default: await handleDefault() - break; - } + break + } } async function handleTimetable(): Promise { - await waitForElm('.time', true, 10); + await waitForElm(".time", true, 10) // Store original heights when timetable loads - const lessons = document.querySelectorAll('.dailycal .lesson'); + const lessons = document.querySelectorAll(".dailycal .lesson") lessons.forEach((lesson: Element) => { - const lessonEl = lesson as HTMLElement; - lessonEl.setAttribute('data-original-height', lessonEl.offsetHeight.toString()); - }); + const lessonEl = lesson as HTMLElement + lessonEl.setAttribute( + "data-original-height", + lessonEl.offsetHeight.toString(), + ) + }) // Existing time format code - if (settingsState.timeFormat == '12') { - const times = document.querySelectorAll('.timetablepage .times .time'); + if (settingsState.timeFormat == "12") { + const times = document.querySelectorAll(".timetablepage .times .time") for (const time of times) { - if (!time.textContent) continue; - time.textContent = convertTo12HourFormat(time.textContent, true); + if (!time.textContent) continue + time.textContent = convertTo12HourFormat(time.textContent, true) } } - handleTimetableZoom(); + handleTimetableZoom() } async function handleNewsPage(): Promise { - console.info('[BetterSEQTA+] Started Init'); + console.info("[BetterSEQTA+] Started Init") if (settingsState.onoff) { - SendNewsPage(); + SendNewsPage() if (settingsState.notificationcollector) { - enableNotificationCollector(); + enableNotificationCollector() } - finishLoad(); + finishLoad() } } async function handleDefault(): Promise { - finishLoad(); + finishLoad() if (settingsState.notificationcollector) { - enableNotificationCollector(); + enableNotificationCollector() } } async function handleMessages(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; + if (!(node instanceof HTMLElement)) return - const element = document.getElementById('title')!.firstChild as HTMLElement; - element.innerText = 'Direct Messages'; - document.title = 'Direct Messages ― SEQTA Learn'; - SortMessagePageItems(node); + const element = document.getElementById("title")!.firstChild as HTMLElement + element.innerText = "Direct Messages" + document.title = "Direct Messages ― SEQTA Learn" + SortMessagePageItems(node) - if (!settingsState.animations) return; + if (!settingsState.animations) return // Hides messages on page load - const style = document.createElement('style') - style.classList.add('messageHider') - style.innerHTML = '[data-message]{opacity: 0 !important;}' - document.head.append(style) + const style = document.createElement("style") + style.classList.add("messageHider") + style.innerHTML = "[data-message]{opacity: 0 !important;}" + document.head.append(style) - await waitForElm('[data-message]', true, 10); - const messages = Array.from(document.querySelectorAll('[data-message]')).slice(0, 35); + await waitForElm("[data-message]", true, 10) + const messages = Array.from( + document.querySelectorAll("[data-message]"), + ).slice(0, 35) animate( messages, { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.03), duration: 0.5, - ease: [.22, .03, .26, 1] - } - ); + ease: [0.22, 0.03, 0.26, 1], + }, + ) - document.head.querySelector('style.messageHider')?.remove() + document.head.querySelector("style.messageHider")?.remove() } async function handleDashboard(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; - if (!settingsState.animations) return; + if (!(node instanceof HTMLElement)) return + if (!settingsState.animations) return - const style = document.createElement('style') - style.classList.add('dashboardHider') - style.innerHTML = '.dashboard{opacity: 0 !important;}' + const style = document.createElement("style") + style.classList.add("dashboardHider") + style.innerHTML = ".dashboard{opacity: 0 !important;}" document.head.append(style) - await waitForElm('.dashlet', true, 10); + await waitForElm(".dashlet", true, 10) animate( - '.dashboard > *', + ".dashboard > *", { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.1), duration: 0.5, - ease: [.22, .03, .26, 1] - } - ); + ease: [0.22, 0.03, 0.26, 1], + }, + ) - document.head.querySelector('style.dashboardHider')?.remove() + document.head.querySelector("style.dashboardHider")?.remove() } async function handleDocuments(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; - if (!settingsState.animations) return; + if (!(node instanceof HTMLElement)) return + if (!settingsState.animations) return - await waitForElm('.document', true, 10); + await waitForElm(".document", true, 10) animate( - '.documents tbody tr.document', + ".documents tbody tr.document", { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.05), duration: 0.5, - ease: [.22, .03, .26, 1] - } - ); + ease: [0.22, 0.03, 0.26, 1], + }, + ) } async function handleReports(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; - if (!settingsState.animations) return; + if (!(node instanceof HTMLElement)) return + if (!settingsState.animations) return - await waitForElm('.report', true, 10); + await waitForElm(".report", true, 10) animate( - '.reports .item', + ".reports .item", { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.05, { startDelay: 0.2 }), duration: 0.5, - ease: [.22, .03, .26, 1] - } - ); + ease: [0.22, 0.03, 0.26, 1], + }, + ) } function CheckNoticeTextColour(notice: any) { - eventManager.register('noticeAdded', { - elementType: 'div', - className: 'notice', - parentElement: notice - }, (node) => { - var hex = (node as HTMLElement).style.cssText.split(' ')[1]; - if (hex) { - const hex1 = hex.slice(0,-1); - var threshold = GetThresholdOfColor(hex1); - if (settingsState.DarkMode && threshold < 100) { - (node as HTMLElement).style.cssText = '--color: undefined;'; + eventManager.register( + "noticeAdded", + { + elementType: "div", + className: "notice", + parentElement: notice, + }, + (node) => { + var hex = (node as HTMLElement).style.cssText.split(" ")[1] + if (hex) { + const hex1 = hex.slice(0, -1) + var threshold = GetThresholdOfColor(hex1) + if (settingsState.DarkMode && threshold < 100) { + ;(node as HTMLElement).style.cssText = "--color: undefined;" + } } - } - }); + }, + ) } export function tryLoad() { - waitForElm('.login').then(() => { + waitForElm(".login").then(() => { finishLoad() }) - waitForElm('.day-container').then(() => { + waitForElm(".day-container").then(() => { finishLoad() }) - waitForElm('[data-key=welcome]').then((elm: any) => { - elm.classList.remove('active') + waitForElm("[data-key=welcome]").then((elm: any) => { + elm.classList.remove("active") }) - waitForElm('.code', true, 50).then((elm: any) => { - if (!elm.innerText.includes('BetterSEQTA')) LoadPageElements() + waitForElm(".code", true, 50).then((elm: any) => { + if (!elm.innerText.includes("BetterSEQTA")) LoadPageElements() }) updateIframesWithDarkMode() // Waits for page to call on load, run scripts document.addEventListener( - 'load', + "load", function () { removeThemeTagsFromNotices() }, @@ -1154,7 +1228,7 @@ export function tryLoad() { function ChangeMenuItemPositions(storage: any) { let menuorder = storage - var menuList = document.querySelector('#menu')!.firstChild!.childNodes + var menuList = document.querySelector("#menu")!.firstChild!.childNodes let listorder = [] for (let i = 0; i < menuList.length; i++) { @@ -1170,12 +1244,12 @@ function ChangeMenuItemPositions(storage: any) { newArr[listorder[i]] = menuList[i] } - let listItemsDOM = document.getElementById('menu')!.firstChild + let listItemsDOM = document.getElementById("menu")!.firstChild for (let i = 0; i < newArr.length; i++) { const element = newArr[i] if (element) { const elem = element as HTMLElement - elem.setAttribute('data-checked', 'true') + elem.setAttribute("data-checked", "true") listItemsDOM!.appendChild(element) } } @@ -1188,62 +1262,71 @@ function ReplaceMenuSVG(element: HTMLElement, svg: string) { item.innerHTML = `${item.innerHTML}` let newsvg = stringToHTML(svg).firstChild - item.insertBefore((newsvg as Node), item.firstChild) + item.insertBefore(newsvg as Node, item.firstChild) } export async function ObserveMenuItemPosition() { - await waitForElm('#menu > ul > li') + await waitForElm("#menu > ul > li") await delay(100) - eventManager.register('menuList', { - parentElement: document.querySelector('#menu')!.firstChild as Element, - }, (element: Element) => { - const node = element as HTMLElement; - if (!node?.dataset?.checked && !MenuOptionsOpen) { - const key = MenuitemSVGKey[node?.dataset?.key! as keyof typeof MenuitemSVGKey]; - if (key) { - ReplaceMenuSVG( - node, - MenuitemSVGKey[node.dataset.key as keyof typeof MenuitemSVGKey], - ); - } else if (node?.firstChild?.nodeName === 'LABEL') { - const label = node.firstChild as HTMLElement; - let textNode = label.lastChild as HTMLElement; + eventManager.register( + "menuList", + { + parentElement: document.querySelector("#menu")!.firstChild as Element, + }, + (element: Element) => { + const node = element as HTMLElement + if (!node?.dataset?.checked && !MenuOptionsOpen) { + const key = + MenuitemSVGKey[node?.dataset?.key! as keyof typeof MenuitemSVGKey] + if (key) { + ReplaceMenuSVG( + node, + MenuitemSVGKey[node.dataset.key as keyof typeof MenuitemSVGKey], + ) + } else if (node?.firstChild?.nodeName === "LABEL") { + const label = node.firstChild as HTMLElement + let textNode = label.lastChild as HTMLElement - if (textNode.nodeType === 3 && textNode.parentNode && textNode.parentNode.nodeName !== 'SPAN') { - const span = document.createElement('span'); - span.textContent = textNode.nodeValue; + if ( + textNode.nodeType === 3 && + textNode.parentNode && + textNode.parentNode.nodeName !== "SPAN" + ) { + const span = document.createElement("span") + span.textContent = textNode.nodeValue - label.replaceChild(span, textNode); + label.replaceChild(span, textNode) + } } + ChangeMenuItemPositions(settingsState.menuorder) } - ChangeMenuItemPositions(settingsState.menuorder); - } - }); + }, + ) } export function showConflictPopup() { - if (document.getElementById('conflict-popup')) return; - document.body.classList.remove('hidden'); + if (document.getElementById("conflict-popup")) return + document.body.classList.remove("hidden") - const background = document.createElement('div'); - background.id = 'conflict-popup'; - background.classList.add('whatsnewBackground'); - background.style.zIndex = '10000000'; + const background = document.createElement("div") + background.id = "conflict-popup" + background.classList.add("whatsnewBackground") + background.style.zIndex = "10000000" - const container = document.createElement('div'); - container.classList.add('whatsnewContainer'); - container.style.height = 'auto'; + const container = document.createElement("div") + container.classList.add("whatsnewContainer") + container.style.height = "auto" - const headerHTML = /* html */` + const headerHTML = /* html */ `

    Extension Conflict Detected

    Legacy BetterSEQTA Installed

    - `; - const header = stringToHTML(headerHTML).firstChild; + ` + const header = stringToHTML(headerHTML).firstChild - const textHTML = /* html */` + const textHTML = /* html */ `

    It appears that you have the legacy BetterSEQTA extension installed alongside BetterSEQTA+. @@ -1253,56 +1336,53 @@ export function showConflictPopup() { Please remove the older BetterSEQTA extension to ensure that BetterSEQTA+ works correctly.

    - `; - const text = stringToHTML(textHTML).firstChild; + ` + const text = stringToHTML(textHTML).firstChild - const exitButton = document.createElement('div'); - exitButton.id = 'whatsnewclosebutton'; + const exitButton = document.createElement("div") + exitButton.id = "whatsnewclosebutton" - if (header) container.append(header); - if (text) container.append(text); - container.append(exitButton); + if (header) container.append(header) + if (text) container.append(text) + container.append(exitButton) - background.append(container); + background.append(container) - document.getElementById('container')?.append(background); + document.getElementById("container")?.append(background) - if (settingsState.animations) { - animate( - [background as HTMLElement], - { opacity: [0, 1] } - ); + if (settingsState.animations) { + animate([background as HTMLElement], { opacity: [0, 1] }) } - background.addEventListener('click', (event) => { + background.addEventListener("click", (event) => { if (event.target === background) { - background.remove(); + background.remove() } - }); + }) - exitButton.addEventListener('click', () => { - background.remove(); - }); + exitButton.addEventListener("click", () => { + background.remove() + }) } function main() { - if (typeof settingsState.onoff === 'undefined') { - browser.runtime.sendMessage({ type: 'setDefaultStorage' }) + if (typeof settingsState.onoff === "undefined") { + browser.runtime.sendMessage({ type: "setDefaultStorage" }) } const handleDisabled = () => { - waitForElm('.code', true, 50).then(AppendElementsToDisabledPage) + waitForElm(".code", true, 50).then(AppendElementsToDisabledPage) } if (settingsState.onoff) { - console.info('[BetterSEQTA+] Enabled') - if (settingsState.DarkMode) document.documentElement.classList.add('dark') + console.info("[BetterSEQTA+] Enabled") + if (settingsState.DarkMode) document.documentElement.classList.add("dark") - document.querySelector('.legacy-root')?.classList.add('hidden') + document.querySelector(".legacy-root")?.classList.add("hidden") - new StorageChangeHandler(); + new StorageChangeHandler() new MessageHandler() - + updateAllColors() loading() InjectCustomIcons() @@ -1310,23 +1390,25 @@ function main() { tryLoad() setTimeout(() => { - const legacyElement = document.querySelector('.outside-container .bottom-container'); + const legacyElement = document.querySelector( + ".outside-container .bottom-container", + ) if (legacyElement) { - console.log('Legacy extension detected'); - showConflictPopup(); + console.log("Legacy extension detected") + showConflictPopup() } - }, 1000); + }, 1000) } else { handleDisabled() - window.addEventListener('load', handleDisabled) + window.addEventListener("load", handleDisabled) } } function InjectCustomIcons() { - console.info('[BetterSEQTA+] Injecting Icons') + console.info("[BetterSEQTA+] Injecting Icons") - const style = document.createElement('style') - style.setAttribute('type', 'text/css') + const style = document.createElement("style") + style.setAttribute("type", "text/css") style.innerHTML = ` @font-face { font-family: 'IconFamily'; @@ -1341,8 +1423,8 @@ export function AppendElementsToDisabledPage() { console.info("[BetterSEQTA+] Appending elements to disabled page") AddBetterSEQTAElements() - let settingsStyle = document.createElement('style') - settingsStyle.innerHTML = /* css */` + let settingsStyle = document.createElement("style") + settingsStyle.innerHTML = /* css */ ` .addedButton { position: absolute !important; right: 50px; @@ -1371,61 +1453,64 @@ export function AppendElementsToDisabledPage() { } export const closeExtensionPopup = (extensionPopup?: HTMLElement) => { - if (!extensionPopup) extensionPopup = document.getElementById('ExtensionPopup')! + if (!extensionPopup) + extensionPopup = document.getElementById("ExtensionPopup")! - extensionPopup.classList.add('hide') + extensionPopup.classList.add("hide") if (settingsState.animations) { animate(1, 0, { onUpdate: (progress) => { extensionPopup.style.opacity = Math.max(0, progress).toString() extensionPopup.style.transform = `scale(${Math.max(0, progress)})` }, - type: 'spring', + type: "spring", stiffness: 520, - damping: 20 - }); + damping: 20, + }) } else { - extensionPopup.style.opacity = '0' - extensionPopup.style.transform = 'scale(0)' + extensionPopup.style.opacity = "0" + extensionPopup.style.transform = "scale(0)" } - + settingsPopup.triggerClose() SettingsClicked = false } export function addExtensionSettings() { - const extensionPopup = document.createElement('div') - extensionPopup.classList.add('outside-container', 'hide') - extensionPopup.id = 'ExtensionPopup' - - const extensionContainer = document.querySelector('#container') as HTMLDivElement + const extensionPopup = document.createElement("div") + extensionPopup.classList.add("outside-container", "hide") + extensionPopup.id = "ExtensionPopup" + + const extensionContainer = document.querySelector( + "#container", + ) as HTMLDivElement if (extensionContainer) extensionContainer.appendChild(extensionPopup) // create shadow dom and render svelte app try { - const shadow = extensionPopup.attachShadow({ mode: 'open' }); - requestIdleCallback(() => renderSvelte(Settings, shadow)); + const shadow = extensionPopup.attachShadow({ mode: "open" }) + requestIdleCallback(() => renderSvelte(Settings, shadow)) } catch (err) { console.error(err) } - const container = document.getElementById('container') + const container = document.getElementById("container") - new SettingsResizer(); + new SettingsResizer() container!.onclick = (event) => { - if (!SettingsClicked) return; + if (!SettingsClicked) return - if (!(event.target as HTMLElement).closest('#AddedSettings')) { - if (event.target == extensionPopup) return; + if (!(event.target as HTMLElement).closest("#AddedSettings")) { + if (event.target == extensionPopup) return closeExtensionPopup() } } } export function OpenMenuOptions() { - var container = document.getElementById('container') - var menu = document.getElementById('menu') + var container = document.getElementById("container") + var menu = document.getElementById("menu") if (settingsState.defaultmenuorder.length == 0) { let childnodes = menu!.firstChild!.childNodes @@ -1440,7 +1525,11 @@ export function OpenMenuOptions() { if (settingsState.defaultmenuorder.length != childnodes.length) { for (let i = 0; i < childnodes.length; i++) { const element = childnodes[i] - if (!settingsState.defaultmenuorder.indexOf((element as HTMLElement).dataset.key)) { + if ( + !settingsState.defaultmenuorder.indexOf( + (element as HTMLElement).dataset.key, + ) + ) { let newdefaultmenuorder = settingsState.defaultmenuorder newdefaultmenuorder.push((element as HTMLElement).dataset.key) settingsState.defaultmenuorder = newdefaultmenuorder @@ -1450,24 +1539,24 @@ export function OpenMenuOptions() { MenuOptionsOpen = true - var cover = document.createElement('div') - cover.classList.add('notMenuCover') - menu!.style.zIndex = '20' - menu!.style.setProperty('--menuHidden', 'flex') + var cover = document.createElement("div") + cover.classList.add("notMenuCover") + menu!.style.zIndex = "20" + menu!.style.setProperty("--menuHidden", "flex") container!.append(cover) - var menusettings = document.createElement('div') - menusettings.classList.add('editmenuoption-container') + var menusettings = document.createElement("div") + menusettings.classList.add("editmenuoption-container") - var defaultbutton = document.createElement('div') - defaultbutton.classList.add('editmenuoption') - defaultbutton.innerText = 'Restore Default' - defaultbutton.id = 'restoredefaultoption' + var defaultbutton = document.createElement("div") + defaultbutton.classList.add("editmenuoption") + defaultbutton.innerText = "Restore Default" + defaultbutton.id = "restoredefaultoption" - var savebutton = document.createElement('div') - savebutton.classList.add('editmenuoption') - savebutton.innerText = 'Save' - savebutton.id = 'restoredefaultoption' + var savebutton = document.createElement("div") + savebutton.classList.add("editmenuoption") + savebutton.innerText = "Save" + savebutton.id = "restoredefaultoption" menusettings.appendChild(defaultbutton) menusettings.appendChild(savebutton) @@ -1479,19 +1568,19 @@ export function OpenMenuOptions() { const element1 = ListItems[i] const element = element1 as HTMLElement - (element as HTMLElement).classList.add('draggable'); - if ((element as HTMLElement).classList.contains('hasChildren')) { - (element as HTMLElement).classList.remove('active'); - (element.firstChild as HTMLElement).classList.remove('noscroll'); + ;(element as HTMLElement).classList.add("draggable") + if ((element as HTMLElement).classList.contains("hasChildren")) { + ;(element as HTMLElement).classList.remove("active") + ;(element.firstChild as HTMLElement).classList.remove("noscroll") } let MenuItemToggle = stringToHTML( - `
    ` - ).firstChild; - (element as HTMLElement).append(MenuItemToggle!) + `
    `, + ).firstChild + ;(element as HTMLElement).append(MenuItemToggle!) if (!element.dataset.betterseqta) { - const a = document.createElement('section') + const a = document.createElement("section") a.innerHTML = element.innerHTML cloneAttributes(a, element) menu!.firstChild!.insertBefore(a, element) @@ -1505,59 +1594,60 @@ export function OpenMenuOptions() { for (var i = 0; i < menubuttons.length; i++) { var id = (menubuttons[i] as HTMLElement).dataset.key const element: any = {} - element.toggle = true; - (menuItems[id as keyof typeof menuItems] as any) = element; + element.toggle = true + ;(menuItems[id as keyof typeof menuItems] as any) = element } settingsState.menuitems = menuItems } - var menubuttons: any = document.getElementsByClassName('menuitem') + var menubuttons: any = document.getElementsByClassName("menuitem") let menuItems = settingsState.menuitems as any - let buttons = document.getElementsByClassName('menuitem') + let buttons = document.getElementsByClassName("menuitem") for (let i = 0; i < buttons.length; i++) { let id = buttons[i].id as string | undefined if (menuItems[id as keyof typeof menuItems]) { - (buttons[i] as HTMLInputElement).checked = menuItems[id as keyof typeof menuItems].toggle + ;(buttons[i] as HTMLInputElement).checked = + menuItems[id as keyof typeof menuItems].toggle } else { - (buttons[i] as HTMLInputElement).checked = true + ;(buttons[i] as HTMLInputElement).checked = true } - (buttons[i] as HTMLInputElement).checked = true + ;(buttons[i] as HTMLInputElement).checked = true } try { - var el = document.querySelector('#menu > ul') - var sortable = Sortable.create((el as HTMLElement), { - draggable: '.draggable', - dataIdAttr: 'data-key', + var el = document.querySelector("#menu > ul") + var sortable = Sortable.create(el as HTMLElement, { + draggable: ".draggable", + dataIdAttr: "data-key", animation: 150, easing: "cubic-bezier(.5,0,.5,1)", - onEnd: function() { + onEnd: function () { saveNewOrder(sortable) }, - }); + }) } catch (err) { console.error(err) } function changeDisplayProperty(element: any) { if (!element.checked) { - element.parentNode.parentNode.style.display = 'var(--menuHidden)' + element.parentNode.parentNode.style.display = "var(--menuHidden)" } if (element.checked) { element.parentNode.parentNode.style.setProperty( - 'display', - 'flex', - 'important', + "display", + "flex", + "important", ) } } function StoreMenuSettings() { - let menu = document.getElementById('menu') + let menu = document.getElementById("menu") const menuItems: any = {} let menubuttons = menu!.firstChild!.childNodes - const button = document.getElementsByClassName('menuitem') + const button = document.getElementsByClassName("menuitem") for (let i = 0; i < menubuttons.length; i++) { const id = (menubuttons[i] as HTMLElement).dataset.key const element: any = {} @@ -1570,8 +1660,8 @@ export function OpenMenuOptions() { for (let i = 0; i < menubuttons.length; i++) { const element = menubuttons[i] - element.addEventListener('change', () => { - element.parentElement.parentElement.getAttribute('data-key') + element.addEventListener("change", () => { + element.parentElement.parentElement.getAttribute("data-key") StoreMenuSettings() changeDisplayProperty(element) }) @@ -1581,17 +1671,16 @@ export function OpenMenuOptions() { menusettings?.remove() cover?.remove() MenuOptionsOpen = false - menu!.style.setProperty('--menuHidden', 'none') + menu!.style.setProperty("--menuHidden", "none") for (let i = 0; i < ListItems.length; i++) { const element1 = ListItems[i] const element = element1 as HTMLElement - element.classList.remove('draggable') - element.setAttribute('draggable', 'false') - + element.classList.remove("draggable") + element.setAttribute("draggable", "false") if (!element.dataset.betterseqta) { - const a = document.createElement('li') + const a = document.createElement("li") a.innerHTML = element.innerHTML cloneAttributes(a, element) menu!.firstChild!.insertBefore(a, element) @@ -1599,16 +1688,16 @@ export function OpenMenuOptions() { } } - let switches = menu!.querySelectorAll('.onoffswitch') + let switches = menu!.querySelectorAll(".onoffswitch") for (let i = 0; i < switches.length; i++) { switches[i].remove() } } - cover?.addEventListener('click', closeAll) - savebutton?.addEventListener('click', closeAll) + cover?.addEventListener("click", closeAll) + savebutton?.addEventListener("click", closeAll) - defaultbutton?.addEventListener('click', function() { + defaultbutton?.addEventListener("click", function () { const options = settingsState.defaultmenuorder settingsState.menuorder = options @@ -1618,9 +1707,9 @@ export function OpenMenuOptions() { const element = menubuttons[i] element.checked = true element.parentNode.parentNode.style.setProperty( - 'display', - 'flex', - 'important', + "display", + "flex", + "important", ) } saveNewOrder(sortable) @@ -1633,18 +1722,18 @@ function saveNewOrder(sortable: any) { } function cloneAttributes(target: any, source: any) { - [...source.attributes].forEach((attr) => { + ;[...source.attributes].forEach((attr) => { target.setAttribute(attr.nodeName, attr.nodeValue) }) } export function setupSettingsButton() { - var AddedSettings = document.getElementById('AddedSettings'); - var extensionPopup = document.getElementById('ExtensionPopup'); + var AddedSettings = document.getElementById("AddedSettings") + var extensionPopup = document.getElementById("ExtensionPopup") - AddedSettings!.addEventListener('click', async () => { + AddedSettings!.addEventListener("click", async () => { if (SettingsClicked) { - closeExtensionPopup(extensionPopup as HTMLElement); + closeExtensionPopup(extensionPopup as HTMLElement) } else { if (settingsState.animations) { animate(0, 1, { @@ -1652,69 +1741,86 @@ export function setupSettingsButton() { extensionPopup!.style.opacity = progress.toString() extensionPopup!.style.transform = `scale(${progress})` }, - type: 'spring', + type: "spring", stiffness: 280, - damping: 20 - }); - + damping: 20, + }) } else { - extensionPopup!.style.opacity = '1' - extensionPopup!.style.transform = 'scale(1)' - extensionPopup!.style.transition = 'opacity 0s linear, transform 0s linear' + extensionPopup!.style.opacity = "1" + extensionPopup!.style.transform = "scale(1)" + extensionPopup!.style.transition = + "opacity 0s linear, transform 0s linear" } - extensionPopup!.classList.remove('hide'); - SettingsClicked = true; + extensionPopup!.classList.remove("hide") + SettingsClicked = true } - }); + }) } async function CheckCurrentLesson(lesson: any, num: number) { - const { from: startTime, until: endTime, code, description, room, staff } = lesson; - const currentDate = new Date(); + const { + from: startTime, + until: endTime, + code, + description, + room, + staff, + } = lesson + const currentDate = new Date() // Create Date objects for start and end times - const [startHour, startMinute] = startTime.split(':').map(Number); - const [endHour, endMinute] = endTime.split(':').map(Number); + const [startHour, startMinute] = startTime.split(":").map(Number) + const [endHour, endMinute] = endTime.split(":").map(Number) - const startDate = new Date(currentDate); - startDate.setHours(startHour, startMinute, 0); + const startDate = new Date(currentDate) + startDate.setHours(startHour, startMinute, 0) - const endDate = new Date(currentDate); - endDate.setHours(endHour, endMinute, 0); + const endDate = new Date(currentDate) + endDate.setHours(endHour, endMinute, 0) // Check if the current time is within the lesson time range - const isValidTime = startDate < currentDate && endDate > currentDate; + const isValidTime = startDate < currentDate && endDate > currentDate - const elementId = `${code}${num}`; - const element = document.getElementById(elementId); + const elementId = `${code}${num}` + const element = document.getElementById(elementId) if (!element) { - clearInterval(LessonInterval); - return; + clearInterval(LessonInterval) + return } - const isCurrentDate = currentSelectedDate.toLocaleDateString('en-au') === currentDate.toLocaleDateString('en-au'); + const isCurrentDate = + currentSelectedDate.toLocaleDateString("en-au") === + currentDate.toLocaleDateString("en-au") if (isCurrentDate) { if (isValidTime) { - element.classList.add('activelesson'); + element.classList.add("activelesson") } else { - element.classList.remove('activelesson'); + element.classList.remove("activelesson") } } - const minutesUntilStart = Math.floor((startDate.getTime() - currentDate.getTime()) / 60000); + const minutesUntilStart = Math.floor( + (startDate.getTime() - currentDate.getTime()) / 60000, + ) - if (minutesUntilStart !== 5 || settingsState.lessonalert || !window.Notification) return; + if ( + minutesUntilStart !== 5 || + settingsState.lessonalert || + !window.Notification + ) + return - if (Notification.permission !== 'granted') await Notification.requestPermission(); + if (Notification.permission !== "granted") + await Notification.requestPermission() try { - new Notification('Next Lesson in 5 Minutes:', { - body: `Subject: ${description}${room ? `\nRoom: ${room}` : ''}${staff ? `\nTeacher: ${staff}` : ''}`, - }); + new Notification("Next Lesson in 5 Minutes:", { + body: `Subject: ${description}${room ? `\nRoom: ${room}` : ""}${staff ? `\nTeacher: ${staff}` : ""}`, + }) } catch (error) { - console.error(error); + console.error(error) } } @@ -1724,7 +1830,7 @@ export function GetThresholdOfColor(color: any) { const rgbaRegex = /rgba?\(([^)]+)\)/gi // Check if the color string is a gradient (linear or radial) - if (color.includes('gradient')) { + if (color.includes("gradient")) { let gradientThresholds = [] // Find and replace all instances of RGBA in the gradient @@ -1732,20 +1838,23 @@ export function GetThresholdOfColor(color: any) { while ((match = rgbaRegex.exec(color)) !== null) { // Extract the individual components (r, g, b, a) const rgbaString = match[1] - const [r, g, b] = rgbaString.split(',').map(str => str.trim()) + const [r, g, b] = rgbaString.split(",").map((str) => str.trim()) // Compute the threshold using your existing algorithm - const threshold = Math.sqrt(parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2) + const threshold = Math.sqrt( + parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2, + ) // Store the computed threshold gradientThresholds.push(threshold) } // Calculate the average threshold - const averageThreshold = gradientThresholds.reduce((acc, val) => acc + val, 0) / gradientThresholds.length - - return averageThreshold + const averageThreshold = + gradientThresholds.reduce((acc, val) => acc + val, 0) / + gradientThresholds.length + return averageThreshold } else { // Handle the color as a simple RGBA (or hex, or whatever the Color library supports) const rgb = Color.rgb(color).object() @@ -1766,30 +1875,44 @@ function CheckCurrentLessonAll(lessons: any) { } // Helper function to build the assessment URL -function buildAssessmentURL(programmeID: any, metaID: any, itemID = '') { - const base = '../#?page=/assessments/' - return itemID ? `${base}${programmeID}:${metaID}&item=${itemID}` : `${base}${programmeID}:${metaID}` +function buildAssessmentURL(programmeID: any, metaID: any, itemID = "") { + const base = "../#?page=/assessments/" + return itemID + ? `${base}${programmeID}:${metaID}&item=${itemID}` + : `${base}${programmeID}:${metaID}` } // Function to create a lesson div element from a lesson object function makeLessonDiv(lesson: any, num: number) { - if (!lesson) throw new Error('No lesson provided.') + if (!lesson) throw new Error("No lesson provided.") - const { code, colour, description, staff, room, from, until, attendanceTitle, programmeID, metaID, assessments } = lesson + const { + code, + colour, + description, + staff, + room, + from, + until, + attendanceTitle, + programmeID, + metaID, + assessments, + } = lesson // Construct the base lesson string with default values using ternary operators - let lessonString = /* html */` + let lessonString = /* html */ `
    -

    ${description || 'Unknown'}

    -

    ${staff || 'Unknown'}

    -

    ${room || 'Unknown'}

    -

    ${from || 'Unknown'} - ${until || 'Unknown'}

    -
    ${attendanceTitle || 'Unknown'}
    +

    ${description || "Unknown"}

    +

    ${staff || "Unknown"}

    +

    ${room || "Unknown"}

    +

    ${from || "Unknown"} - ${until || "Unknown"}

    +
    ${attendanceTitle || "Unknown"}
    ` // Add buttons for assessments and courses if applicable if (programmeID !== 0) { - lessonString += /* html */` + lessonString += /* html */ `
    ${assessmentsicon}
    ${coursesicon}
    ` @@ -1797,11 +1920,14 @@ function makeLessonDiv(lesson: any, num: number) { // Add assessments if they exist if (assessments && assessments.length > 0) { - const assessmentString = assessments.map((element: any) => - `

    ${element.title}

    ` - ).join('') + const assessmentString = assessments + .map( + (element: any) => + `

    ${element.title}

    `, + ) + .join("") - lessonString += /* html */` + lessonString += /* html */ `
    @@ -1811,7 +1937,7 @@ function makeLessonDiv(lesson: any, num: number) { ` } - lessonString += '
    ' + lessonString += "
    " return stringToHTML(lessonString) } @@ -1820,43 +1946,46 @@ function CheckUnmarkedAttendance(lessonattendance: any) { if (lessonattendance) { var lesson = lessonattendance.label } else { - lesson = ' ' + lesson = " " } return lesson } -function convertTo12HourFormat(time: string, noMinutes: boolean = false): string { - let [hours, minutes] = time.split(':').map(Number); - let period = 'AM'; +function convertTo12HourFormat( + time: string, + noMinutes: boolean = false, +): string { + let [hours, minutes] = time.split(":").map(Number) + let period = "AM" if (hours >= 12) { - period = 'PM'; - if (hours > 12) hours -= 12; + period = "PM" + if (hours > 12) hours -= 12 } else if (hours === 0) { - hours = 12; + hours = 12 } - let hoursStr = hours.toString(); - if (hoursStr.length === 2 && hoursStr.startsWith('0')) { - hoursStr = hoursStr.substring(1); + let hoursStr = hours.toString() + if (hoursStr.length === 2 && hoursStr.startsWith("0")) { + hoursStr = hoursStr.substring(1) } - return `${hoursStr}${noMinutes ? '' : `:${minutes.toString().padStart(2, '0')}`} ${period}`; + return `${hoursStr}${noMinutes ? "" : `:${minutes.toString().padStart(2, "0")}`} ${period}` } function callHomeTimetable(date: string, change?: any) { // Creates a HTTP Post Request to the SEQTA page for the students timetable var xhr = new XMLHttpRequest() - xhr.open('POST', `${location.origin}/seqta/student/load/timetable?`, true) + xhr.open("POST", `${location.origin}/seqta/student/load/timetable?`, true) // Sets the response type to json - xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8') + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") xhr.onreadystatechange = function () { // Once the response is ready if (xhr.readyState === 4) { var serverResponse = JSON.parse(xhr.response) let lessonArray: Array = [] - const DayContainer = document.getElementById('day-container')! + const DayContainer = document.getElementById("day-container")! // If items in response: if (serverResponse.payload.items.length > 0) { if (DayContainer.innerText || change) { @@ -1877,7 +2006,7 @@ function callHomeTimetable(date: string, change?: any) { (element: any) => element.name === subjectname, ) if (!subject) { - lessonArray[i].colour = '--item-colour: #8e8e8e;' + lessonArray[i].colour = "--item-colour: #8e8e8e;" } else { lessonArray[i].colour = `--item-colour: ${subject.value};` let result = GetThresholdOfColor(subject.value) @@ -1890,9 +2019,11 @@ function callHomeTimetable(date: string, change?: any) { lessonArray[i].from = lessonArray[i].from.substring(0, 5) lessonArray[i].until = lessonArray[i].until.substring(0, 5) - if (settingsState.timeFormat === '12') { + if (settingsState.timeFormat === "12") { lessonArray[i].from = convertTo12HourFormat(lessonArray[i].from) - lessonArray[i].until = convertTo12HourFormat(lessonArray[i].until) + lessonArray[i].until = convertTo12HourFormat( + lessonArray[i].until, + ) } // Checks if attendance is unmarked, and sets the string to " ". @@ -1901,13 +2032,13 @@ function callHomeTimetable(date: string, change?: any) { ) } // If on home page, apply each lesson to HTML with information in each div - DayContainer.innerText = '' + DayContainer.innerText = "" for (let i = 0; i < lessonArray.length; i++) { var div = makeLessonDiv(lessonArray[i], i + 1) // Append each of the lessons into the day-container if (lessonArray[i].invert) { const div1 = div.firstChild! as HTMLElement - div1.classList.add('day-inverted') + div1.classList.add("day-inverted") } DayContainer.append(div.firstChild as HTMLElement) @@ -1924,13 +2055,13 @@ function callHomeTimetable(date: string, change?: any) { }) } } else { - DayContainer.innerHTML = '' - var dummyDay = document.createElement('div') - dummyDay.classList.add('day-empty') - let img = document.createElement('img') + DayContainer.innerHTML = "" + var dummyDay = document.createElement("div") + dummyDay.classList.add("day-empty") + let img = document.createElement("img") img.src = browser.runtime.getURL(LogoLight) - let text = document.createElement('p') - text.innerText = 'No lessons available.' + let text = document.createElement("p") + text.innerText = "No lessons available." dummyDay.append(img) dummyDay.append(text) DayContainer.append(dummyDay) @@ -1949,13 +2080,16 @@ function callHomeTimetable(date: string, change?: any) { } async function GetUpcomingAssessments() { - let func = fetch(`${location.origin}/seqta/student/assessment/list/upcoming?`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=utf-8', + let func = fetch( + `${location.origin}/seqta/student/assessment/list/upcoming?`, + { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ student: 69 }), }, - body: JSON.stringify({ student: 69 }), - }) + ) return func .then((result) => result.json()) @@ -1964,11 +2098,14 @@ async function GetUpcomingAssessments() { async function GetActiveClasses() { try { - const response = await fetch(`${location.origin}/seqta/student/load/subjects?`, { - method: 'POST', - headers: { 'Content-Type': 'application/json; charset=utf-8' }, - body: JSON.stringify({}) - }) + const response = await fetch( + `${location.origin}/seqta/student/load/subjects?`, + { + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({}), + }, + ) if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`) @@ -1977,7 +2114,7 @@ async function GetActiveClasses() { const data = await response.json() return data.payload } catch (error) { - console.error('Oops! There was a problem fetching active classes:', error) + console.error("Oops! There was a problem fetching active classes:", error) } } @@ -1991,7 +2128,14 @@ function comparedate(obj1: any, obj2: any) { return 0 } -function CreateElement(type: string, class_?: any, id?: any, innerText?: string, innerHTML?: string, style?: string) { +function CreateElement( + type: string, + class_?: any, + id?: any, + innerText?: string, + innerHTML?: string, + style?: string, +) { let element = document.createElement(type) if (class_ !== undefined) { element.classList.add(class_) @@ -2012,43 +2156,47 @@ function CreateElement(type: string, class_?: any, id?: any, innerText?: string, } function createAssessmentDateDiv(date: string, value: any, datecase?: any) { - var options = { weekday: 'long' as 'long', month: 'long' as 'long', day: 'numeric' as 'numeric' } + var options = { + weekday: "long" as "long", + month: "long" as "long", + day: "numeric" as "numeric", + } const FormattedDate = new Date(date) const assessments = value.assessments const container = value.div - let DateTitleDiv = document.createElement('div') - DateTitleDiv.classList.add('upcoming-date-title') + let DateTitleDiv = document.createElement("div") + DateTitleDiv.classList.add("upcoming-date-title") if (datecase) { - let datetitle = document.createElement('h5') - datetitle.classList.add('upcoming-special-day') + let datetitle = document.createElement("h5") + datetitle.classList.add("upcoming-special-day") datetitle.innerText = datecase DateTitleDiv.append(datetitle) - container.setAttribute('data-day', datecase) + container.setAttribute("data-day", datecase) } - let DateTitle = document.createElement('h5') - DateTitle.innerText = FormattedDate.toLocaleDateString('en-AU', options) + let DateTitle = document.createElement("h5") + DateTitle.innerText = FormattedDate.toLocaleDateString("en-AU", options) DateTitleDiv.append(DateTitle) container.append(DateTitleDiv) - let assessmentContainer = document.createElement('div') - assessmentContainer.classList.add('upcoming-date-assessments') + let assessmentContainer = document.createElement("div") + assessmentContainer.classList.add("upcoming-date-assessments") for (let i = 0; i < assessments.length; i++) { const element = assessments[i] - let item = document.createElement('div') - item.classList.add('upcoming-assessment') - item.setAttribute('data-subject', element.code) + let item = document.createElement("div") + item.classList.add("upcoming-assessment") + item.setAttribute("data-subject", element.code) item.id = `assessment${element.id}` item.style.cssText = element.colour - let titlediv = document.createElement('div') - titlediv.classList.add('upcoming-subject-title') + let titlediv = document.createElement("div") + titlediv.classList.add("upcoming-subject-title") let titlesvg = stringToHTML(` @@ -2056,15 +2204,15 @@ function createAssessmentDateDiv(date: string, value: any, datecase?: any) { `).firstChild titlediv.append(titlesvg!) - let detailsdiv = document.createElement('div') - detailsdiv.classList.add('upcoming-details') - let detailstitle = document.createElement('h5') + let detailsdiv = document.createElement("div") + detailsdiv.classList.add("upcoming-details") + let detailstitle = document.createElement("h5") detailstitle.innerText = `${element.subject} assessment` - let subject = document.createElement('p') + let subject = document.createElement("p") subject.innerText = element.title - subject.classList.add('upcoming-assessment-title') + subject.classList.add("upcoming-assessment-title") subject.onclick = function () { - document.querySelector('#menu ul')!.classList.add('noscroll'); + document.querySelector("#menu ul")!.classList.add("noscroll") location.href = `../#?page=/assessments/${element.programmeID}:${element.metaclassID}&item=${element.id}` } detailsdiv.append(detailstitle) @@ -2075,9 +2223,9 @@ function createAssessmentDateDiv(date: string, value: any, datecase?: any) { assessmentContainer.append(item) fetch(`${location.origin}/seqta/student/assessment/submissions/get`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json; charset=utf-8', + "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ assessment: element.id, @@ -2093,9 +2241,9 @@ function createAssessmentDateDiv(date: string, value: any, datecase?: any) { // ticksvg = stringToHTML(``).firstChild // ticksvg.classList.add('upcoming-tick') // assessment.append(ticksvg) - let submittedtext = document.createElement('div') - submittedtext.classList.add('upcoming-submittedtext') - submittedtext.innerText = 'Submitted' + let submittedtext = document.createElement("div") + submittedtext.classList.add("upcoming-submittedtext") + submittedtext.innerText = "Submitted" assessment!.append(submittedtext) } }) @@ -2112,40 +2260,44 @@ function CheckSpecialDay(date1: Date, date2: Date) { date1.getMonth() === date2.getMonth() && date1.getDate() - 1 === date2.getDate() ) { - return 'Yesterday' + return "Yesterday" } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ) { - return 'Today' + return "Today" } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() + 1 === date2.getDate() ) { - return 'Tomorrow' + return "Tomorrow" } } -function CreateSubjectFilter(subjectcode: any, itemcolour: string, checked: any) { - let label = CreateElement('label', 'upcoming-checkbox-container') +function CreateSubjectFilter( + subjectcode: any, + itemcolour: string, + checked: any, +) { + let label = CreateElement("label", "upcoming-checkbox-container") label.innerText = subjectcode - let input1 = CreateElement('input') + let input1 = CreateElement("input") const input = input1 as HTMLInputElement - input.type = 'checkbox' + input.type = "checkbox" input.checked = checked input.id = `filter-${subjectcode}` label.style.cssText = itemcolour - let span = CreateElement('span', 'upcoming-checkmark') + let span = CreateElement("span", "upcoming-checkmark") label.append(input) label.append(span) - input.addEventListener('change', function (change) { + input.addEventListener("change", function (change) { let filters = settingsState.subjectfilters - let id = (change.target as HTMLInputElement)!.id.split('-')[1] + let id = (change.target as HTMLInputElement)!.id.split("-")[1] filters[id] = (change.target as HTMLInputElement)!.checked settingsState.subjectfilters = filters @@ -2157,7 +2309,7 @@ function CreateSubjectFilter(subjectcode: any, itemcolour: string, checked: any) function CreateFilters(subjects: any) { let filteroptions = settingsState.subjectfilters - let filterdiv = document.querySelector('#upcoming-filters') + let filterdiv = document.querySelector("#upcoming-filters") for (let i = 0; i < subjects.length; i++) { const element = subjects[i] // eslint-disable-next-line @@ -2176,7 +2328,7 @@ function CreateFilters(subjects: any) { } async function CreateUpcomingSection(assessments: any, activeSubjects: any) { - let upcomingitemcontainer = document.querySelector('#upcoming-items') + let upcomingitemcontainer = document.querySelector("#upcoming-items") let overdueDates = [] let upcomingDates = {} @@ -2201,27 +2353,27 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { TomorrowDate.setDate(TomorrowDate.getDate() + 1) const colours = await GetLessonColours() - + let subjects = colours for (let i = 0; i < assessments.length; i++) { let subjectname = `timetable.subject.colour.${assessments[i].code}` let subject = subjects.find((element: any) => element.name === subjectname) - + if (!subject) { - assessments[i].colour = '--item-colour: #8e8e8e;' + assessments[i].colour = "--item-colour: #8e8e8e;" } else { assessments[i].colour = `--item-colour: ${subject.value};` - GetThresholdOfColor(subject.value); // result (originally) result = GetThresholdOfColor + GetThresholdOfColor(subject.value) // result (originally) result = GetThresholdOfColor } } - + for (let i = 0; i < activeSubjects.length; i++) { const element = activeSubjects[i] let subjectname = `timetable.subject.colour.${element.code}` let colour = colours.find((element: any) => element.name === subjectname) if (!colour) { - element.colour = '--item-colour: #8e8e8e;' + element.colour = "--item-colour: #8e8e8e;" } else { element.colour = `--item-colour: ${colour.value};` let result = GetThresholdOfColor(colour.value) @@ -2232,7 +2384,7 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { } CreateFilters(activeSubjects) - + // @ts-ignore let type // @ts-ignore @@ -2245,23 +2397,28 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { dateObj.div = CreateElement( // TODO: not sure whats going on here? // eslint-disable-next-line - type = "div", + (type = "div"), // eslint-disable-next-line - class_ = "upcoming-date-container", + (class_ = "upcoming-date-container"), ) - dateObj.assessments = []; - - (upcomingDates[element.due as keyof typeof upcomingDates] as any) = dateObj + dateObj.assessments = [] + ;(upcomingDates[element.due as keyof typeof upcomingDates] as any) = + dateObj } - let assessmentDateDiv = upcomingDates[element.due as keyof typeof upcomingDates]; + let assessmentDateDiv = + upcomingDates[element.due as keyof typeof upcomingDates] if (assessmentDateDiv) { - (assessmentDateDiv as any).assessments.push(element) + ;(assessmentDateDiv as any).assessments.push(element) } } for (var date in upcomingDates) { - let assessmentdue = new Date((upcomingDates[date as keyof typeof upcomingDates] as any).assessments[0].due) + let assessmentdue = new Date( + ( + upcomingDates[date as keyof typeof upcomingDates] as any + ).assessments[0].due, + ) let specialcase = CheckSpecialDay(Today, assessmentdue) let assessmentDate @@ -2274,10 +2431,13 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { datecase, ) } else { - assessmentDate = createAssessmentDateDiv(date, upcomingDates[date as keyof typeof upcomingDates]) + assessmentDate = createAssessmentDateDiv( + date, + upcomingDates[date as keyof typeof upcomingDates], + ) } - if (specialcase === 'Yesterday') { + if (specialcase === "Yesterday") { upcomingitemcontainer!.insertBefore( assessmentDate, upcomingitemcontainer!.firstChild, @@ -2285,21 +2445,20 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { } else { upcomingitemcontainer!.append(assessmentDate) } - } FilterUpcomingAssessments(settingsState.subjectfilters) } function AddPlaceHolderToParent(parent: any, numberofassessments: any) { - let textcontainer = CreateElement('div', 'upcoming-blank') - let textblank = CreateElement('p', 'upcoming-hiddenassessment') - let s = '' + let textcontainer = CreateElement("div", "upcoming-blank") + let textblank = CreateElement("p", "upcoming-hiddenassessment") + let s = "" if (numberofassessments > 1) { - s = 's' + s = "s" } textblank.innerText = `${numberofassessments} hidden assessment${s} due` textcontainer.append(textblank) - textcontainer.setAttribute('data-hidden', 'true') + textcontainer.setAttribute("data-hidden", "true") parent.append(textcontainer) } @@ -2312,37 +2471,45 @@ export function FilterUpcomingAssessments(subjectoptions: any) { const element = subjectdivs[i] if (!subjectoptions[item]) { - element.classList.add('hidden') + element.classList.add("hidden") } if (subjectoptions[item]) { - element.classList.remove('hidden') + element.classList.remove("hidden") } - (element.parentNode! as HTMLElement).classList.remove('hidden') + ;(element.parentNode! as HTMLElement).classList.remove("hidden") let children = element.parentNode!.parentNode!.children for (let i = 0; i < children.length; i++) { const element = children[i] - if (element.hasAttribute('data-hidden')) { + if (element.hasAttribute("data-hidden")) { element.remove() } } if ( element.parentNode!.children.length == - element.parentNode!.querySelectorAll('.hidden').length + element.parentNode!.querySelectorAll(".hidden").length ) { - if (element.parentNode!.querySelectorAll('.hidden').length > 0) { - if (!(element.parentNode!.parentNode! as HTMLElement).hasAttribute('data-day')) { - (element.parentNode!.parentNode! as HTMLElement).classList.add('hidden') + if (element.parentNode!.querySelectorAll(".hidden").length > 0) { + if ( + !(element.parentNode!.parentNode! as HTMLElement).hasAttribute( + "data-day", + ) + ) { + ;(element.parentNode!.parentNode! as HTMLElement).classList.add( + "hidden", + ) } else { AddPlaceHolderToParent( element.parentNode!.parentNode, - element.parentNode!.querySelectorAll('.hidden').length, + element.parentNode!.querySelectorAll(".hidden").length, ) } } } else { - (element.parentNode!.parentNode! as HTMLElement).classList.remove('hidden') + ;(element.parentNode!.parentNode! as HTMLElement).classList.remove( + "hidden", + ) } } } @@ -2350,11 +2517,11 @@ export function FilterUpcomingAssessments(subjectoptions: any) { async function GetLessonColours() { let func = fetch(`${location.origin}/seqta/student/load/prefs?`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json; charset=utf-8', + "Content-Type": "application/json; charset=utf-8", }, - body: JSON.stringify({ request: 'userPrefs', asArray: true, user: 69 }), + body: JSON.stringify({ request: "userPrefs", asArray: true, user: 69 }), }) return func .then((result) => result.json()) @@ -2363,55 +2530,56 @@ async function GetLessonColours() { export function CreateCustomShortcutDiv(element: any) { // Creates the stucture and element information for each seperate shortcut - var shortcut = document.createElement('a') - shortcut.setAttribute('href', element.url) - shortcut.setAttribute('target', '_blank') - var shortcutdiv = document.createElement('div') - shortcutdiv.classList.add('shortcut') - shortcutdiv.classList.add('customshortcut') + var shortcut = document.createElement("a") + shortcut.setAttribute("href", element.url) + shortcut.setAttribute("target", "_blank") + var shortcutdiv = document.createElement("div") + shortcutdiv.classList.add("shortcut") + shortcutdiv.classList.add("customshortcut") let image = stringToHTML( ` - ${element.icon} `, - ).firstChild; - (image as HTMLElement).classList.add('shortcuticondiv') - var text = document.createElement('p') + ).firstChild + ;(image as HTMLElement).classList.add("shortcuticondiv") + var text = document.createElement("p") text.textContent = element.name shortcutdiv.append(image!) shortcutdiv.append(text) shortcut.append(shortcutdiv) - document.getElementById('shortcuts')!.append(shortcut) + document.getElementById("shortcuts")!.append(shortcut) } export function RemoveShortcutDiv(elements: any) { if (elements.length === 0) return - + elements.forEach((element: any) => { - const shortcuts = document.querySelectorAll('.shortcut') + const shortcuts = document.querySelectorAll(".shortcut") shortcuts.forEach((shortcut) => { - const anchorElement = shortcut.parentElement; // the
    element is the parent - const textElement = shortcut.querySelector('p'); //

    is a direct child of .shortcut - const title = textElement ? textElement.textContent : '' + const anchorElement = shortcut.parentElement // the element is the parent + const textElement = shortcut.querySelector("p") //

    is a direct child of .shortcut + const title = textElement ? textElement.textContent : "" let shouldRemove = title === element.name // Check href only if element.url exists if (element.url) { - shouldRemove = shouldRemove && (anchorElement!.getAttribute('href') === element.url) + shouldRemove = + shouldRemove && anchorElement!.getAttribute("href") === element.url } if (shouldRemove) { @@ -2432,34 +2600,36 @@ async function AddCustomShortcutsToPage() { } export async function loadHomePage() { - console.info('[BetterSEQTA+] Started Loading Home Page') - + console.info("[BetterSEQTA+] Started Loading Home Page") + // Wait for the DOM to finish clearing await delay(10) - document.title = 'Home ― SEQTA Learn' - const element = document.querySelector('[data-key=home]') - element?.classList.add('active') - + document.title = "Home ― SEQTA Learn" + const element = document.querySelector("[data-key=home]") + element?.classList.add("active") + // Cache DOM queries - const main = document.getElementById('main') + const main = document.getElementById("main") if (!main) { - console.error('[BetterSEQTA+] Main element not found.') + console.error("[BetterSEQTA+] Main element not found.") return } // Create root container first - const homeRoot = stringToHTML(/* html */`

    `) - + const homeRoot = stringToHTML( + /* html */ `
    `, + ) + // Clear main and add home root - main.innerHTML = '' + main.innerHTML = "" main.appendChild(homeRoot?.firstChild!) // Get reference to home container for all subsequent additions - const homeContainer = document.getElementById('home-root') + const homeContainer = document.getElementById("home-root") if (!homeContainer) return - const skeletonStructure = stringToHTML(/* html */` + const skeletonStructure = stringToHTML(/* html */ `
    @@ -2503,15 +2673,15 @@ export async function loadHomePage() { // Run animations if enabled if (settingsState.animations) { animate( - '.home-container > div', + ".home-container > div", { opacity: [0, 1], y: [10, 0], scale: [0.99, 1] }, { delay: stagger(0.15, { startDelay: 0.1 }), - type: 'spring', + type: "spring", stiffness: 341, damping: 20, - mass: 1 - } + mass: 1, + }, ) } @@ -2521,9 +2691,8 @@ export async function loadHomePage() { // Initialize shortcuts immediately try { addShortcuts(settingsState.shortcuts) - } catch(err: any) { - console.error('[BetterSEQTA+] Error adding shortcuts:', - err.message || err) + } catch (err: any) { + console.error("[BetterSEQTA+] Error adding shortcuts:", err.message || err) } AddCustomShortcutsToPage() @@ -2532,74 +2701,75 @@ export async function loadHomePage() { const TodayFormatted = formatDate(date) // Start all data fetching in parallel - const [ - timetablePromise, - assessmentsPromise, - classesPromise, - prefsPromise, - ] = [ + const [timetablePromise, assessmentsPromise, classesPromise, prefsPromise] = [ // Timetable data fetch(`${location.origin}/seqta/student/load/timetable?`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ from: TodayFormatted, until: TodayFormatted, student: 69, - }) - }).then(res => res.json()), + }), + }).then((res) => res.json()), // Assessments data GetUpcomingAssessments(), - // Classes data + // Classes data GetActiveClasses(), // Preferences data fetch(`${location.origin}/seqta/student/load/prefs?`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ asArray: true, request: 'userPrefs' }) - }).then(res => res.json()) + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ asArray: true, request: "userPrefs" }), + }).then((res) => res.json()), ] // Process all data in parallel const [timetableData, assessments, classes, prefs] = await Promise.all([ timetablePromise, - assessmentsPromise, + assessmentsPromise, classesPromise, - prefsPromise + prefsPromise, ]) // Process timetable data - const dayContainer = document.getElementById('day-container') + const dayContainer = document.getElementById("day-container") if (dayContainer && timetableData.payload.items.length > 0) { - const lessonArray = timetableData.payload.items.sort((a: any, b: any) => a.from.localeCompare(b.from)) + const lessonArray = timetableData.payload.items.sort((a: any, b: any) => + a.from.localeCompare(b.from), + ) const colours = await GetLessonColours() - + // Process and display lessons - dayContainer.innerHTML = '' + dayContainer.innerHTML = "" for (let i = 0; i < lessonArray.length; i++) { const lesson = lessonArray[i] const subjectname = `timetable.subject.colour.${lesson.code}` - const subject = colours.find((element: any) => element.name === subjectname) - - lesson.colour = subject ? `--item-colour: ${subject.value};` : '--item-colour: #8e8e8e;' + const subject = colours.find( + (element: any) => element.name === subjectname, + ) + + lesson.colour = subject + ? `--item-colour: ${subject.value};` + : "--item-colour: #8e8e8e;" lesson.from = lesson.from.substring(0, 5) lesson.until = lesson.until.substring(0, 5) - if (settingsState.timeFormat === '12') { + if (settingsState.timeFormat === "12") { lesson.from = convertTo12HourFormat(lesson.from) lesson.until = convertTo12HourFormat(lesson.until) } lesson.attendanceTitle = CheckUnmarkedAttendance(lesson.attendance) - + const div = makeLessonDiv(lesson, i + 1) if (GetThresholdOfColor(subject?.value) > 300) { const firstChild = div.firstChild as HTMLElement if (firstChild) { - firstChild.classList.add('day-inverted') + firstChild.classList.add("day-inverted") } } dayContainer.appendChild(div.firstChild!) @@ -2613,13 +2783,13 @@ export async function loadHomePage() { CheckCurrentLessonAll(lessonArray) } } else if (dayContainer) { - dayContainer.innerHTML = /* html */` + dayContainer.innerHTML = /* html */ `

    No lessons available.

    ` } - dayContainer?.classList.remove('loading') + dayContainer?.classList.remove("loading") // Process assessments data const activeClass = classes.find((c: any) => c.hasOwnProperty("active")) @@ -2629,26 +2799,28 @@ export async function loadHomePage() { .filter((a: any) => activeSubjectCodes.includes(a.code)) .sort(comparedate) - const upcomingItems = document.getElementById('upcoming-items') + const upcomingItems = document.getElementById("upcoming-items") if (upcomingItems) { await CreateUpcomingSection(currentAssessments, activeSubjects) - upcomingItems.classList.remove('loading') + upcomingItems.classList.remove("loading") } // Process notices data const labelArray = prefs.payload - .filter((item: any) => item.name === 'notices.filters') + .filter((item: any) => item.name === "notices.filters") .map((item: any) => item.value) if (labelArray.length > 0) { - const noticeContainer = document.getElementById('notice-container') + const noticeContainer = document.getElementById("notice-container") if (noticeContainer) { - const dateControl = document.querySelector('input[type="date"]') as HTMLInputElement + const dateControl = document.querySelector( + 'input[type="date"]', + ) as HTMLInputElement if (dateControl) { dateControl.value = TodayFormatted - setupNotices(labelArray[0].split(' '), TodayFormatted) + setupNotices(labelArray[0].split(" "), TodayFormatted) } - noticeContainer.classList.remove('loading') + noticeContainer.classList.remove("loading") } } @@ -2662,15 +2834,15 @@ export async function loadHomePage() { // Helper functions function formatDate(date: Date): string { const year = date.getFullYear() - const month = (date.getMonth() + 1).toString().padStart(2, '0') - const day = date.getDate().toString().padStart(2, '0') + const month = (date.getMonth() + 1).toString().padStart(2, "0") + const day = date.getDate().toString().padStart(2, "0") return `${year}-${month}-${day}` } function setupTimetableListeners() { const listeners: Array<() => void> = [] - const timetableBack = document.getElementById('home-timetable-back') - const timetableForward = document.getElementById('home-timetable-forward') + const timetableBack = document.getElementById("home-timetable-back") + const timetableForward = document.getElementById("home-timetable-forward") function changeTimetable(value: number) { currentSelectedDate.setDate(currentSelectedDate.getDate() + value) @@ -2682,26 +2854,31 @@ function setupTimetableListeners() { const backHandler = () => changeTimetable(-1) const forwardHandler = () => changeTimetable(1) - timetableBack?.addEventListener('click', backHandler) - timetableForward?.addEventListener('click', forwardHandler) + timetableBack?.addEventListener("click", backHandler) + timetableForward?.addEventListener("click", forwardHandler) listeners.push( - () => timetableBack?.removeEventListener('click', backHandler), - () => timetableForward?.removeEventListener('click', forwardHandler) + () => timetableBack?.removeEventListener("click", backHandler), + () => timetableForward?.removeEventListener("click", forwardHandler), ) - return () => listeners.forEach(cleanup => cleanup()) + return () => listeners.forEach((cleanup) => cleanup()) } function setupNotices(labelArray: string[], date: string) { - const dateControl = document.querySelector('input[type="date"]') as HTMLInputElement - + const dateControl = document.querySelector( + 'input[type="date"]', + ) as HTMLInputElement + const fetchNotices = async (date: string) => { - const response = await fetch(`${location.origin}/seqta/student/load/notices?`, { - method: 'POST', - headers: { 'Content-Type': 'application/json; charset=utf-8' }, - body: JSON.stringify({ date }) - }) + const response = await fetch( + `${location.origin}/seqta/student/load/notices?`, + { + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({ date }), + }, + ) const data = await response.json() processNotices(data, labelArray) } @@ -2712,15 +2889,15 @@ function setupNotices(labelArray: string[], date: string) { fetchNotices(target.value) }, 250) - dateControl?.addEventListener('input', debouncedInputChange) + dateControl?.addEventListener("input", debouncedInputChange) fetchNotices(date) - return () => dateControl?.removeEventListener('input', debouncedInputChange) + return () => dateControl?.removeEventListener("input", debouncedInputChange) } function debounce any>( func: T, - wait: number + wait: number, ): (...args: Parameters) => void { let timeout: NodeJS.Timeout return (...args: Parameters) => { @@ -2732,17 +2909,18 @@ function debounce any>( export function addShortcuts(shortcuts: any) { for (let i = 0; i < shortcuts.length; i++) { const currentShortcut = shortcuts[i] - - if (currentShortcut?.enabled) { - const Itemname = (currentShortcut?.name ?? '').replace(/\s/g, '') - const linkDetails = ShortcutLinks?.[Itemname as keyof typeof ShortcutLinks] + if (currentShortcut?.enabled) { + const Itemname = (currentShortcut?.name ?? "").replace(/\s/g, "") + + const linkDetails = + ShortcutLinks?.[Itemname as keyof typeof ShortcutLinks] if (linkDetails) { createNewShortcut( linkDetails.link, linkDetails.icon, linkDetails.viewBox, - currentShortcut?.name + currentShortcut?.name, ) } else { console.warn(`No link details found for '${Itemname}'`) @@ -2753,19 +2931,16 @@ export function addShortcuts(shortcuts: any) { export function enableNotificationCollector() { var xhr3 = new XMLHttpRequest() - xhr3.open('POST', `${location.origin}/seqta/student/heartbeat?`, true) - xhr3.setRequestHeader( - 'Content-Type', - 'application/json; charset=utf-8' - ) + xhr3.open("POST", `${location.origin}/seqta/student/heartbeat?`, true) + xhr3.setRequestHeader("Content-Type", "application/json; charset=utf-8") xhr3.onreadystatechange = function () { if (xhr3.readyState === 4) { var Notifications = JSON.parse(xhr3.response) var alertdiv = document.getElementsByClassName( - 'notifications__bubble___1EkSQ' + "notifications__bubble___1EkSQ", )[0] - if (typeof alertdiv == 'undefined') { - console.info('[BetterSEQTA+] No notifications currently') + if (typeof alertdiv == "undefined") { + console.info("[BetterSEQTA+] No notifications currently") } else { alertdiv.textContent = Notifications.payload.notifications.length } @@ -2773,58 +2948,60 @@ export function enableNotificationCollector() { } xhr3.send( JSON.stringify({ - timestamp: '1970-01-01 00:00:00.0', - hash: '#?page=/home', - }) + timestamp: "1970-01-01 00:00:00.0", + hash: "#?page=/home", + }), ) } export function disableNotificationCollector() { - var alertdiv = document.getElementsByClassName('notifications__bubble___1EkSQ')[0] - if (typeof alertdiv != 'undefined') { + var alertdiv = document.getElementsByClassName( + "notifications__bubble___1EkSQ", + )[0] + if (typeof alertdiv != "undefined") { var currentNumber = parseInt(alertdiv.textContent!) if (currentNumber < 9) { alertdiv.textContent = currentNumber.toString() } else { - alertdiv.textContent = '9+' + alertdiv.textContent = "9+" } } } function createNewShortcut(link: any, icon: any, viewBox: any, title: any) { // Creates the stucture and element information for each seperate shortcut - let shortcut = document.createElement('a') - shortcut.setAttribute('href', link) - shortcut.setAttribute('target', '_blank') - let shortcutdiv = document.createElement('div') - shortcutdiv.classList.add('shortcut') + let shortcut = document.createElement("a") + shortcut.setAttribute("href", link) + shortcut.setAttribute("target", "_blank") + let shortcutdiv = document.createElement("div") + shortcutdiv.classList.add("shortcut") let image = stringToHTML( ``, - ).firstChild; - (image! as HTMLElement).classList.add('shortcuticondiv') - let text = document.createElement('p') + ).firstChild + ;(image! as HTMLElement).classList.add("shortcuticondiv") + let text = document.createElement("p") text.textContent = title shortcutdiv.append(image as HTMLElement) shortcutdiv.append(text) shortcut.append(shortcutdiv) - document.getElementById('shortcuts')!.appendChild(shortcut) + document.getElementById("shortcuts")!.appendChild(shortcut) } export async function SendNewsPage() { - console.info('[BetterSEQTA+] Started Loading News Page') - document.title = 'News ― SEQTA Learn' + console.info("[BetterSEQTA+] Started Loading News Page") + document.title = "News ― SEQTA Learn" await delay(100) - const element = document.querySelector('[data-key=news]') - element!.classList.add('active') + const element = document.querySelector("[data-key=news]") + element!.classList.add("active") // Remove all current elements in the main div to add new elements - const main = document.getElementById('main') - main!.innerHTML = '' + const main = document.getElementById("main") + main!.innerHTML = "" - const html = stringToHTML(/* html */` + const html = stringToHTML(/* html */ `

    Latest Headlines in ${settingsState.newsSource}

    @@ -2833,28 +3010,31 @@ export async function SendNewsPage() { main!.append(html.firstChild!) - const titlediv = document.getElementById('title')!.firstChild; - (titlediv! as HTMLElement).innerText = 'News' - AppendLoadingSymbol('newsloading', '#news-container') + const titlediv = document.getElementById("title")!.firstChild + ;(titlediv! as HTMLElement).innerText = "News" + AppendLoadingSymbol("newsloading", "#news-container") - const response = await browser.runtime.sendMessage({ type: 'sendNews', source: settingsState.newsSource }) - const newscontainer = document.querySelector('#news-container') - document.getElementById('newsloading')?.remove() + const response = await browser.runtime.sendMessage({ + type: "sendNews", + source: settingsState.newsSource, + }) + const newscontainer = document.querySelector("#news-container") + document.getElementById("newsloading")?.remove() // Create a document fragment to batch DOM operations const fragment = document.createDocumentFragment() // Map over articles to create elements response.news.articles.forEach((article: any) => { - const newsarticle = document.createElement('a') - newsarticle.classList.add('NewsArticle') + const newsarticle = document.createElement("a") + newsarticle.classList.add("NewsArticle") newsarticle.href = article.url - newsarticle.target = '_blank' + newsarticle.target = "_blank" - const articleimage = document.createElement('div') - articleimage.classList.add('articleimage') + const articleimage = document.createElement("div") + articleimage.classList.add("articleimage") - if (article.urlToImage == 'null' || article.urlToImage == null) { + if (article.urlToImage == "null" || article.urlToImage == null) { articleimage.style.cssText = ` background-image: url(${browser.runtime.getURL(LogoLightOutline)}); width: 20%; @@ -2864,17 +3044,20 @@ export async function SendNewsPage() { articleimage.style.backgroundImage = `url(${article.urlToImage})` } - const articletext = document.createElement('div') - articletext.classList.add('ArticleText') - - const title = document.createElement('a') + const articletext = document.createElement("div") + articletext.classList.add("ArticleText") + + const title = document.createElement("a") title.innerText = article.title title.href = article.url - title.target = '_blank' + title.target = "_blank" - const description = document.createElement('p') + const description = document.createElement("p") - article.description = article.description.length > 400 ? article.description.substring(0, 400) + '...' : article.description + article.description = + article.description.length > 400 + ? article.description.substring(0, 400) + "..." + : article.description description.innerHTML = article.description articletext.append(title, description) @@ -2884,46 +3067,53 @@ export async function SendNewsPage() { // Single DOM update to append all articles newscontainer?.append(fragment) - - if (!settingsState.animations) return; - const articles = Array.from(document.querySelectorAll('.NewsArticle')) + if (!settingsState.animations) return + + const articles = Array.from(document.querySelectorAll(".NewsArticle")) animate( articles.slice(0, 20), { opacity: [0, 1], y: [10, 0], scale: [0.99, 1] }, - { delay: stagger(0.1), type: 'spring', stiffness: 341, damping: 20, mass: 1 } + { + delay: stagger(0.1), + type: "spring", + stiffness: 341, + damping: 20, + mass: 1, + }, ) } async function CheckForMenuList() { try { - await waitForElm('#menu > ul'); - ObserveMenuItemPosition(); + await waitForElm("#menu > ul") + ObserveMenuItemPosition() } catch (error) { - return; + return } } function SetTimetableSubtitle() { - const homelessonsubtitle = document.getElementById('home-lesson-subtitle') + const homelessonsubtitle = document.getElementById("home-lesson-subtitle") if (!homelessonsubtitle) return const date = new Date() - const isSameMonth = date.getFullYear() === currentSelectedDate.getFullYear() && - date.getMonth() === currentSelectedDate.getMonth() - + const isSameMonth = + date.getFullYear() === currentSelectedDate.getFullYear() && + date.getMonth() === currentSelectedDate.getMonth() + if (isSameMonth) { const dayDiff = date.getDate() - currentSelectedDate.getDate() - switch(dayDiff) { + switch (dayDiff) { case 0: - homelessonsubtitle.innerText = 'Today\'s Lessons' + homelessonsubtitle.innerText = "Today's Lessons" break case 1: - homelessonsubtitle.innerText = 'Yesterday\'s Lessons' + homelessonsubtitle.innerText = "Yesterday's Lessons" break case -1: - homelessonsubtitle.innerText = 'Tomorrow\'s Lessons' + homelessonsubtitle.innerText = "Tomorrow's Lessons" break default: homelessonsubtitle.innerText = formatDateString(currentSelectedDate) @@ -2934,21 +3124,21 @@ function SetTimetableSubtitle() { } function formatDateString(date: Date): string { - return `${date.toLocaleString('en-us', { weekday: 'short' })} ${date.toLocaleDateString('en-au')}` + return `${date.toLocaleString("en-us", { weekday: "short" })} ${date.toLocaleDateString("en-au")}` } function processNotices(response: any, labelArray: string[]) { - const NoticeContainer = document.getElementById('notice-container') + const NoticeContainer = document.getElementById("notice-container") if (!NoticeContainer) return // Clear existing notices - NoticeContainer.innerHTML = '' + NoticeContainer.innerHTML = "" const notices = response.payload if (!notices.length) { - const dummyNotice = document.createElement('div') - dummyNotice.textContent = 'No notices for today.' - dummyNotice.classList.add('dummynotice') + const dummyNotice = document.createElement("div") + dummyNotice.textContent = "No notices for today." + dummyNotice.classList.add("dummynotice") NoticeContainer.append(dummyNotice) return } @@ -2970,7 +3160,7 @@ function processNotices(response: any, labelArray: string[]) { } function processNoticeColor(colour: string): string | undefined { - if (typeof colour === 'string') { + if (typeof colour === "string") { const rgb = GetThresholdOfColor(colour) if (rgb < 100 && settingsState.DarkMode) { return undefined @@ -2983,105 +3173,116 @@ function createNoticeElement(notice: any, colour: string | undefined): Node { const htmlContent = `

    ${notice.title}

    - ${notice.label_title !== undefined ? `
    ${notice.label_title}
    ` : ''} + ${notice.label_title !== undefined ? `
    ${notice.label_title}
    ` : ""}
    ${notice.staff}
    - ${notice.contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, '').replace(/ +/, ' ')} + ${notice.contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "").replace(/ +/, " ")}
    ` - + const element = stringToHTML(htmlContent).firstChild if (element instanceof HTMLElement) { - element.style.setProperty('--colour', colour ?? '') + element.style.setProperty("--colour", colour ?? "") } return element! } async function handleAssessments(node: Element): Promise { - if (!(node instanceof HTMLElement)) return; + if (!(node instanceof HTMLElement)) return // Wait for the assessments wrapper to be mounted - const assessmentsWrapper = await waitForElm('#main > .assessmentsWrapper .assessments .AssessmentItem__AssessmentItem___2EZ95', true, 50); - if (!assessmentsWrapper) return; + const assessmentsWrapper = await waitForElm( + "#main > .assessmentsWrapper .assessments .AssessmentItem__AssessmentItem___2EZ95", + true, + 50, + ) + if (!assessmentsWrapper) return // Grade conversion map for letter grades const letterGradeMap: Record = { - 'A+': 100, - 'A': 95, - 'A-': 90, - 'B+': 85, - 'B': 80, - 'B-': 75, - 'C+': 70, - 'C': 65, - 'C-': 60, - 'D+': 55, - 'D': 50, - 'D-': 45, - 'E+': 40, - 'E': 35, - 'E-': 30, - 'F': 0 - }; + "A+": 100, + A: 95, + "A-": 90, + "B+": 85, + B: 80, + "B-": 75, + "C+": 70, + C: 65, + "C-": 60, + "D+": 55, + D: 50, + "D-": 45, + "E+": 40, + E: 35, + "E-": 30, + F: 0, + } // Function to parse grade text into a number function parseGrade(gradeText: string): number { // Remove any whitespace - const trimmedGrade = gradeText.trim().toUpperCase(); + const trimmedGrade = gradeText.trim().toUpperCase() // Check if it is a non-percent grade - if (trimmedGrade.includes('/')) { - const grade = trimmedGrade.split("/"); + if (trimmedGrade.includes("/")) { + const grade = trimmedGrade.split("/") var a = grade[1] as unknown as number var b = grade[0] as unknown as number - return ((b/a) * 100); + return (b / a) * 100 } // Check if it's a percentage - if (trimmedGrade.includes('%')) { - return parseFloat(trimmedGrade.replace('%', '')) || 0; - } - - // Check if it's a letter grade - if (letterGradeMap.hasOwnProperty(trimmedGrade)) { - return letterGradeMap[trimmedGrade]; + if (trimmedGrade.includes("%")) { + return parseFloat(trimmedGrade.replace("%", "")) || 0 } - return 0; + // Check if it's a letter grade + if (letterGradeMap.hasOwnProperty(trimmedGrade)) { + return letterGradeMap[trimmedGrade] + } + + return 0 } // Function to calculate average of grades function calculateAverageGrade(): number { - const gradeElements = document.querySelectorAll('.Thermoscore__text___1NdvB'); - let total = 0; - let count = 0; + const gradeElements = document.querySelectorAll( + ".Thermoscore__text___1NdvB", + ) + let total = 0 + let count = 0 - gradeElements.forEach(element => { - const gradeText = element.textContent || ''; - const grade = parseGrade(gradeText); + gradeElements.forEach((element) => { + const gradeText = element.textContent || "" + const grade = parseGrade(gradeText) if (grade > 0) { - total += grade; - count++; + total += grade + count++ } - }); + }) - return count > 0 ? total / count : 0; + return count > 0 ? total / count : 0 } // Function to add the average assessment item function addAverageAssessment() { - const numaverage = calculateAverageGrade(); - if (numaverage === 0) return; + const numaverage = calculateAverageGrade() + if (numaverage === 0) return // Remove existing average section if it exists - const existingAverage = document.querySelector('.AssessmentItem__AssessmentItem___2EZ95:first-child'); - if (existingAverage?.querySelector('.AssessmentItem__title___2bELn')?.textContent === 'Subject Average') { - existingAverage.remove(); + const existingAverage = document.querySelector( + ".AssessmentItem__AssessmentItem___2EZ95:first-child", + ) + if ( + existingAverage?.querySelector(".AssessmentItem__title___2bELn") + ?.textContent === "Subject Average" + ) { + existingAverage.remove() } const preaverage = numaverage.toFixed(0) as unknown as number - const prepaverage = Math.ceil(preaverage / 5) * 5; + const prepaverage = Math.ceil(preaverage / 5) * 5 const NumberGradeMap: Record = { 100: "A+", 95: "A", 90: "A-", - 85: "B+", + 85: "B+", 80: "B", 75: "B-", 70: "C+", @@ -3093,13 +3294,16 @@ async function handleAssessments(node: Element): Promise { 40: "E+", 35: "E", 30: "E-", - 0: "F" - }; + 0: "F", + } var letteraverage = "N/A" - const check = Object.prototype.hasOwnProperty.call(NumberGradeMap, prepaverage); + const check = Object.prototype.hasOwnProperty.call( + NumberGradeMap, + prepaverage, + ) if (check) { console.debug("[BetterSEQTA+ Debugger] Match found") - letteraverage = NumberGradeMap[prepaverage]; + letteraverage = NumberGradeMap[prepaverage] } else { console.debug("[BetterSEQTA+ Debugger] No match found") letteraverage = "N/A" @@ -3110,7 +3314,7 @@ async function handleAssessments(node: Element): Promise { } else { average = `${numaverage.toFixed(2)}%` } - const averageElement = stringToHTML(/* html */` + const averageElement = stringToHTML(/* html */ `
    @@ -3125,15 +3329,20 @@ async function handleAssessments(node: Element): Promise {
    - `); + `) // Insert at the beginning of the assessments list - const assessmentsList = document.querySelector('.assessments .AssessmentList__items___3LcmQ'); + const assessmentsList = document.querySelector( + ".assessments .AssessmentList__items___3LcmQ", + ) if (assessmentsList && averageElement.firstChild) { - assessmentsList.insertBefore(averageElement.firstChild, assessmentsList.firstChild); + assessmentsList.insertBefore( + averageElement.firstChild, + assessmentsList.firstChild, + ) } } // Add the average assessment item - addAverageAssessment(); + addAverageAssessment() } diff --git a/src/interface/components/TabbedContainer.svelte b/src/interface/components/TabbedContainer.svelte index c06d2804..5771af5b 100644 --- a/src/interface/components/TabbedContainer.svelte +++ b/src/interface/components/TabbedContainer.svelte @@ -5,7 +5,6 @@ let { tabs } = $props<{ tabs: { title: string, Content: any, props?: any }[] }>(); let activeTab = $state(0); - let hoveredTab = $state(null); let containerRef: HTMLElement | null = null; let tabWidth = $state(0); @@ -24,10 +23,6 @@ return 0; }; - $effect(() => { - calcXPos(hoveredTab); - }); - onMount(() => { updateTabWidth(); @@ -45,19 +40,17 @@
    -
    -
    +
    +
    {#each tabs as { title }, index} @@ -80,4 +73,4 @@
    -
    \ No newline at end of file +
    diff --git a/src/manifests/manifest.json b/src/manifests/manifest.json index 3c34412c..5e6fca90 100644 --- a/src/manifests/manifest.json +++ b/src/manifests/manifest.json @@ -36,7 +36,7 @@ "matches": ["*://*/*"] }, { - "resources": ["resources/icons/*"], + "resources": ["resources/*"], "matches": ["*://*/*"] }, {