schoolbox web extension :)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: JS to TS (#214)

* its broken now

* refactor: abolish that pesky js

authored by

willow and committed by
GitHub
bd142804 9136a18d

+161 -116
-2
src/entrypoints/plugins.content.ts
··· 1 - // @ts-expect-error js plugin 2 1 import subheader from "./plugins/subheader"; 3 2 import scrollSegments from "./plugins/scrollSegments"; 4 3 import scrollPeriod from "./plugins/scrollPeriod"; 5 4 import progressBar from "./plugins/progressBar"; 6 5 import modernIcons from "./plugins/modernIcons"; 7 - // @ts-expect-error js plugin 8 6 import tabTitle from "./plugins/tabTitle"; 9 7 import homepageSwitcher from "./plugins/homepageSwitcher"; 10 8 import legacyTimetable from "./plugins/legacyTimetable";
+4 -21
src/entrypoints/plugins/progressBar/index.ts
··· 6 6 () => { 7 7 if (window.location.pathname === "/" && document.querySelector(".timetable")) { 8 8 const periodList = getListOfPeriods(); 9 - // console.log(periodList); 9 + console.log(periodList); 10 10 11 11 const progressRow = document.createElement("tr"); 12 12 progressRow.classList.add("progress-container"); ··· 20 20 ); 21 21 } 22 22 23 - function insertProgressBars(periodList: any[], container: HTMLElement) { 23 + function insertProgressBars(periodList: Period[], container: HTMLElement) { 24 24 periodList.forEach((period) => { 25 25 const td = document.createElement("td"); 26 26 const progressBar = document.createElement("progress"); 27 - const progress = calculateProgress(period); 27 + const progress = period.getProgress(); 28 28 progressBar.className = "progress-bar"; 29 29 progressBar.max = 100; 30 30 progressBar.style.width = "100%"; ··· 32 32 33 33 if (progress < 100) { 34 34 const intervalId = setInterval(() => { 35 - progressBar.value = calculateProgress(period); 35 + progressBar.value = period.getProgress(); 36 36 if (progressBar.value === 100) { 37 37 clearInterval(intervalId); 38 38 } ··· 43 43 container.appendChild(td); 44 44 }); 45 45 } 46 - 47 - function calculateProgress(period: any): number { 48 - const now = new Date().getTime(); 49 - const startTime = new Date(period.header.time.start).getTime(); 50 - const endTime = new Date(period.header.time.end).getTime(); 51 - const periodDuration = endTime - startTime; 52 - let progressPercentage = 0; 53 - 54 - if (now >= startTime && now <= endTime) { 55 - const elapsedTime = now - startTime; 56 - progressPercentage = Math.min(Math.max((elapsedTime / periodDuration) * 100, 0), 100); 57 - } else if (now > endTime) { 58 - progressPercentage = 100; 59 - } 60 - 61 - return progressPercentage; 62 - }
+8 -4
src/entrypoints/plugins/subheader.js src/entrypoints/plugins/subheader.ts
··· 2 2 defineStPlugin( 3 3 "subheader", 4 4 () => { 5 - let style = document.createElement("style"); 5 + const style = document.createElement("style"); 6 6 style.classList = "schooltape"; 7 7 style.innerHTML = ` 8 8 .subheader span:not(:last-child):not(.period:empty)::after { ··· 24 24 25 25 function createSubheader() { 26 26 const subheader = document.querySelector("h2.subheader"); 27 + if (!subheader) return; 27 28 // TODO: Refactor to support hot reload/uninjection 28 29 // delete all children of the subheader 29 30 while (subheader.firstChild) { ··· 42 43 let periodSpan = document.querySelector(".subheader .period"); 43 44 if (!periodSpan) { 44 45 const subheader = document.querySelector(".subheader .schooltape"); 46 + if (!subheader) return; 45 47 periodSpan = document.createElement("span"); 46 48 periodSpan.classList.add("period"); 47 49 subheader.appendChild(periodSpan); 48 50 } 49 51 periodSpan.textContent = ""; 50 52 51 - let period = getCurrentPeriod(); 53 + const period = getCurrentPeriod(); 52 54 if (period) { 53 55 const name = period.data.name || period.header.name; 54 56 const room = period.data.room ? ` (${period.data.room})` : ""; ··· 76 78 let clockSpan = document.querySelector(".subheader .clock"); 77 79 if (!clockSpan) { 78 80 const subheader = document.querySelector(".subheader .schooltape"); 81 + if (!subheader) return; 79 82 clockSpan = document.createElement("span"); 80 83 clockSpan.classList.add("clock"); 81 84 subheader.appendChild(clockSpan); 82 85 } 83 - let date = new Date(); 86 + const date = new Date(); 84 87 clockSpan.textContent = date.toLocaleTimeString([], { 85 88 hour: "2-digit", 86 89 minute: "2-digit", ··· 91 94 let dateSpan = document.querySelector(".subheader .date"); 92 95 if (!dateSpan) { 93 96 const subheader = document.querySelector(".subheader .schooltape"); 97 + if (!subheader) return; 94 98 dateSpan = document.createElement("span"); 95 99 dateSpan.classList.add("date"); 96 100 subheader.appendChild(dateSpan); 97 101 } 98 - let date = new Date(); 102 + const date = new Date(); 99 103 dateSpan.textContent = date.toDateString(); 100 104 } 101 105 },
+4 -3
src/entrypoints/plugins/tabTitle.js src/entrypoints/plugins/tabTitle.ts
··· 3 3 "tabTitle", 4 4 () => { 5 5 const path = window.location.pathname; 6 - const titleMap = { 6 + const titleMap: { [key: string]: string } = { 7 7 "/": "Homepage", 8 8 "/calendar": "Calendar", 9 9 "/news": "News", 10 10 "/learning/classes": "Classes", 11 11 "/resources": "Resources", 12 - "/settings/messages": "Message Settings", 12 + "/groups": "Groups", 13 + "/settings/notifications": "Notifications Settings", 13 14 "/mail/create": "Compose Email", 14 15 "/feedback": "Support and Feedback", 15 16 "/policy": "Guidelines of Use and Privacy Policy", 16 17 }; 17 18 18 - if (Object.prototype.hasOwnProperty.call(titleMap, path)) { 19 + if (titleMap[path]) { 19 20 document.title = titleMap[path]; 20 21 } else if (path.includes("/timetable")) { 21 22 document.title = "Timetable";
-86
src/utils/periodUtils.js
··· 1 - export function getCurrentPeriod() { 2 - const periodList = getListOfPeriods(); 3 - const currentTime = new Date().getTime(); 4 - const currentPeriod = periodList.find((period) => { 5 - if (period.header.time) { 6 - const { start, end } = period.header.time; 7 - // console.log("start time: ", start.getTime(), "end time: ", end.getTime()); // log the start and end times 8 - if (start.getTime() <= currentTime && currentTime <= end.getTime()) { 9 - return true; 10 - } 11 - } 12 - return false; 13 - }); 14 - // console.log("currentPeriod: ", currentPeriod); // log the currentPeriod 15 - return currentPeriod || null; 16 - } 17 - 18 - export function getListOfPeriods() { 19 - const periods = document.querySelectorAll(".timetable thead tr th"); 20 - return Array.from(periods).map((_, i) => getPeriodData(i)); 21 - } 22 - 23 - function getPeriodData(index) { 24 - if (typeof index !== "number") { 25 - console.error("Period number was not provided or is not a number"); 26 - return null; 27 - } 28 - 29 - index += 1; 30 - // Example data structure: 31 - // { 32 - // header: { 33 - // name: "Period 1" 34 - // time: "8:30am-8-44am" 35 - // }, 36 - // data: { 37 - // name: "Programming - Schooltape" 38 - // link: "/home/page/code/schooltape" 39 - // id: "(-ST-CC)" 40 - // room: "Library" 41 - // } 42 - // } 43 - const header = document.querySelector(`.timetable thead tr th:nth-child(${index})`); 44 - const data = document.querySelector(`.timetable tbody tr td:nth-child(${index}) div:nth-child(1) div:nth-child(1)`); 45 - 46 - // console.log(header, data); 47 - 48 - return { 49 - header: { 50 - name: header?.childNodes[0]?.textContent.trim() || null, 51 - time: extractTimes(header?.querySelector("time")?.textContent.trim()) || null, 52 - }, 53 - data: { 54 - name: data?.querySelector("a")?.textContent.trim() || null, 55 - link: data?.querySelector("a")?.getAttribute("href") || null, 56 - id: data?.querySelector("div:nth-child(2)")?.textContent.trim() || null, 57 - room: data?.querySelector("div:nth-child(3)")?.textContent.trim() || null, 58 - }, 59 - index: index, 60 - }; 61 - } 62 - 63 - function extractTimes(periodTime) { 64 - try { 65 - let times = periodTime.split("–"); // en dash 66 - let [start, end] = times.map((time, index) => { 67 - let [hour, minute] = time.split(":"); 68 - let isAM = time.slice(-2).toLowerCase() === "am"; 69 - hour = parseInt(hour); 70 - minute = parseInt(minute.substring(0, 2)); 71 - if (!isAM && hour !== 12) { 72 - hour += 12; 73 - } 74 - let date = new Date(); 75 - date.setHours(hour, minute); 76 - if (index === 1) { 77 - date.setSeconds(59, 999); 78 - } 79 - return date; 80 - }); 81 - return { start, end }; 82 - } catch (error) { 83 - console.error("Error extracting times:", error); 84 - return false; 85 - } 86 - }
+145
src/utils/periodUtils.ts
··· 1 + // these utility functions are intended to be used on the dashboard, as that is where the timetable is displayed 2 + 3 + interface PeriodHeader { 4 + name: string; 5 + time: { 6 + start: Date; 7 + end: Date; 8 + }; 9 + } 10 + interface PeriodData { 11 + name: string; 12 + link: string; 13 + id: string; 14 + room: string; 15 + } 16 + 17 + export class Period { 18 + header: PeriodHeader; 19 + data: PeriodData; 20 + 21 + constructor(header: PeriodHeader, data: PeriodData) { 22 + this.header = header; 23 + this.data = data; 24 + } 25 + 26 + inProgress(): boolean { 27 + // check if it's currently the period 28 + const now = new Date(); 29 + const { start, end } = this.header.time; 30 + return start.getTime() <= now.getTime() && now.getTime() <= end.getTime(); 31 + } 32 + getProgress(): number { 33 + const now = new Date(); 34 + const { start, end } = this.header.time; 35 + const periodDuration = end.getTime() - start.getTime(); 36 + let progressPercentage = 0; 37 + 38 + if (now.getTime() >= start.getTime() && now.getTime() <= end.getTime()) { 39 + console.log("in progress"); 40 + const elapsedTime = now.getTime() - start.getTime(); 41 + progressPercentage = Math.min(Math.max((elapsedTime / periodDuration) * 100, 0), 100); 42 + } else if (now.getTime() > end.getTime()) { 43 + console.log("after period"); 44 + progressPercentage = 100; 45 + } else { 46 + console.log("not in progress"); 47 + console.log(now.getTime(), end.getTime()); 48 + console.log(now, end); 49 + } 50 + 51 + return progressPercentage; 52 + } 53 + } 54 + 55 + export function getCurrentPeriod(): Period | null { 56 + const periodList = getListOfPeriods(); 57 + const currentPeriod = periodList.find((period) => { 58 + return period.inProgress(); 59 + }); 60 + return currentPeriod || null; 61 + } 62 + 63 + export function getListOfPeriods(): Period[] { 64 + const periods = document.querySelectorAll(".timetable thead tr th"); 65 + return Array.from(periods).map((_, i) => getPeriodData(i)); 66 + } 67 + 68 + function getPeriodData(index: number): Period { 69 + index += 1; 70 + // Example data structure: 71 + // { 72 + // header: { 73 + // name: "Period 1" 74 + // time: "8:30am-8-44am" 75 + // }, 76 + // data: { 77 + // name: "Programming - Schooltape" 78 + // link: "/home/page/code/schooltape" 79 + // id: "(-ST-CC)" 80 + // room: "Library" 81 + // } 82 + // } 83 + const header = document.querySelector(`.timetable thead tr th:nth-child(${index})`); 84 + const data = document.querySelector(`.timetable tbody tr td:nth-child(${index}) div:nth-child(1) div:nth-child(1)`); 85 + 86 + if (!header) { 87 + throw new Error(`Failed to find header for index ${index}`); 88 + } 89 + 90 + if (!data) { 91 + throw new Error(`Failed to find data for index ${index}`); 92 + } 93 + 94 + const headerName = header.childNodes[0]?.textContent?.trim() || ""; 95 + const headerTime = extractTimes(header.querySelector("time")?.textContent?.trim() || ""); 96 + 97 + const dataName = data.querySelector("a")?.textContent?.trim() || ""; 98 + const dataLink = data.querySelector("a")?.getAttribute("href") || ""; 99 + const dataId = data.querySelector("div:nth-child(2)")?.textContent?.trim() || ""; 100 + const dataRoom = data.querySelector("div:nth-child(3)")?.textContent?.trim() || ""; 101 + 102 + return new Period( 103 + { 104 + name: headerName, 105 + time: headerTime, 106 + }, 107 + { 108 + name: dataName, 109 + link: dataLink, 110 + id: dataId, 111 + room: dataRoom, 112 + }, 113 + ); 114 + } 115 + 116 + function extractTimes(periodTime: string): { start: Date; end: Date } { 117 + try { 118 + const times = periodTime.split("–"); // en dash 119 + 120 + const [start, end] = times.map((time, index) => { 121 + const [hourStr, minuteStr] = time.split(":"); 122 + let hour = parseInt(hourStr); 123 + const minute = parseInt(minuteStr.substring(0, 2)); 124 + 125 + const isAM = time.includes("am"); 126 + 127 + if (!isAM && hour !== 12) { 128 + hour += 12; 129 + } 130 + const date = new Date(); 131 + date.setHours(hour, minute); 132 + 133 + // if it's the end time 134 + if (index === 1) { 135 + date.setSeconds(59, 999); 136 + } 137 + 138 + return date; 139 + }); 140 + return { start, end }; 141 + } catch (error) { 142 + logger.error("Error extracting times:", error); 143 + throw new Error("Failed to extract times"); 144 + } 145 + }