schoolbox web extension :)
at refactor/plugins/class 170 lines 5.6 kB view raw
1import { browser } from "#imports"; 2import { flavorEntries } from "@catppuccin/palette"; 3import { logger } from "./logger"; 4import type { LogoInfo } from "./storage"; 5import { globalSettings } from "./storage"; 6 7export const dataAttr = (id: string) => `[data-schooltape="${id}"]`; 8export function setDataAttr(el: HTMLElement, id: string) { 9 el.dataset.schooltape = id; 10} 11 12export function injectInlineStyles(styleText: string, id: string) { 13 logger.info(`injecting styles with id ${id}`); 14 const style = document.createElement("style"); 15 style.textContent = styleText; 16 setDataAttr(style, `inline-${id}`); 17 document.head.append(style); 18 // logger.info(`injected styles with id ${id}`); 19} 20 21export function uninjectInlineStyles(id: string) { 22 logger.info(`uninjecting styles with id ${id}`); 23 const style = document.querySelector(dataAttr(`inline-${id}`)); 24 if (style) document.head.removeChild(style); 25} 26 27export async function injectCatppuccin() { 28 const settings = await globalSettings.get(); 29 const flavour = settings.themeFlavour; 30 const accent = settings.themeAccent; 31 32 logger.info(`injecting catppuccin: ${flavour} ${accent}`); 33 let styleText = ":root {"; 34 const flavourArray = flavorEntries.find((entry) => entry[0] === flavour); 35 if (flavourArray) { 36 flavourArray[1].colorEntries.map(([colorName, { hsl }]) => { 37 styleText += `--ctp-${colorName}: ${hsl.h}, ${hsl.s * 100}%, ${hsl.l * 100}%;\n`; 38 if (colorName === accent) { 39 styleText += `--ctp-accent: ${hsl.h}, ${hsl.s * 100}%, ${hsl.l * 100}%;\n`; 40 } 41 }); 42 } 43 styleText += "}"; 44 injectInlineStyles(styleText, "catppuccin"); 45} 46 47export function uninjectCatppuccin() { 48 uninjectInlineStyles("catppuccin"); 49} 50 51export function injectLogo(logo: LogoInfo, setAsFavicon: boolean) { 52 let url = logo.url; 53 if (!url.startsWith("http")) { 54 // eslint-disable-next-line @typescript-eslint/no-explicit-any 55 url = browser.runtime.getURL(url as any); 56 } 57 logger.info(`injecting logo: ${logo.name}`); 58 if (logo.disable) { 59 return; 60 } 61 const style = document.createElement("style"); 62 style.classList.add("schooltape"); 63 if (logo.adaptive) { 64 style.textContent = `a.logo > img { display: none !important; } a.logo { display: flex; align-items: center; justify-content: center; }`; 65 const span = document.createElement("span"); 66 span.style.mask = `url("${url}") no-repeat center`; 67 span.style.maskSize = "100% 100%"; 68 span.style.backgroundColor = "hsl(var(--ctp-accent))"; 69 span.style.width = "100%"; 70 span.style.height = "60px"; 71 span.style.display = "block"; 72 window.addEventListener("load", () => { 73 document.querySelectorAll("a.logo").forEach((logo) => { 74 const clonedSpan = span.cloneNode(true); 75 logo.append(clonedSpan); 76 }); 77 }); 78 } else { 79 style.textContent = `a.logo > img { content: url("${url}"); max-width: 30%; width: 100px; }`; 80 } 81 document.head.appendChild(style); 82 83 // inject favicon 84 if (setAsFavicon) { 85 let favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null; 86 if (!favicon) { 87 favicon = document.createElement("link") as HTMLLinkElement; 88 favicon.rel = "icon"; 89 document.head.appendChild(favicon); 90 } 91 favicon.href = url; 92 } 93} 94 95export function injectStylesheet(url: string, id: string) { 96 // check if stylesheet has already been injected 97 const existingLink = document.querySelector(dataAttr(`stylesheet-${id}`)); 98 if (existingLink) return; 99 100 // inject stylesheet 101 logger.info(`injecting stylesheet with id ${id}: ${url}`); 102 const link = document.createElement("link"); 103 link.rel = "stylesheet"; 104 link.href = url; 105 setDataAttr(link, `stylesheet-${id}`); 106 document.head.appendChild(link); 107} 108 109export function uninjectStylesheet(id: string) { 110 logger.info(`uninjecting stylesheet with id ${id}`); 111 112 const link = document.querySelector(dataAttr(`stylesheet-${id}`)); 113 if (link) document.head.removeChild(link); 114} 115 116export async function injectUserSnippet(id: string) { 117 logger.info(`injecting user snippet with id ${id}`); 118 119 const userSnippets = (await globalSettings.get()).userSnippets; 120 const snippet = userSnippets[id]; 121 122 if (!snippet) { 123 logger.error(`user snippet with id ${id} not found, aborting`); 124 return; 125 } 126 127 if (!snippet.toggle) { 128 logger.error(`trying to inject user snippet with id ${id} which is disabled, aborting`); 129 return; 130 } 131 132 // check not already injected 133 if (document.querySelector(dataAttr(`userSnippet-${id}`))) { 134 logger.info(`user snippet with id ${id} already injected, aborting`); 135 return; 136 } 137 138 // inject user snippet 139 const response = await fetch(`https://gist.githubusercontent.com/${snippet.author}/${id}/raw`); 140 const css = await response.text(); 141 const style = document.createElement("style"); 142 143 style.textContent = css; 144 setDataAttr(style, `userSnippet-${id}`); 145 document.head.appendChild(style); 146 147 logger.info(`injected user snippet with id ${id}`); 148} 149 150export function uninjectUserSnippet(id: string) { 151 logger.info(`uninjecting user snippet with id ${id}`); 152 153 const style = document.querySelector(dataAttr(`userSnippet-${id}`)); 154 if (!style) return; 155 156 document.head.removeChild(style); 157 logger.info(`uninjected user snippet with id ${id}`); 158} 159 160export function hasChanged<T>(newValue: T, oldValue: T, keys: (keyof T)[]) { 161 const changed: (keyof T)[] = []; 162 163 for (const key in newValue) { 164 if (Object.prototype.hasOwnProperty.call(newValue, key) && oldValue[key] !== newValue[key]) { 165 changed.push(key); 166 } 167 } 168 169 return keys.some((item) => changed.includes(item)); 170}