schoolbox web extension :)
0
fork

Configure Feed

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

feat(plugins/subheader): hot reload

willow d0a8e392 0cc6515f

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