schoolbox web extension :)
1import { getCurrentPeriod } from "@/utils/periodUtils"; 2import { Plugin } from "@/utils/plugin"; 3import styleText from "./styles.css?inline"; 4import { dataAttr, injectInlineStyles, setDataAttr, uninjectInlineStyles } from "@/utils"; 5import type { StorageState } from "@/utils/storage/state.svelte"; 6import type { Toggle } from "@/utils/storage"; 7import menu from "./Menu.svelte?url"; 8 9const ID = "subheader"; 10const PLUGIN_ID = `plugin-${ID}`; 11 12let intervals: NodeJS.Timeout[] = []; 13let oldChildren: ChildNode[] = []; 14let subheader: HTMLHeadingElement | null = null; 15 16export type Settings = { 17 openInNewTab: StorageState<Toggle>; 18}; 19 20export default new Plugin<Settings>( 21 { 22 id: ID, 23 name: "Subheader Revamp", 24 description: "Adds a clock and current period info to the subheader.", 25 }, 26 true, 27 { 28 config: { 29 openInNewTab: { toggle: true }, 30 }, 31 menu, 32 }, 33 async (settings) => { 34 const openInNewTab = await settings.openInNewTab.get(); 35 injectSubheader(openInNewTab.toggle); 36 }, 37 uninjectSubheader, 38 [".subheader", ".timetable"], 39); 40 41function injectSubheader(openInNewTab: boolean) { 42 // abort if plugin is injected 43 if (subheader !== null) return; 44 45 // abort if not on homepage 46 if (window.location.pathname !== "/") return; 47 48 subheader = document.querySelector("h2.subheader"); 49 if (!subheader) return; 50 51 // inject subheader styling 52 injectInlineStyles(styleText, PLUGIN_ID); 53 54 // delete all children of the subheader 55 while (subheader.firstChild) { 56 oldChildren.push(subheader.removeChild(subheader.firstChild)); 57 } 58 59 updatePeriodSpan(openInNewTab); 60 updateClockSpan(); 61 updateDateSpan(); 62 63 intervals = [ 64 setInterval(() => updatePeriodSpan(openInNewTab), 5000), 65 setInterval(updateClockSpan, 1000), 66 setInterval(updateDateSpan, 60000), 67 ]; 68} 69 70function uninjectSubheader() { 71 // abort if plugin is not injected 72 if (subheader === null) return; 73 74 // abort if not on homepage 75 if (window.location.pathname !== "/") return; 76 77 // stop updating the subheader 78 intervals.forEach((interval) => clearInterval(interval)); 79 80 // remove new children 81 while (subheader.firstChild) { 82 subheader.removeChild(subheader.firstChild); 83 } 84 85 // restore old children 86 for (const child of oldChildren) { 87 subheader.appendChild(child); 88 } 89 90 // uninject subheader styling 91 uninjectInlineStyles(PLUGIN_ID); 92 93 // reset variables 94 intervals = []; 95 oldChildren = []; 96 subheader = null; 97} 98 99async function updatePeriodSpan(openInNewTab: boolean) { 100 if (!subheader) return; 101 102 const periodId = `${PLUGIN_ID}-period`; 103 let periodSpan = document.querySelector<HTMLSpanElement>(`.subheader ${dataAttr(periodId)}`); 104 105 if (!periodSpan) { 106 periodSpan = document.createElement("span"); 107 setDataAttr(periodSpan, periodId); 108 subheader.appendChild(periodSpan); 109 } 110 111 periodSpan.textContent = ""; 112 113 // set period span content 114 const period = getCurrentPeriod(); 115 if (period) { 116 const name = period.data.name || period.header.name; 117 const room = period.data.room ? ` (${period.data.room})` : ""; 118 let periodLink = periodSpan.querySelector("a"); 119 if (period.data.name && period.data.link) { 120 // if there's period data 121 if (!periodLink) { 122 periodLink = document.createElement("a"); 123 124 periodLink.target = openInNewTab ? "_blank" : "_self"; 125 periodSpan.appendChild(periodLink); 126 } 127 periodLink.href = period.data.link; 128 periodLink.textContent = `${name}${room}`; 129 } else { 130 // if there's only the header 131 periodSpan.textContent = `${name}${room}`; 132 if (periodLink) { 133 periodSpan.removeChild(periodLink); 134 } 135 } 136 } 137} 138 139function updateClockSpan() { 140 if (!subheader) return; 141 142 const clockId = `${PLUGIN_ID}-clock`; 143 let clockSpan = document.querySelector<HTMLSpanElement>(`.subheader ${dataAttr(clockId)}`); 144 145 if (!clockSpan) { 146 clockSpan = document.createElement("span"); 147 setDataAttr(clockSpan, clockId); 148 subheader.appendChild(clockSpan); 149 } 150 151 // set clock span content 152 const date = new Date(); 153 clockSpan.textContent = date.toLocaleTimeString([], { 154 hour: "2-digit", 155 minute: "2-digit", 156 }); 157} 158 159function updateDateSpan() { 160 if (!subheader) return; 161 162 const dateId = `${PLUGIN_ID}-date`; 163 let dateSpan = document.querySelector<HTMLSpanElement>(`.subheader ${dataAttr(dateId)}`); 164 165 if (!dateSpan) { 166 dateSpan = document.createElement("span"); 167 setDataAttr(dateSpan, dateId); 168 subheader.appendChild(dateSpan); 169 } 170 171 // set date span content 172 const date = new Date(); 173 dateSpan.textContent = date.toDateString(); 174}