schoolbox web extension :)
0
fork

Configure Feed

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

feat: plugin options (#230)

* do stuff

* refactor: tidy up svelte 5 migration

* feat: super scuffed plugin options

* i need to move away from classes, hydrating after deserialisation is
both unelegant, overcomplicated, and probably un-needed

* refactor: not-cursed plugin options

* feat: slider options

* close #232

* feat(plugins/homepageSwitcher): close current tab option

* close #231

* fix(plugins/scrollPeriod): delay

authored by

willow and committed by
GitHub
bdf4767d f882a2c2

+391 -112
bun.lockb

This is a binary file and will not be displayed.

+5 -1
src/entrypoints/plugins/homepageSwitcher.ts
··· 1 1 export default function init() { 2 2 defineStPlugin( 3 3 "homepageSwitcher", 4 - () => { 4 + (_id, storage) => { 5 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 10 const tab = logos[0].href; 11 + const settings = (await storage.getValue()).settings; 12 + if (settings?.toggle?.closeCurrentTabOnSwitch.toggle === true) { 13 + window.close(); 14 + } 11 15 browser.runtime.sendMessage({ toTab: tab }); 12 16 }); 13 17 });
+18 -2
src/entrypoints/plugins/scrollPeriod.ts
··· 1 1 export default function init() { 2 2 defineStPlugin( 3 3 "scrollPeriod", 4 - () => { 4 + async (_id, storage) => { 5 5 const timetable = document.querySelector("[data-timetable-container] div.scrollable"); 6 6 7 7 if (window.location.pathname === "/" && document.getElementsByClassName("timetable")[0]) { 8 8 updateScrollbar(); 9 - setInterval(updateScrollbar, 10000); 9 + const settings = (await storage.getValue()).settings; 10 + 11 + let interval: string | number | NodeJS.Timeout | undefined; 12 + function start() { 13 + interval = setInterval(updateScrollbar, (settings?.slider?.cooldownDuration.value || 10) * 1000); 14 + } 15 + function reset() { 16 + if (interval) { 17 + clearInterval(interval); 18 + } 19 + } 20 + 21 + start(); 22 + 23 + if (settings?.toggle?.resetCooldownOnMouseMove.toggle === true) { 24 + document.addEventListener("mousemove", reset); 25 + } 10 26 } 11 27 12 28 function updateScrollbar() {
+7 -2
src/entrypoints/plugins/tabTitle.ts
··· 1 1 export default function init() { 2 2 defineStPlugin( 3 3 "tabTitle", 4 - () => { 4 + async (_id, storage) => { 5 5 const path = window.location.pathname; 6 6 const titleMap: { [key: string]: string } = { 7 7 "/": "Homepage", ··· 35 35 } else if (path.includes("/learning/due/")) { 36 36 document.title = "Due Work"; 37 37 } else if (path.includes("/homepage/")) { 38 - document.title = document.getElementsByTagName("h1")[0].innerText; 38 + const settings = (await storage.getValue()).settings; 39 + if (settings?.toggle?.showSubjectPrefix.toggle === false) { 40 + document.title = document.getElementsByTagName("h1")[0].innerText.replace(/^.*- /, ""); 41 + } else { 42 + document.title = document.getElementsByTagName("h1")[0].innerText; 43 + } 39 44 } 40 45 }, 41 46 ["h1"],
-12
src/entrypoints/popup/app.css
··· 26 26 @apply after:h-3 after:w-3; 27 27 } 28 28 29 - .slider-input { 30 - @apply absolute left-1/2 h-full w-full -translate-x-1/2 appearance-none rounded-md; 31 - } 32 - 33 - .slider-label { 34 - @apply text-ctp-text relative flex items-center justify-between py-2 text-xl; 35 - } 36 - 37 - .slider-description { 38 - @apply text-ctp-overlay1 group-hover:text-ctp-subtext0 transition-colors duration-500 ease-in-out; 39 - } 40 - 41 29 #flavours button { 42 30 @apply bg-ctp-surface0; 43 31 }
+3 -3
src/entrypoints/popup/components/Footer.svelte
··· 35 35 href="https://github.com/schooltape/schooltape/releases/tag/v{version}">Version: v{version}</a> 36 36 </p> 37 37 <div class="flex"> 38 - <IconBtn title="Wiki" id="wiki" onClick={handleWikiClick} icon={BookText} /> 39 - <IconBtn title="Discord" id="discord" onClick={handleDiscordClick} icon={MessageCircleMore} /> 40 - <IconBtn title="Reset" id="reset" onClick={handleResetClick} icon={RotateCcw} color="red" /> 38 + <IconBtn title="Wiki" id="wiki" onclick={handleWikiClick}><BookText /></IconBtn> 39 + <IconBtn title="Discord" id="discord" onclick={handleDiscordClick}><MessageCircleMore /></IconBtn> 40 + <IconBtn title="Reset" id="reset" onclick={handleResetClick} color="red"><RotateCcw /></IconBtn> 41 41 </div> 42 42 </footer>
+4 -11
src/entrypoints/popup/components/Title.svelte
··· 1 1 <script lang="ts"> 2 - import { createEventDispatcher } from "svelte"; 3 - import Slider from "./inputs/Slider.svelte"; 2 + import Toggle from "./inputs/Toggle.svelte"; 4 3 5 4 interface Props { 5 + update: (toggled: boolean) => void; 6 6 title?: string; 7 7 checked: boolean; 8 8 } 9 9 10 - let { title = "", checked = $bindable() }: Props = $props(); 11 - 12 - const dispatch = createEventDispatcher(); 13 - 14 - function handleChange(event: CustomEvent) { 15 - checked = event.detail.checked; 16 - dispatch("change", { checked }); 17 - } 10 + let { update, title = "", checked }: Props = $props(); 18 11 </script> 19 12 20 13 <label for="theme-toggle" class="relative flex justify-between items-center group p-2 text-xl text-ctp-text"> 21 14 <h2>{title}</h2> 22 - <Slider id="theme-toggle" size="big" bind:checked on:change={handleChange} /> 15 + <Toggle id="theme-toggle" size="big" {checked} {update} /> 23 16 </label>
+9 -14
src/entrypoints/popup/components/inputs/IconBtn.svelte
··· 1 1 <script lang="ts"> 2 + import type { Snippet } from "svelte"; 3 + 2 4 interface Props { 3 - title: any; 4 - id: any; 5 - onClick: any; 6 - icon: any; 5 + children: Snippet; 6 + title: string; 7 + id: string; 8 + onclick: () => void; 7 9 label?: string; 8 10 color?: string; 9 11 } 10 12 11 - let { title, id, onClick, icon, label = "" }: Props = $props(); 13 + let { children, title, id, onclick, label = "" }: Props = $props(); 12 14 </script> 13 15 14 - <button 15 - {title} 16 - {id} 17 - class="flex items-center mx-2 small hover:text-ctp-crust hover:bg-(--ctp-accent)" 18 - onclick={onClick}> 19 - {#if icon} 20 - {@const SvelteComponent = icon} 21 - <SvelteComponent /> 22 - {/if} 16 + <button {title} {id} class="flex items-center ml-4 small hover:text-ctp-crust hover:bg-(--ctp-accent)" {onclick}> 17 + {@render children()} 23 18 {#if label} 24 19 <span class="ml-3">{label}</span> 25 20 {/if}
+157 -26
src/entrypoints/popup/components/inputs/Slider.svelte
··· 1 1 <script lang="ts"> 2 - import { createEventDispatcher } from "svelte"; 3 - 4 2 interface Props { 5 3 id: string; 6 - checked: boolean; 7 - size?: "big" | "small"; 8 - text?: string; 4 + min: number; 5 + max: number; 6 + value: number; 7 + update: (value: number) => void; 8 + name?: string; 9 9 description?: string; 10 10 } 11 11 12 - let { 13 - id, 14 - checked = $bindable(), 15 - size = "big", 16 - text = "", 17 - description = "" 18 - }: Props = $props(); 12 + let { update, id, min, max, value, name = "", description = "" }: Props = $props(); 13 + let currentValue = $state(value); 14 + </script> 15 + 16 + <div class="space-y-2 mt-4"> 17 + {#if name} 18 + <label for={id} class="block text-ctp-text">{name}</label> 19 + {/if} 20 + <div class="flex items-center gap-4"> 21 + <input 22 + style="--min: {min}; --max: {max}; --value: {currentValue};" 23 + type="range" 24 + onchange={(event: Event) => { 25 + const target = event.target as HTMLInputElement; 26 + update(parseInt(target.value)); 27 + }} 28 + oninput={(event: Event) => { 29 + const target = event.target as HTMLInputElement; 30 + currentValue = parseInt(target.value); 31 + }} 32 + {id} 33 + {min} 34 + {max} 35 + {value} 36 + class="styled-slider slider-progress" /> 37 + <span id={id + "-value"} class="text-sm font-medium text-ctp-text">{currentValue}</span> 38 + </div> 39 + {#if description} 40 + <p class="text-ctp-overlay1">{description}</p> 41 + {/if} 42 + </div> 43 + 44 + <style lang="postcss"> 45 + @reference "tailwindcss"; 46 + @reference "@catppuccin/tailwindcss/mocha.css"; 47 + 48 + /*generated with Input range slider CSS style generator (version 20211225) 49 + https://toughengineer.github.io/demo/slider-styler*/ 50 + input[type="range"].styled-slider { 51 + height: 1.8em; 52 + -webkit-appearance: none; 53 + appearance: none; 54 + } 55 + 56 + /*progress support*/ 57 + input[type="range"].styled-slider.slider-progress { 58 + --range: calc(var(--max) - var(--min)); 59 + --ratio: calc((var(--value) - var(--min)) / var(--range)); 60 + --sx: calc(0.5 * 1em + var(--ratio) * (100% - 1em)); 61 + } 62 + 63 + input[type="range"].styled-slider:focus { 64 + outline: none; 65 + } 66 + 67 + /*webkit*/ 68 + input[type="range"].styled-slider::-webkit-slider-thumb { 69 + -webkit-appearance: none; 70 + width: 1em; 71 + height: 1em; 72 + border-radius: 1em; 73 + background: var(--ctp-accent); 74 + border: none; 75 + box-shadow: none; 76 + margin-top: calc(0.4em * 0.5 - 1em * 0.5); 77 + } 78 + 79 + input[type="range"].styled-slider::-webkit-slider-runnable-track { 80 + height: 0.4em; 81 + border: none; 82 + border-radius: 0.5em; 83 + @apply bg-ctp-surface0; 84 + box-shadow: none; 85 + } 86 + 87 + input[type="range"].styled-slider::-webkit-slider-thumb:hover { 88 + @apply bg-(--ctp-accent) brightness-90; 89 + } 90 + 91 + input[type="range"].styled-slider.slider-progress::-webkit-slider-runnable-track { 92 + background: 93 + linear-gradient(var(--ctp-accent), var(--ctp-accent)) 0 / var(--sx) 100% no-repeat, 94 + theme("colors.ctp-surface0"); 95 + } 96 + 97 + /*mozilla*/ 98 + input[type="range"].styled-slider::-moz-range-thumb { 99 + width: 1em; 100 + height: 1em; 101 + border-radius: 1em; 102 + background: var(--ctp-accent); 103 + border: none; 104 + box-shadow: none; 105 + } 106 + 107 + input[type="range"].styled-slider::-moz-range-track { 108 + height: 0.4em; 109 + border: none; 110 + border-radius: 0.5em; 111 + @apply bg-ctp-surface0; 112 + box-shadow: none; 113 + } 114 + 115 + input[type="range"].styled-slider::-moz-range-thumb:hover { 116 + @apply bg-(--ctp-accent) brightness-90; 117 + } 118 + 119 + input[type="range"].styled-slider.slider-progress::-moz-range-track { 120 + background: 121 + linear-gradient(var(--ctp-accent), var(--ctp-accent)) 0 / var(--sx) 100% no-repeat, 122 + theme("colors.ctp-surface0"); 123 + } 124 + 125 + /*ms*/ 126 + input[type="range"].styled-slider::-ms-fill-upper { 127 + background: transparent; 128 + border-color: transparent; 129 + } 130 + 131 + input[type="range"].styled-slider::-ms-fill-lower { 132 + background: transparent; 133 + border-color: transparent; 134 + } 19 135 20 - const dispatch = createEventDispatcher(); 136 + input[type="range"].styled-slider::-ms-thumb { 137 + width: 1em; 138 + height: 1em; 139 + border-radius: 1em; 140 + background: var(--ctp-accent); 141 + border: none; 142 + box-shadow: none; 143 + margin-top: 0; 144 + box-sizing: border-box; 145 + } 21 146 22 - function handleChange(event: Event) { 23 - const input = event.target as HTMLInputElement; 24 - checked = input.checked; 25 - dispatch("change", { checked }); 147 + input[type="range"].styled-slider::-ms-track { 148 + height: 0.4em; 149 + border-radius: 0.5em; 150 + @apply bg-ctp-surface0; 151 + border: none; 152 + box-shadow: none; 153 + box-sizing: border-box; 26 154 } 27 - </script> 28 155 29 - <label class="slider-label group"> 30 - <h4 class="text-ctp-text">{text}</h4> 31 - <input {id} type="checkbox" class="peer slider-input" bind:checked onchange={handleChange} /> 32 - <span class="slider {size}"></span> 33 - </label> 156 + input[type="range"].styled-slider::-ms-thumb:hover { 157 + @apply bg-(--ctp-accent) brightness-90; 158 + } 34 159 35 - <div class="slider-description"> 36 - {description} 37 - </div> 160 + input[type="range"].styled-slider.slider-progress::-ms-fill-lower { 161 + height: 0.4em; 162 + border-radius: 0.5em 0 0 0.5em; 163 + margin: 0; 164 + background: var(--ctp-accent); 165 + border: none; 166 + border-right-width: 0; 167 + } 168 + </style>
+3 -3
src/entrypoints/popup/components/inputs/TextInput.svelte
··· 1 1 <script lang="ts"> 2 2 interface Props { 3 + onclick: (event: Event) => void; 3 4 id: string; 4 5 placeholder: string; 5 6 value: string; 6 - onClick: (event: Event) => void; 7 7 label: string; 8 8 } 9 9 10 - let { id, placeholder, value = $bindable(), onClick, label }: Props = $props(); 10 + let { onclick, id, placeholder, value = $bindable(), label }: Props = $props(); 11 11 </script> 12 12 13 13 <div class="flex justify-center items-center w-full"> 14 14 <input {id} class="w-full p-2 rounded-l-xl bg-ctp-surface0 text-ctp-text" bind:value {placeholder} type="text" /> 15 - <button class="p-2 rounded-r-xl bg-(--ctp-accent) text-ctp-crust" type="submit" onclick={onClick}>{label}</button> 15 + <button class="p-2 rounded-r-xl bg-(--ctp-accent) text-ctp-crust" type="submit" {onclick}>{label}</button> 16 16 </div>
+35
src/entrypoints/popup/components/inputs/Toggle.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from "svelte"; 3 + 4 + interface Props { 5 + update: (toggled: boolean) => void; 6 + checked: boolean; 7 + id: string; 8 + size?: "big" | "small"; 9 + text?: string; 10 + description?: string; 11 + children?: Snippet; 12 + } 13 + 14 + let { update, checked, id, size = "big", text = "", description = "", children }: Props = $props(); 15 + </script> 16 + 17 + <label class="group text-ctp-text relative flex items-center justify-between py-2"> 18 + <h4 class="text-ctp-text">{text}</h4> 19 + <input 20 + {id} 21 + type="checkbox" 22 + class="peer absolute left-1/2 h-full w-full -translate-x-1/2 appearance-none rounded-md" 23 + {checked} 24 + onchange={(event: Event) => { 25 + const target = event.target as HTMLInputElement; 26 + update(target.checked); 27 + }} /> 28 + <span class="slider {size}"></span> 29 + </label> 30 + 31 + <div 32 + class="flex items-center text-ctp-overlay1 group-hover:text-ctp-subtext0 transition-colors duration-500 ease-in-out"> 33 + <div>{description}</div> 34 + <div>{@render children?.()}</div> 35 + </div>
+69 -8
src/entrypoints/popup/routes/Plugins.svelte
··· 1 1 <script lang="ts"> 2 2 import Title from "../components/Title.svelte"; 3 + import { globalSettings } from "#imports"; 4 + import IconBtn from "../components/inputs/IconBtn.svelte"; 5 + import { Settings } from "lucide-svelte"; 6 + import Modal from "../components/Modal.svelte"; 7 + import ToggleComponent from "../components/inputs/Toggle.svelte"; 3 8 import Slider from "../components/inputs/Slider.svelte"; 9 + 10 + let showModal = $state(false); 11 + let selectedPluginId: PluginId | undefined = $state(); 12 + let selectedPlugin: StorageState<globalThis.PluginGeneric, globalThis.PluginInfo> | undefined = $derived.by(() => { 13 + if (selectedPluginId !== undefined) { 14 + return plugins[selectedPluginId]; 15 + } 16 + }); 4 17 </script> 5 18 6 19 <div id="card"> 7 20 <Title 8 21 title="Plugins" 9 - bind:checked={globalSettings.state.plugins} 10 - on:change={(event: CustomEvent) => { 11 - globalSettings.set({ plugins: event.detail.checked }); 22 + checked={globalSettings.state.plugins} 23 + update={(toggled: boolean) => { 24 + globalSettings.set({ plugins: toggled }); 12 25 }} /> 13 26 14 27 <div class="plugins-container"> 15 28 {#each Object.entries(plugins) as [id, plugin] (id)} 16 29 <div class="my-4 group"> 17 - <Slider 30 + <ToggleComponent 18 31 {id} 19 - bind:checked={plugin.state.toggle} 20 - on:change={(event: CustomEvent) => { 21 - plugin.set({ toggle: event.detail.checked }); 32 + checked={plugin.state.toggle} 33 + update={(toggled: boolean) => { 34 + plugin.set({ toggle: toggled }); 22 35 }} 23 36 text={plugin.info?.name} 24 37 description={plugin.info?.description} 25 - size="small" /> 38 + size="small"> 39 + {#if plugin.state.settings} 40 + <IconBtn 41 + title="Wiki" 42 + id="wiki" 43 + onclick={() => { 44 + selectedPluginId = id as PluginId; 45 + showModal = true; 46 + }}><Settings /></IconBtn> 47 + {/if} 48 + </ToggleComponent> 26 49 </div> 27 50 {/each} 28 51 </div> 29 52 </div> 53 + 54 + {#if selectedPlugin} 55 + <Modal bind:showModal> 56 + {#snippet header()} 57 + <h2 class="mb-4 text-xl">{selectedPlugin.info?.name}</h2> 58 + {/snippet} 59 + {#if selectedPlugin.state.settings?.toggle} 60 + {#each Object.entries(selectedPlugin.state.settings.toggle) as [id, setting] (id)} 61 + <ToggleComponent 62 + text={setting.name} 63 + description={setting.description} 64 + size="small" 65 + checked={setting.toggle} 66 + update={async () => { 67 + const settings = await selectedPlugin.storage.getValue(); 68 + if (!settings.settings?.toggle) return; 69 + settings.settings.toggle[id].toggle = !settings.settings.toggle[id].toggle; 70 + await selectedPlugin.storage.setValue(settings); 71 + }} 72 + {id} /> 73 + {/each} 74 + {/if} 75 + 76 + {#if selectedPlugin.state.settings?.slider} 77 + {#each Object.entries(selectedPlugin.state.settings.slider) as [id, setting] (id)} 78 + <Slider 79 + {id} 80 + update={async (newValue) => { 81 + const settings = await selectedPlugin.storage.getValue(); 82 + if (!settings.settings?.slider) return; 83 + settings.settings.slider[id].value = newValue; 84 + await selectedPlugin.storage.setValue(settings); 85 + }} 86 + {...setting} /> 87 + {/each} 88 + {/if} 89 + </Modal> 90 + {/if}
+15 -14
src/entrypoints/popup/routes/Snippets.svelte
··· 1 1 <script lang="ts"> 2 2 import Title from "../components/Title.svelte"; 3 - import Slider from "../components/inputs/Slider.svelte"; 3 + import Toggle from "../components/inputs/Toggle.svelte"; 4 4 import TextInput from "../components/inputs/TextInput.svelte"; 5 + import { globalSettings } from "#imports"; 5 6 6 7 let snippetURL = $state(""); 7 8 ··· 36 37 <div id="card"> 37 38 <Title 38 39 title="Snippets" 39 - bind:checked={globalSettings.state.snippets} 40 - on:change={(event: CustomEvent) => { 41 - globalSettings.set({ snippets: event.detail.checked }); 40 + checked={globalSettings.state.snippets} 41 + update={(toggled: boolean) => { 42 + globalSettings.set({ snippets: toggled }); 42 43 }} /> 43 44 44 45 <div class="snippets-container w-full"> 45 46 {#each Object.entries(snippets) as [id, snippet] (id)} 46 47 <div class="my-4 group w-full"> 47 - <Slider 48 + <Toggle 48 49 {id} 49 - bind:checked={snippet.state.toggle} 50 - on:change={(event: CustomEvent) => { 51 - snippet.set({ toggle: event.detail.checked }); 50 + checked={snippet.state.toggle} 51 + update={(toggled: boolean) => { 52 + snippet.set({ toggle: toggled }); 52 53 }} 53 54 text={snippet.info?.name} 54 55 description={snippet.info?.description} ··· 67 68 >. 68 69 </p> 69 70 <!-- input box to make new snippet --> 70 - <TextInput id="snippet-input" bind:value={snippetURL} placeholder="Gist URL" label="Add" onClick={addUserSnippet} /> 71 + <TextInput id="snippet-input" bind:value={snippetURL} placeholder="Gist URL" label="Add" onclick={addUserSnippet} /> 71 72 </div> 72 73 73 74 <div class="user-snippets-container w-full"> 74 - {#each Object.entries(globalSettings.state.userSnippets) as [id, snippet] (id)} 75 + {#each Object.entries(globalSettings.state.userSnippets as Record<string, UserSnippet>) as [id, snippet] (id)} 75 76 <div class="my-4 group w-full"> 76 - <Slider 77 + <Toggle 77 78 {id} 78 - bind:checked={snippet.toggle} 79 - on:change={async (event: CustomEvent) => { 79 + checked={snippet.toggle} 80 + update={async (toggled: boolean) => { 80 81 let settings = await globalSettings.storage.getValue(); 81 - settings.userSnippets[id].toggle = event.detail.checked; 82 + settings.userSnippets[id].toggle = toggled; 82 83 await globalSettings.storage.setValue(settings); 83 84 }} 84 85 text={snippet.name}
+6 -9
src/entrypoints/popup/routes/Themes.svelte
··· 3 3 import Modal from "../components/Modal.svelte"; 4 4 import IconBtn from "../components/inputs/IconBtn.svelte"; 5 5 import { Layers3 } from "lucide-svelte"; 6 + import { globalSettings } from "#imports"; 6 7 import { PublicPath } from "wxt/browser"; 7 8 8 9 const flavours = ["latte", "frappe", "macchiato", "mocha"]; ··· 60 61 <div id="card"> 61 62 <Title 62 63 title="Themes" 63 - bind:checked={globalSettings.state.themes} 64 - on:change={(event: CustomEvent) => { 65 - globalSettings.set({ themes: event.detail.checked }); 64 + checked={globalSettings.state.themes} 65 + update={(toggled: boolean) => { 66 + globalSettings.set({ themes: toggled }); 66 67 }} /> 67 68 68 69 <div id="flavours" class="flex my-6 py-2 rounded-xl text-ctp-text"> ··· 91 92 {/each} 92 93 </div> 93 94 94 - <IconBtn 95 - title="Choose icon" 96 - id="choose-icon" 97 - onClick={() => (showModal = true)} 98 - icon={Layers3} 99 - label="Choose an icon" /> 95 + <IconBtn title="Choose icon" id="choose-icon" onclick={() => (showModal = true)} label="Choose an icon" 96 + ><Layers3 /></IconBtn> 100 97 </div>
+4 -1
src/utils/periodUtils.ts
··· 17 17 export class Period { 18 18 header: PeriodHeader; 19 19 data: PeriodData; 20 + index: number; 20 21 21 - constructor(header: PeriodHeader, data: PeriodData) { 22 + constructor(header: PeriodHeader, data: PeriodData, index: number) { 22 23 this.header = header; 23 24 this.data = data; 25 + this.index = index; 24 26 } 25 27 26 28 inProgress(): boolean { ··· 110 112 id: dataId, 111 113 room: dataRoom, 112 114 }, 115 + index, 113 116 ); 114 117 } 115 118
+4 -4
src/utils/plugin.ts
··· 1 1 export async function defineStPlugin( 2 2 pluginId: PluginId, 3 - injectLogic: (pluginId: PluginId) => void, 3 + injectLogic: (pluginId: PluginId, pluginStorage: WxtStorageItem<globalThis.PluginGeneric, {}>) => void, 4 4 elementsToWaitFor: string[] = [], 5 5 ) { 6 6 const plugin = await plugins[pluginId].storage.getValue(); ··· 20 20 if (allElementsPresent) { 21 21 observer.disconnect(); 22 22 logger.info(`all elements present, injecting plugin: ${plugins[pluginId].info?.name}`); 23 - injectLogic(pluginId); 23 + injectLogic(pluginId, plugins[pluginId].storage); 24 24 } 25 25 }); 26 26 ··· 31 31 if (allElementsPresent) { 32 32 observer.disconnect(); 33 33 logger.info(`all elements already present, injecting plugin: ${plugins[pluginId].info?.name}`); 34 - injectLogic(pluginId); 34 + injectLogic(pluginId, plugins[pluginId].storage); 35 35 } 36 36 } else { 37 37 // no elements to wait for 38 38 logger.info(`injecting plugin: ${plugins[pluginId].info?.name}`); 39 - injectLogic(pluginId); 39 + injectLogic(pluginId, plugins[pluginId].storage); 40 40 } 41 41 }; 42 42
+34 -1
src/utils/storage.ts
··· 58 58 storage.defineItem<Types.PluginGeneric>("local:plugin-scrollPeriod", { 59 59 fallback: { 60 60 toggle: true, 61 + settings: { 62 + toggle: { 63 + resetCooldownOnMouseMove: { 64 + name: "Reset on mouse move", 65 + description: "Whether to reset the scrolling cooldown when you move your mouse", 66 + toggle: true, 67 + } as ToggleSetting, 68 + }, 69 + slider: { 70 + cooldownDuration: { 71 + name: "Cooldown duration (s)", 72 + description: "How long to wait before scrolling", 73 + min: 1, 74 + max: 60, 75 + value: 10, 76 + }, 77 + }, 78 + }, 61 79 }, 62 80 }), 63 81 { ··· 92 110 fallback: { 93 111 toggle: true, 94 112 settings: { 95 - showSubjectPrefix: true, 113 + toggle: { 114 + showSubjectPrefix: { 115 + name: "Show homepage prefix", 116 + description: 'e.g. "ENG - VCE English 1 & 2" becomes "VCE English 1 & 2"', 117 + toggle: true, 118 + } as Types.ToggleSetting, 119 + }, 96 120 }, 97 121 }, 98 122 }), ··· 105 129 storage.defineItem<Types.PluginGeneric>("local:plugin-homepageSwitcher", { 106 130 fallback: { 107 131 toggle: true, 132 + settings: { 133 + toggle: { 134 + closeCurrentTabOnSwitch: { 135 + name: "Close current tab", 136 + description: "When switching to another tab, close the current one", 137 + toggle: false, 138 + } as Types.ToggleSetting, 139 + }, 140 + }, 108 141 }, 109 142 }), 110 143 {
+18 -1
src/utils/types.ts
··· 53 53 | "homepageSwitcher"; 54 54 55 55 export interface PluginInfo extends ItemInfo {} 56 + 57 + interface Setting { 58 + name: string; 59 + description?: string; 60 + } 61 + export interface ToggleSetting extends Setting { 62 + toggle: boolean; 63 + } 64 + export interface SliderSetting extends Setting { 65 + min: number; 66 + max: number; 67 + value: number; 68 + } 69 + 56 70 export interface PluginGeneric extends ItemGeneric { 57 - settings?: any; // temporary until plugin options is implemented 71 + settings?: { 72 + toggle?: Record<string, ToggleSetting>; 73 + slider?: Record<string, SliderSetting>; 74 + }; 58 75 } 59 76 60 77 export interface TabTitle extends ItemGeneric {