schoolbox web extension :)
0
fork

Configure Feed

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

Merge branch 'v4' of https://github.com/schooltape/schooltape into v4

willow b11b0a58 74704813

+93 -54
bun.lockb

This is a binary file and will not be displayed.

+42
eslint.config.js
··· 1 + import js from "@eslint/js"; 2 + import ts from "typescript-eslint"; 3 + import svelte from "eslint-plugin-svelte"; 4 + import wxtAutoImports from "./.wxt/eslint-auto-imports.mjs"; 5 + 6 + import globals from "globals"; 7 + import { includeIgnoreFile } from "@eslint/compat"; 8 + import path from "node:path"; 9 + 10 + const gitignorePath = path.resolve(import.meta.dirname, ".gitignore"); 11 + 12 + export default ts.config( 13 + js.configs.recommended, 14 + // ...ts.configs.recommendedTypeChecked, 15 + ...ts.configs.recommended, 16 + ...svelte.configs.recommended, 17 + 18 + includeIgnoreFile(gitignorePath), 19 + wxtAutoImports, 20 + { ignores: ["*.d.ts", "tailwind.config.js"] }, 21 + 22 + // { 23 + // files: ["**/*.js"], 24 + // extends: [ts.configs.disableTypeChecked], 25 + // }, 26 + { 27 + languageOptions: { 28 + parserOptions: { 29 + // projectService: { 30 + // allowDefaultProject: ["*.js"], 31 + // }, 32 + extraFileExtensions: [".svelte"], 33 + parser: ts.parser, 34 + }, 35 + globals: globals.browser, 36 + }, 37 + rules: { 38 + "@typescript-eslint/no-empty-object-type": "off", 39 + "@typescript-eslint/no-explicit-any": "warn", 40 + }, 41 + }, 42 + );
+7
package.json
··· 6 6 "devDependencies": { 7 7 "@catppuccin/palette": "^1.7.1", 8 8 "@catppuccin/tailwindcss": "^0.1.6", 9 + "@eslint/compat": "^1.2.7", 10 + "@eslint/js": "^9.21.0", 9 11 "@sveltejs/vite-plugin-svelte": "^5.0.3", 10 12 "@tsconfig/svelte": "^5.0.4", 11 13 "@wxt-dev/module-svelte": "^2.0.3", 12 14 "autoprefixer": "^10.4.21", 15 + "eslint": "^9.22.0", 16 + "eslint-plugin-svelte": "^3.1.0", 17 + "globals": "^16.0.0", 13 18 "lucide-svelte": "^0.475.0", 14 19 "pino": "^9.6.0", 15 20 "postcss": "^8.5.3", ··· 21 26 "svelte-spa-router": "^4.0.1", 22 27 "tailwindcss": "^3.4.17", 23 28 "typescript": "^5.8.2", 29 + "typescript-eslint": "^8.25.0", 24 30 "wxt": "^0.19.29" 25 31 }, 26 32 "description": "Schooltape is a free, open-source, web extension that allows you to customise the look, feel, and functionality of Schoolbox!", ··· 34 40 "build:firefox": "wxt build -b firefox", 35 41 "zip": "wxt zip", 36 42 "zip:firefox": "wxt zip -b firefox", 43 + "lint": "eslint", 37 44 "check": "svelte-check --tsconfig ./tsconfig.json", 38 45 "format": "prettier --write .", 39 46 "postinstall": "wxt prepare"
+4 -11
src/entrypoints/background.ts
··· 53 53 inject?: string; 54 54 toTab?: string; 55 55 } 56 - browser.runtime.onMessage.addListener(async (msg, sender, sendResponse) => { 57 - let message = msg as Message; 56 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 57 + browser.runtime.onMessage.addListener(async (msg: any, sender: any) => { 58 + const message = msg as Message; 58 59 logger.child({ message, sender }).info("[background] Received message"); 59 60 if (message.resetSettings) { 60 61 resetSettings(); 61 - } 62 - if (message.inject && sender.tab?.id) { 63 - logger.info(`[background] Injecting ${message.inject}`); 64 - // https://wxt.dev/guide/extension-apis/scripting.html 65 - const res = await browser.scripting.executeScript({ 66 - target: { tabId: sender.tab.id }, 67 - files: [message.inject], 68 - }); 69 62 } 70 63 if (message.toTab) { 71 64 const tabs = await browser.tabs.query({ url: message.toTab }); ··· 101 94 title: "GitHub", 102 95 contexts: contexts, 103 96 }); 104 - browser.contextMenus.onClicked.addListener((info, tab) => { 97 + browser.contextMenus.onClicked.addListener((info) => { 105 98 const manifest = browser.runtime.getManifest(); 106 99 const version = manifest.version_name || manifest.version; 107 100
+5 -5
src/entrypoints/end.content.ts
··· 3 3 runAt: "document_end", 4 4 excludeMatches: EXCLUDE_MATCHES, 5 5 async main() { 6 - let settings = await globalSettings.getValue(); 7 - let urls = await schoolboxUrls.getValue(); 6 + const settings = await globalSettings.getValue(); 7 + const urls = await schoolboxUrls.getValue(); 8 8 9 9 if (!settings.global) return; 10 - let footer = document.querySelector("#footer > ul"); 10 + const footer = document.querySelector("#footer > ul"); 11 11 if (footer && footer.innerHTML.includes("Schoolbox")) { 12 - let footerListItem = document.createElement("li"); 13 - let footerLink = document.createElement("a"); 12 + const footerListItem = document.createElement("li"); 13 + const footerLink = document.createElement("a"); 14 14 footerLink.href = "https://github.com/schooltape/schooltape"; 15 15 footerLink.textContent = `Schooltape v${browser.runtime.getManifest().version}`; 16 16 footerListItem.appendChild(footerLink);
+2 -2
src/entrypoints/plugins.content.ts
··· 1 - // @ts-ignore 1 + // @ts-expect-error js plugin 2 2 import subheader from "./plugins/subheader"; 3 3 import scrollSegments from "./plugins/scrollSegments"; 4 4 import scrollPeriod from "./plugins/scrollPeriod"; 5 5 import progressBar from "./plugins/progressBar"; 6 6 import modernIcons from "./plugins/modernIcons"; 7 - // @ts-ignore 7 + // @ts-expect-error js plugin 8 8 import tabTitle from "./plugins/tabTitle"; 9 9 import homepageSwitcher from "./plugins/homepageSwitcher"; 10 10 import legacyTimetable from "./plugins/legacyTimetable";
+2 -2
src/entrypoints/plugins/homepageSwitcher.ts
··· 2 2 defineStPlugin( 3 3 "homepageSwitcher", 4 4 () => { 5 - let logos = Array.from(document.getElementsByClassName("logo")) as HTMLAnchorElement[]; 5 + const logos = Array.from(document.getElementsByClassName("logo")) as HTMLAnchorElement[]; 6 6 logos.forEach((logo) => { 7 7 logo.addEventListener("click", async function (e) { 8 8 if (window.location.pathname === "/") return; 9 9 e.preventDefault(); 10 - let tab = logos[0].href; 10 + const tab = logos[0].href; 11 11 browser.runtime.sendMessage({ toTab: tab }); 12 12 }); 13 13 });
+2 -2
src/entrypoints/plugins/legacyTimetable.ts
··· 4 4 () => { 5 5 if (window.location.pathname === "/" && document.querySelector(".timetable")) { 6 6 // get the timetable container and add the 'columns' class 7 - let timetableContainer = document.querySelector("[data-timetable-container]"); 7 + const timetableContainer = document.querySelector("[data-timetable-container]"); 8 8 if (timetableContainer) { 9 9 timetableContainer.classList.add("columns"); 10 10 // get the row element and move the timetable container to the beginning 11 - let rowElement = document.querySelector(".Component_Dashboard_TimetableController")?.parentNode?.parentNode; 11 + const rowElement = document.querySelector(".Component_Dashboard_TimetableController")?.parentNode?.parentNode; 12 12 if (rowElement) { 13 13 rowElement.insertBefore(timetableContainer, rowElement.firstChild); 14 14 }
+8 -12
src/entrypoints/plugins/modernIcons/index.ts
··· 42 42 function insertIcon(className: string, iconName: string) { 43 43 const selectors = [`nav.tab-bar .top-menu .${className}`, `#overflow-nav .${className}`]; 44 44 selectors.forEach((selector) => { 45 - try { 46 - const icon = document.querySelector(selector); 47 - // Check if the icon already exists 48 - if (icon && !icon.querySelector(".material-icons-round")) { 49 - // console.log(`Inserting icon for ${className} at ${selector}`); 50 - const iconElement = document.createElement("i"); 51 - iconElement.innerHTML = iconName; 52 - iconElement.classList.add("material-icons-round"); 53 - icon.insertBefore(iconElement, icon.firstChild); 54 - } 55 - } catch (e) { 56 - // console.error(`Error inserting icon for ${className}: ${e}`); 45 + const icon = document.querySelector(selector); 46 + // Check if the icon already exists 47 + if (icon && !icon.querySelector(".material-icons-round")) { 48 + // console.log(`Inserting icon for ${className} at ${selector}`); 49 + const iconElement = document.createElement("i"); 50 + iconElement.innerHTML = iconName; 51 + iconElement.classList.add("material-icons-round"); 52 + icon.insertBefore(iconElement, icon.firstChild); 57 53 } 58 54 }); 59 55 }
+1 -1
src/entrypoints/plugins/subheader.js
··· 1 1 export default function init() { 2 2 defineStPlugin( 3 3 "subheader", 4 - (pluginId) => { 4 + () => { 5 5 let style = document.createElement("style"); 6 6 style.classList = "schooltape"; 7 7 style.innerHTML = `
+1 -1
src/entrypoints/plugins/tabTitle.js
··· 15 15 "/policy": "Guidelines of Use and Privacy Policy", 16 16 }; 17 17 18 - if (titleMap.hasOwnProperty(path)) { 18 + if (Object.prototype.hasOwnProperty.call(titleMap, path)) { 19 19 document.title = titleMap[path]; 20 20 } else if (path.includes("/timetable")) { 21 21 document.title = "Timetable";
+2 -2
src/entrypoints/popup/App.svelte
··· 57 57 accentHex = getAccentHex(accent, flavour); 58 58 document.documentElement.style.setProperty("--ctp-accent", accentHex); 59 59 60 - settingsUnwatch = globalSettings.watch((newValue, oldValue) => { 60 + settingsUnwatch = globalSettings.watch((newValue) => { 61 61 settings = newValue; 62 62 flavour = newValue.themeFlavour; 63 63 accent = newValue.themeAccent; ··· 67 67 refresh = true; 68 68 needsRefresh.setValue(refresh); 69 69 }); 70 - refreshUnwatch = needsRefresh.watch((newValue, oldValue) => { 70 + refreshUnwatch = needsRefresh.watch((newValue) => { 71 71 refresh = newValue; 72 72 }); 73 73 });
+1 -2
src/entrypoints/popup/components/Footer.svelte
··· 3 3 import IconBtn from "./inputs/IconBtn.svelte"; 4 4 import { MessageCircleMore, RotateCcw, BookText } from "lucide-svelte"; 5 5 6 - let version: string = $state(); 7 - let prefix: string; 6 + let version = $state(); 8 7 onMount(async () => { 9 8 // Set version number 10 9 // Uses manifest.version_name when available (on mv3)
+2 -2
src/entrypoints/start.content.ts
··· 6 6 runAt: "document_start", 7 7 excludeMatches: EXCLUDE_MATCHES, 8 8 async main() { 9 - let settings = await globalSettings.getValue(); 10 - let urls = await schoolboxUrls.getValue(); 9 + const settings = await globalSettings.getValue(); 10 + const urls = await schoolboxUrls.getValue(); 11 11 12 12 if (settings.global && urls.includes(window.location.origin)) { 13 13 // inject themes
+3 -3
src/utils/plugin.ts
··· 3 3 injectLogic: (pluginId: PluginId) => void, 4 4 elementsToWaitFor: string[] = [], 5 5 ) { 6 - let plugin = await plugins[pluginId].getValue(); 6 + const plugin = await plugins[pluginId].getValue(); 7 7 8 8 logger.info(`${PLUGIN_INFO[pluginId].name}: ${plugin.toggle ? "enabled" : "disabled"}`); 9 9 10 - let settings = await globalSettings.getValue(); 11 - let urls = await schoolboxUrls.getValue(); 10 + const settings = await globalSettings.getValue(); 11 + const urls = await schoolboxUrls.getValue(); 12 12 13 13 if (plugin && typeof window !== "undefined" && urls.includes(window.location.origin)) { 14 14 if (settings.global && settings.plugins && plugin.toggle) {
+3 -3
src/utils/snippet.ts
··· 1 1 export async function defineStSnippet(snippetId: SnippetId, styleText: string) { 2 - let snippet = await snippets[snippetId].getValue(); 2 + const snippet = await snippets[snippetId].getValue(); 3 3 4 4 logger.info(`${SNIPPET_INFO[snippetId].name}: ${snippet.toggle ? "enabled" : "disabled"}`); 5 5 6 - let settings = await globalSettings.getValue(); 7 - let urls = await schoolboxUrls.getValue(); 6 + const settings = await globalSettings.getValue(); 7 + const urls = await schoolboxUrls.getValue(); 8 8 9 9 if (snippet && typeof window !== "undefined" && urls.includes(window.location.origin)) { 10 10 if (settings.global && settings.snippets && snippet.toggle) {
+2
src/utils/storage.ts
··· 25 25 }); 26 26 27 27 // Plugins 28 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 29 export const plugins: Record<Types.PluginId, WxtStorageItem<Types.PluginGeneric, any>> = { 29 30 subheader: storage.defineItem<Types.PluginGeneric>("local:plugin-subheader", { 30 31 fallback: { ··· 70 71 }; 71 72 72 73 // Snippets 74 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 73 75 export const snippets: Record<Types.SnippetId, WxtStorageItem<Types.SnippetGeneric, any>> = { 74 76 hidePfp: storage.defineItem<Types.SnippetGeneric>("local:snippet-hidePfp", { 75 77 fallback: {
+6 -6
src/utils/utils.ts
··· 1 - import { flavors, flavorEntries } from "@catppuccin/palette"; 1 + import { flavorEntries } from "@catppuccin/palette"; 2 2 import { WxtStorageItem } from "wxt/storage"; 3 3 4 4 export async function populateItems<T extends ItemId>( ··· 76 76 if (logo.disable) { 77 77 return; 78 78 } 79 - let style = document.createElement("style"); 79 + const style = document.createElement("style"); 80 80 style.classList.add("schooltape"); 81 81 if (logo.adaptive) { 82 82 style.textContent = `a.logo > img { display: none !important; } a.logo { display: flex; align-items: center; justify-content: center; }`; 83 - let span = document.createElement("span"); 83 + const span = document.createElement("span"); 84 84 span.style.mask = `url("${url}") no-repeat center`; 85 85 span.style.maskSize = "100% 100%"; 86 86 span.style.backgroundColor = "hsl(var(--ctp-accent))"; ··· 101 101 102 102 export function injectStylesheet(url: any) { 103 103 logger.info(`[content-utils] Injecting stylesheet: ${url}`); 104 - let link = document.createElement("link"); 104 + const link = document.createElement("link"); 105 105 link.rel = "stylesheet"; 106 106 link.href = browser.runtime.getURL(url); 107 107 link.classList.add("schooltape"); ··· 112 112 logger.info("[content-utils] Injecting snippets"); 113 113 // user snippets 114 114 Object.keys(userSnippets).forEach((snippetId) => { 115 - let userSnippet = userSnippets[snippetId]; 115 + const userSnippet = userSnippets[snippetId]; 116 116 if (userSnippet.toggle) { 117 117 fetch(`https://gist.githubusercontent.com/${userSnippet.author}/${snippetId}/raw`) 118 118 .then((response) => response.text()) 119 119 .then((css) => { 120 - let style = document.createElement("style"); 120 + const style = document.createElement("style"); 121 121 style.textContent = css; 122 122 style.classList.add("schooltape"); 123 123 document.head.appendChild(style);