tangled
alpha
login
or
join now
42willow.github.io
/
schooltape
schoolbox web extension :)
0
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
refactor/storage
refactor/plugins/class
main
feat/plugins/quick-switcher
feat/plugins/change-logo
feat/hot-reload
feat/css-hot-reload
no tags found
compare:
refactor/storage
refactor/plugins/class
main
feat/plugins/quick-switcher
feat/plugins/change-logo
feat/hot-reload
feat/css-hot-reload
no tags found
go
+287
-161
11 changed files
expand all
collapse all
unified
split
src
entrypoints
plugins
changeLogo
Menu.svelte
index.ts
plugins.content.ts
popup
components
inputs
Toggle.svelte
routes
Themes.svelte
start.content.ts
utils
constants.ts
index.ts
plugin.ts
storage
global.ts
types.ts
+34
src/entrypoints/plugins/changeLogo/Menu.svelte
···
1
1
+
<script lang="ts">
2
2
+
import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte";
3
3
+
import type { Settings } from ".";
4
4
+
import { logos } from ".";
5
5
+
6
6
+
let { settings }: { settings: Settings } = $props();
7
7
+
</script>
8
8
+
9
9
+
<div class="grid grid-cols-3 gap-4">
10
10
+
{#await logos then logos}
11
11
+
{#each Object.entries(logos) as [id, logo] (id)}
12
12
+
<button
13
13
+
onclick={() => settings.logo.set({ id: id as keyof typeof logos })}
14
14
+
class:highlight={settings.logo.state.id === id}
15
15
+
class="flex flex-col rounded-lg border border-(--ctp-accent) p-2">
16
16
+
<span>{logo.name}</span>
17
17
+
<div class="flex h-full w-full items-center justify-center">
18
18
+
<img src={logo.url} alt="Logo" class="mt-2 w-16" />
19
19
+
</div>
20
20
+
</button>
21
21
+
{/each}
22
22
+
{/await}
23
23
+
</div>
24
24
+
25
25
+
<div class="mt-2">
26
26
+
<Toggle
27
27
+
text="Set as tab favicon"
28
28
+
update={(toggle) => {
29
29
+
settings.setAsFavicon.set({ toggle });
30
30
+
}}
31
31
+
checked={settings.setAsFavicon.state.toggle}
32
32
+
size="small"
33
33
+
id="setAsFavicon" />
34
34
+
</div>
+171
src/entrypoints/plugins/changeLogo/index.ts
···
1
1
+
import { browser } from "#imports";
2
2
+
import { hasChanged, injectInlineStyles, uninjectInlineStyles } from "@/utils";
3
3
+
import { logger } from "@/utils/logger";
4
4
+
import { Plugin } from "@/utils/plugin";
5
5
+
import type { Toggle } from "@/utils/storage";
6
6
+
import { globalSettings } from "@/utils/storage";
7
7
+
import type { StorageState } from "@/utils/storage/state.svelte";
8
8
+
import { flavors } from "@catppuccin/palette";
9
9
+
import type { Unwatch } from "wxt/utils/storage";
10
10
+
import schoolbox from "/schoolbox.svg?raw";
11
11
+
12
12
+
const ID = "changeLogo";
13
13
+
const PLUGIN_ID = `plugin-${ID}`;
14
14
+
15
15
+
let originalFavicon: string | null = null;
16
16
+
let unwatch: Unwatch | null = null;
17
17
+
18
18
+
export const logos = buildLogos({
19
19
+
schooltape: {
20
20
+
name: "Schooltape",
21
21
+
url: "schooltape.svg",
22
22
+
},
23
23
+
"schooltape-rainbow": {
24
24
+
name: "ST Rainbow",
25
25
+
url: "schooltape-ctp.svg",
26
26
+
},
27
27
+
"schooltape-legacy": {
28
28
+
name: "ST Legacy",
29
29
+
url: "https://schooltape.github.io/schooltape-legacy.svg",
30
30
+
},
31
31
+
catppuccin: {
32
32
+
name: "Catppuccin",
33
33
+
url: "https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/logos/exports/1544x1544_circle.png",
34
34
+
},
35
35
+
schoolbox: {
36
36
+
name: "Schoolbox",
37
37
+
raw: schoolbox,
38
38
+
},
39
39
+
});
40
40
+
41
41
+
export type Settings = {
42
42
+
setAsFavicon: StorageState<Toggle>;
43
43
+
logo: StorageState<{ id: keyof Awaited<typeof logos> }>;
44
44
+
};
45
45
+
46
46
+
interface LogoInfo {
47
47
+
name: string;
48
48
+
url: string;
49
49
+
}
50
50
+
type ImageSource = { name: string; url: string; raw?: never } | { name: string; url?: never; raw: string };
51
51
+
52
52
+
export default new Plugin<Settings>(
53
53
+
{
54
54
+
id: ID,
55
55
+
name: "Change Logo",
56
56
+
description: "Changes the Schoolbox logo to a logo of your choice.",
57
57
+
},
58
58
+
true,
59
59
+
{
60
60
+
setAsFavicon: { toggle: false },
61
61
+
logo: { id: "schooltape-rainbow" },
62
62
+
},
63
63
+
async (settings) => {
64
64
+
await inject(settings);
65
65
+
66
66
+
// add watcher to reload logo
67
67
+
unwatch = globalSettings.watch((newValue, oldValue) => {
68
68
+
if (hasChanged(newValue, oldValue, ["themeFlavour", "themeAccent"])) {
69
69
+
uninject();
70
70
+
inject(settings);
71
71
+
}
72
72
+
});
73
73
+
},
74
74
+
() => {
75
75
+
uninject();
76
76
+
77
77
+
// remove watcher
78
78
+
if (unwatch) {
79
79
+
unwatch();
80
80
+
unwatch = null;
81
81
+
}
82
82
+
},
83
83
+
[".logo"],
84
84
+
);
85
85
+
86
86
+
async function inject(settings: Settings) {
87
87
+
const resolvedLogos = await logos;
88
88
+
const logoId = (await settings.logo.get()).id;
89
89
+
90
90
+
injectLogo(resolvedLogos[logoId]);
91
91
+
92
92
+
if ((await settings.setAsFavicon.get()).toggle) {
93
93
+
injectFavicon(resolvedLogos[logoId]);
94
94
+
}
95
95
+
}
96
96
+
97
97
+
function uninject() {
98
98
+
uninjectLogo();
99
99
+
uninjectFavicon();
100
100
+
}
101
101
+
102
102
+
function injectLogo(logo: LogoInfo): void {
103
103
+
logger.info(`injecting logo: ${logo.name}`);
104
104
+
105
105
+
injectInlineStyles(
106
106
+
`a.logo > img { content: url("${logo.url}"); max-width: 30%; width: 100px; }`,
107
107
+
`${PLUGIN_ID}-logo`,
108
108
+
);
109
109
+
}
110
110
+
111
111
+
function uninjectLogo() {
112
112
+
logger.info("uninjecting logo...");
113
113
+
114
114
+
uninjectInlineStyles(`${PLUGIN_ID}-logo`);
115
115
+
}
116
116
+
117
117
+
function injectFavicon(logo: LogoInfo) {
118
118
+
logger.info(`injecting favicon: ${logo.name}`);
119
119
+
120
120
+
let favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
121
121
+
if (!favicon) {
122
122
+
favicon = document.createElement("link") as HTMLLinkElement;
123
123
+
favicon.rel = "icon";
124
124
+
document.head.appendChild(favicon);
125
125
+
}
126
126
+
127
127
+
originalFavicon = favicon?.href;
128
128
+
favicon.href = logo.url;
129
129
+
}
130
130
+
131
131
+
function uninjectFavicon() {
132
132
+
logger.info("uninjecting favicon...");
133
133
+
134
134
+
const favicon = document.querySelector<HTMLLinkElement>("link[rel~='icon']");
135
135
+
if (favicon && originalFavicon) {
136
136
+
favicon.href = originalFavicon;
137
137
+
originalFavicon = null;
138
138
+
}
139
139
+
}
140
140
+
141
141
+
async function buildLogos<T extends Record<string, ImageSource>>(logos: T): Promise<Record<keyof T, LogoInfo>> {
142
142
+
const output: Record<keyof T, LogoInfo> = {} as Record<keyof T, LogoInfo>;
143
143
+
144
144
+
for (const [key, value] of Object.entries(logos) as [keyof T, ImageSource][]) {
145
145
+
let url;
146
146
+
147
147
+
if (value.url) {
148
148
+
if (value.url.startsWith("http")) {
149
149
+
url = value.url;
150
150
+
} else {
151
151
+
// @ts-expect-error unlisted CSS not a PublicPath
152
152
+
url = browser.runtime.getURL(value.url);
153
153
+
}
154
154
+
} else if (value.raw) {
155
155
+
const settings = await globalSettings.get();
156
156
+
const flavour = settings.themeFlavour;
157
157
+
const accent = settings.themeAccent;
158
158
+
const accentHex = flavors[flavour].colors[accent].hex;
159
159
+
url = `data:image/svg+xml;utf8,${encodeURIComponent(value.raw.replaceAll("currentColor", accentHex))}`;
160
160
+
}
161
161
+
162
162
+
if (!url) throw new Error("error getting URL for logo");
163
163
+
164
164
+
output[key] = {
165
165
+
name: value.name,
166
166
+
url,
167
167
+
};
168
168
+
}
169
169
+
170
170
+
return output;
171
171
+
}
+11
-1
src/entrypoints/plugins.content.ts
···
1
1
import { defineContentScript } from "#imports";
2
2
import { EXCLUDE_MATCHES } from "@/utils/constants";
3
3
+
import changeLogo from "./plugins/changeLogo";
3
4
import homepageSwitcher from "./plugins/homepageSwitcher";
4
5
import modernIcons from "./plugins/modernIcons";
5
6
import progressBar from "./plugins/progressBar";
···
8
9
import subheader from "./plugins/subheader";
9
10
import tabTitle from "./plugins/tabTitle";
10
11
11
11
-
export const plugins = [subheader, scrollSegments, scrollPeriod, progressBar, modernIcons, tabTitle, homepageSwitcher];
12
12
+
export const plugins = [
13
13
+
subheader,
14
14
+
scrollSegments,
15
15
+
scrollPeriod,
16
16
+
progressBar,
17
17
+
modernIcons,
18
18
+
tabTitle,
19
19
+
changeLogo,
20
20
+
homepageSwitcher,
21
21
+
];
12
22
13
23
export type PluginInstance = (typeof plugins)[number];
14
24
+2
-2
src/entrypoints/popup/components/inputs/Toggle.svelte
···
29
29
</label>
30
30
31
31
<div
32
32
-
class="flex items-center justify-between text-ctp-overlay1 transition-colors duration-500 ease-in-out group-hover:text-ctp-subtext0">
32
32
+
class="flex items-center justify-between gap-2 text-ctp-overlay1 transition-colors duration-500 ease-in-out group-hover:text-ctp-subtext0">
33
33
<div>{description}</div>
34
34
-
<div>{@render children?.()}</div>
34
34
+
{@render children?.()}
35
35
</div>
+3
-55
src/entrypoints/popup/routes/Themes.svelte
···
1
1
<script lang="ts">
2
2
-
import { browser } from "#imports";
3
3
-
import type { LogoId } from "@/utils/storage";
4
2
import { globalSettings } from "@/utils/storage";
5
5
-
import { LOGO_INFO } from "@/utils/constants";
6
6
-
import { Palette } from "@lucide/svelte";
3
3
+
import type { Accent, Flavour } from "@/utils/storage";
7
4
8
5
import Title from "../components/Title.svelte";
9
9
-
import Modal from "../components/Modal.svelte";
10
10
-
import Button from "../components/inputs/Button.svelte";
11
11
-
import Toggle from "../components/inputs/Toggle.svelte";
12
6
13
13
-
const flavours = ["latte", "frappe", "macchiato", "mocha"];
7
7
+
const flavours: Flavour[] = ["latte", "frappe", "macchiato", "mocha"];
14
8
const accents = [
15
9
"bg-ctp-rosewater",
16
10
"bg-ctp-flamingo",
···
28
22
"bg-ctp-lavender",
29
23
];
30
24
31
31
-
const logos = LOGO_INFO;
32
32
-
let showModal = $state(false);
33
33
-
34
25
function cleanAccent(accent: string) {
35
26
return accent.replace("bg-ctp-", "");
36
27
}
37
28
</script>
38
29
39
39
-
<Modal bind:showModal>
40
40
-
{#snippet header()}
41
41
-
<h2 class="mb-4 text-xl">Choose an icon</h2>
42
42
-
{/snippet}
43
43
-
44
44
-
<div class="grid grid-cols-3 gap-4">
45
45
-
{#each Object.entries(logos) as [logoId, logo] (logoId)}
46
46
-
<button
47
47
-
onclick={() => {
48
48
-
globalSettings.update({ themeLogo: logoId as LogoId });
49
49
-
}}
50
50
-
class:highlight={globalSettings.state.themeLogo === logoId}
51
51
-
class="flex flex-col rounded-lg border border-(--ctp-accent) p-2">
52
52
-
<span>{logo.name}</span>
53
53
-
{#if logo.disable !== true}
54
54
-
<div class="flex h-full w-full items-center justify-center">
55
55
-
{#if logo.adaptive}
56
56
-
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
57
57
-
<span class="logo-picker" style="--icon: url({browser.runtime.getURL(logo.url as any)})"></span>
58
58
-
{:else}
59
59
-
<img src={logo.url} alt="Logo" class="mt-2 w-16" />
60
60
-
{/if}
61
61
-
</div>
62
62
-
{/if}
63
63
-
</button>
64
64
-
{/each}
65
65
-
</div>
66
66
-
67
67
-
<div class="mt-4">
68
68
-
<Toggle
69
69
-
update={(toggled) => {
70
70
-
globalSettings.update({ themeLogoAsFavicon: toggled });
71
71
-
}}
72
72
-
checked={globalSettings.state.themeLogoAsFavicon}
73
73
-
id="setAsFavicon"
74
74
-
size="small"
75
75
-
text="Set icon as tab favicon" />
76
76
-
</div>
77
77
-
</Modal>
78
78
-
79
30
<div id="card">
80
31
<Title
81
32
title="Themes"
···
105
56
aria-label={cleanAccent(accent)}
106
57
title={cleanAccent(accent)}
107
58
onclick={() => {
108
108
-
globalSettings.update({ themeAccent: cleanAccent(accent) });
59
59
+
globalSettings.update({ themeAccent: cleanAccent(accent) as Accent });
109
60
}}></button>
110
61
{/each}
111
62
</div>
112
112
-
113
113
-
<Button title="Choose icon" id="choose-icon" onclick={() => (showModal = true)}
114
114
-
><Palette size={22} /> Choose an icon</Button>
115
63
</div>
+4
-8
src/entrypoints/start.content.ts
···
2
2
import {
3
3
hasChanged,
4
4
injectCatppuccin,
5
5
-
injectLogo,
6
5
injectStylesheet,
7
6
injectUserSnippet,
8
7
onSchoolboxPage,
···
11
10
uninjectStylesheet,
12
11
uninjectUserSnippet,
13
12
} from "@/utils";
14
14
-
import { EXCLUDE_MATCHES, LOGO_INFO } from "@/utils/constants";
15
15
-
import type { LogoId, Settings } from "@/utils/storage";
13
13
+
import { EXCLUDE_MATCHES } from "@/utils/constants";
14
14
+
import type { SettingsV2 } from "@/utils/storage";
16
15
import { globalSettings } from "@/utils/storage";
17
16
import type { WatchCallback } from "wxt/utils/storage";
18
17
import cssUrl from "./catppuccin.css?url";
···
26
25
// if not on Schoolbox page
27
26
if (!(await onSchoolboxPage())) return;
28
27
29
29
-
const updateThemes: WatchCallback<Settings> = async (newValue, oldValue) => {
28
28
+
const updateThemes: WatchCallback<SettingsV2> = async (newValue, oldValue) => {
30
29
// if global or themes was changed
31
30
if (hasChanged(newValue, oldValue, ["global", "themes", "themeFlavour", "themeAccent"])) {
32
31
if (newValue.global && newValue.themes) {
···
39
38
}
40
39
};
41
40
42
42
-
const updateUserSnippets: WatchCallback<Settings> = async (newValue, oldValue) => {
41
41
+
const updateUserSnippets: WatchCallback<SettingsV2> = async (newValue, oldValue) => {
43
42
// if global or userSnippets were changed
44
43
if (hasChanged(newValue, oldValue, ["global", "userSnippets"])) {
45
44
// uninject removed snippets
···
79
78
injectThemes();
80
79
injectCatppuccin();
81
80
}
82
82
-
83
83
-
// inject logo
84
84
-
injectLogo(LOGO_INFO[settings.themeLogo as LogoId], settings.themeLogoAsFavicon);
85
81
86
82
// inject user snippets
87
83
if (settings.snippets) {
-30
src/utils/constants.ts
···
1
1
-
import type { LogoId, LogoInfo } from "./storage";
2
2
-
3
1
export const EXCLUDE_MATCHES: string[] = ["*://*/learning/quiz/*"];
4
4
-
export const LOGO_INFO: Record<LogoId, LogoInfo> = {
5
5
-
default: {
6
6
-
name: "Default",
7
7
-
url: "default",
8
8
-
disable: true,
9
9
-
},
10
10
-
catppuccin: {
11
11
-
name: "Catppuccin",
12
12
-
url: "https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/logos/exports/1544x1544_circle.png",
13
13
-
},
14
14
-
schoolbox: {
15
15
-
name: "Schoolbox",
16
16
-
url: "schoolbox.svg",
17
17
-
adaptive: true,
18
18
-
},
19
19
-
schooltape: {
20
20
-
name: "Schooltape",
21
21
-
url: "schooltape.svg",
22
22
-
},
23
23
-
"schooltape-rainbow": {
24
24
-
name: "ST Rainbow",
25
25
-
url: "schooltape-ctp.svg",
26
26
-
},
27
27
-
"schooltape-legacy": {
28
28
-
name: "ST Legacy",
29
29
-
url: "https://schooltape.github.io/schooltape-legacy.svg",
30
30
-
},
31
31
-
};
+2
-47
src/utils/index.ts
···
1
1
import { browser } from "#imports";
2
2
import { flavorEntries } from "@catppuccin/palette";
3
3
import { logger } from "./logger";
4
4
-
import type { BackgroundMessage, LogoInfo } from "./storage";
4
4
+
import type { BackgroundMessage } from "./storage";
5
5
import { globalSettings, schoolboxUrls } from "./storage";
6
6
7
7
export const dataAttr = (id: string) => `[data-schooltape="${id}"]`;
···
20
20
const style = document.createElement("style");
21
21
style.textContent = styleText;
22
22
setDataAttr(style, `inline-${id}`);
23
23
-
document.head.append(style);
24
24
-
// logger.info(`injected styles with id ${id}`);
23
23
+
document.head.appendChild(style);
25
24
}
26
25
27
26
export function uninjectInlineStyles(id: string) {
···
52
51
53
52
export function uninjectCatppuccin() {
54
53
uninjectInlineStyles("catppuccin");
55
55
-
}
56
56
-
57
57
-
export function injectLogo(logo: LogoInfo, setAsFavicon: boolean) {
58
58
-
let url = logo.url;
59
59
-
if (!url.startsWith("http")) {
60
60
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
61
61
-
url = browser.runtime.getURL(url as any);
62
62
-
}
63
63
-
logger.info(`injecting logo: ${logo.name}`);
64
64
-
if (logo.disable) {
65
65
-
return;
66
66
-
}
67
67
-
const style = document.createElement("style");
68
68
-
style.classList.add("schooltape");
69
69
-
if (logo.adaptive) {
70
70
-
style.textContent = `a.logo > img { display: none !important; } a.logo { display: flex; align-items: center; justify-content: center; }`;
71
71
-
const span = document.createElement("span");
72
72
-
span.style.mask = `url("${url}") no-repeat center`;
73
73
-
span.style.maskSize = "100% 100%";
74
74
-
span.style.backgroundColor = "hsl(var(--ctp-accent))";
75
75
-
span.style.width = "100%";
76
76
-
span.style.height = "60px";
77
77
-
span.style.display = "block";
78
78
-
window.addEventListener("load", () => {
79
79
-
document.querySelectorAll("a.logo").forEach((logo) => {
80
80
-
const clonedSpan = span.cloneNode(true);
81
81
-
logo.append(clonedSpan);
82
82
-
});
83
83
-
});
84
84
-
} else {
85
85
-
style.textContent = `a.logo > img { content: url("${url}"); max-width: 30%; width: 100px; }`;
86
86
-
}
87
87
-
document.head.appendChild(style);
88
88
-
89
89
-
// inject favicon
90
90
-
if (setAsFavicon) {
91
91
-
let favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
92
92
-
if (!favicon) {
93
93
-
favicon = document.createElement("link") as HTMLLinkElement;
94
94
-
favicon.rel = "icon";
95
95
-
document.head.appendChild(favicon);
96
96
-
}
97
97
-
favicon.href = url;
98
98
-
}
99
54
}
100
55
101
56
export function injectStylesheet(url: string, id: string) {
-1
src/utils/plugin.ts
···
9
9
private injected = false;
10
10
public toggle: StorageState<Toggle>;
11
11
public settings!: T;
12
12
-
public menu: string | undefined;
13
12
14
13
constructor(
15
14
public meta: {
+27
-4
src/utils/storage/global.ts
···
1
1
import { storage } from "#imports";
2
2
+
import type { Settings as LogoSettings } from "@/entrypoints/plugins/changeLogo";
2
3
import { StorageState } from "./state.svelte";
3
4
import type * as Types from "./types";
4
5
5
5
-
export const globalSettings = new StorageState<Types.Settings>(
6
6
-
storage.defineItem<Types.Settings>("local:globalSettings", {
6
6
+
export const globalSettings = new StorageState(
7
7
+
storage.defineItem<Types.SettingsV2>("local:globalSettings", {
8
8
+
version: 2,
7
9
fallback: {
8
10
global: true,
9
11
plugins: true,
···
12
14
13
15
themeFlavour: "mocha",
14
16
themeAccent: "mauve",
15
15
-
themeLogo: "schooltape-rainbow",
16
16
-
themeLogoAsFavicon: false,
17
17
18
18
userSnippets: {},
19
19
+
},
20
20
+
migrations: {
21
21
+
2: async (settings: Types.SettingsV1) => {
22
22
+
const { themeLogo, themeLogoAsFavicon, ...rest } = settings;
23
23
+
24
24
+
// dynamic import to avoid TDZ error
25
25
+
const { plugins } = await import("@/entrypoints/plugins.content");
26
26
+
const changeLogo = plugins.find((plugin) => plugin.meta.id === "changeLogo");
27
27
+
28
28
+
if (changeLogo) {
29
29
+
const s = changeLogo.settings as LogoSettings;
30
30
+
if (themeLogo !== "default") {
31
31
+
// update logo
32
32
+
s.logo.set({ id: themeLogo });
33
33
+
} else {
34
34
+
// disable changeLogo
35
35
+
changeLogo.toggle.set({ toggle: false });
36
36
+
}
37
37
+
s.setAsFavicon.set({ toggle: themeLogoAsFavicon });
38
38
+
}
39
39
+
40
40
+
return rest;
41
41
+
},
19
42
},
20
43
}),
21
44
);
+33
-13
src/utils/storage/types.ts
···
5
5
| { type: "updateTabUrl"; url: string };
6
6
7
7
// global
8
8
-
export interface Settings {
8
8
+
export interface SettingsV1 {
9
9
global: boolean;
10
10
plugins: boolean;
11
11
themes: boolean;
12
12
snippets: boolean;
13
13
14
14
-
themeFlavour: string;
15
15
-
themeAccent: string;
16
16
-
themeLogo: LogoId;
14
14
+
themeFlavour: Flavour;
15
15
+
themeAccent: Accent;
16
16
+
themeLogo: "default" | "schooltape" | "schooltape-rainbow" | "schooltape-legacy" | "catppuccin" | "schoolbox";
17
17
themeLogoAsFavicon: boolean;
18
18
19
19
userSnippets: Record<string, UserSnippet>;
20
20
}
21
21
22
22
+
export interface SettingsV2 {
23
23
+
global: boolean;
24
24
+
plugins: boolean;
25
25
+
themes: boolean;
26
26
+
snippets: boolean;
27
27
+
28
28
+
themeFlavour: Flavour;
29
29
+
themeAccent: Accent;
30
30
+
31
31
+
userSnippets: Record<string, UserSnippet>;
32
32
+
}
33
33
+
34
34
+
export type Flavour = "latte" | "frappe" | "macchiato" | "mocha";
35
35
+
export type Accent =
36
36
+
| "rosewater"
37
37
+
| "flamingo"
38
38
+
| "pink"
39
39
+
| "mauve"
40
40
+
| "red"
41
41
+
| "maroon"
42
42
+
| "peach"
43
43
+
| "yellow"
44
44
+
| "green"
45
45
+
| "teal"
46
46
+
| "sky"
47
47
+
| "sapphire"
48
48
+
| "blue"
49
49
+
| "lavender";
50
50
+
22
51
export interface UpdatedBadges {
23
52
icon: boolean;
24
53
changelog: boolean;
···
30
59
31
60
export interface SchoolboxUrls {
32
61
urls: string[];
33
33
-
}
34
34
-
35
35
-
export type LogoId = "default" | "catppuccin" | "schoolbox" | "schooltape" | "schooltape-rainbow" | "schooltape-legacy";
36
36
-
37
37
-
export interface LogoInfo {
38
38
-
name: string;
39
39
-
url: string;
40
40
-
disable?: boolean; // whether the icon should be injected or not
41
41
-
adaptive?: boolean; // whether the icon should follow the accent colour
42
62
}
43
63
44
64
export interface UserSnippet {