├── .github
├── FUNDING.yml
└── workflows
│ ├── dev.yml
│ └── main.yml
├── icons
├── icon@128px.png
├── icon@16px.png
├── icon@256px.png
├── icon@300px.png
├── icon@32px.png
├── icon@48px.png
├── icon@512px.png
└── icon@64px.png
├── src
├── package.json
├── jsconfig.json
├── scripts
│ ├── theme-store.js
│ ├── microsoft-login.js
│ ├── login.js
│ ├── books.js
│ └── grades
│ │ ├── list.js
│ │ └── backup.js
├── package-lock.json
├── globals.d.ts
├── styles
│ ├── studyguide.css
│ ├── gamification.css
│ └── today
│ │ └── today.css
├── background.js
└── service-worker.js
├── popup
├── src
│ ├── assets
│ │ ├── logo.png
│ │ ├── fa-regular-400.ttf
│ │ ├── decorations
│ │ │ ├── lego.png
│ │ │ ├── waves.png
│ │ │ ├── stripes.png
│ │ │ ├── zig-zag.png
│ │ │ └── polka-dot.png
│ │ └── variables.css
│ ├── main.js
│ ├── stores
│ │ └── store.js
│ ├── components
│ │ ├── Icon.vue
│ │ ├── setting-types
│ │ │ ├── ColorSetting.vue
│ │ │ ├── LinkToOptionsTab.vue
│ │ │ ├── ColorOverrideSetting.vue
│ │ │ ├── Text.vue
│ │ │ ├── Slider.vue
│ │ │ └── SingleChoice.vue
│ │ ├── sheets
│ │ │ └── ImageUrlSheet.vue
│ │ ├── Chip.vue
│ │ ├── TopAppBar.vue
│ │ ├── ThemeColors.vue
│ │ ├── Dialog.vue
│ │ ├── inputs
│ │ │ ├── TextInput.vue
│ │ │ ├── SegmentedButton.vue
│ │ │ └── ColorPicker.vue
│ │ ├── NavigationBar.vue
│ │ ├── MagisterThemePreview.vue
│ │ ├── DialogFullscreen.vue
│ │ ├── InputText.vue
│ │ ├── SwitchInput.vue
│ │ ├── BottomSheet.vue
│ │ ├── IconInput.vue
│ │ ├── ThemePresets.vue
│ │ ├── NavigationRail.vue
│ │ ├── ShortcutsEditor.vue
│ │ ├── ImageInput.vue
│ │ ├── About.vue
│ │ ├── CustomCssEditor.vue
│ │ └── ThemePreviewImage.vue
│ └── composables
│ │ └── chrome.js
├── dist
│ ├── assets
│ │ ├── waves-BpDb7oY5.js
│ │ ├── stripes-CrSqOAWt.js
│ │ ├── waves-BYx-PK3B.png
│ │ ├── polka-dot-Bk_kYWko.js
│ │ ├── stripes-BhwmhtOl.png
│ │ ├── polka-dot-vnfSTs_N.png
│ │ ├── fa-regular-400-BMFokQJ2.ttf
│ │ ├── zig-zag-BPQTWn5Y.js
│ │ └── lego-B9XBXV1o.js
│ └── index.html
├── jsconfig.json
├── index.html
├── vite.config.js
└── package.json
├── _locales
├── en
│ └── messages.json
├── de
│ └── messages.json
├── fr
│ └── messages.json
└── nl
│ └── messages.json
├── .gitignore
├── LICENSE
├── README.md
├── updates.json
├── manifest.json
└── manifest-firefox.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["paypal.me/QkeleQ10"]
2 |
--------------------------------------------------------------------------------
/icons/icon@128px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@128px.png
--------------------------------------------------------------------------------
/icons/icon@16px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@16px.png
--------------------------------------------------------------------------------
/icons/icon@256px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@256px.png
--------------------------------------------------------------------------------
/icons/icon@300px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@300px.png
--------------------------------------------------------------------------------
/icons/icon@32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@32px.png
--------------------------------------------------------------------------------
/icons/icon@48px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@48px.png
--------------------------------------------------------------------------------
/icons/icon@512px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@512px.png
--------------------------------------------------------------------------------
/icons/icon@64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/icons/icon@64px.png
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@types/chrome": "^0.0.326"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/popup/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/logo.png
--------------------------------------------------------------------------------
/popup/dist/assets/waves-BpDb7oY5.js:
--------------------------------------------------------------------------------
1 | const e=""+new URL("waves-BYx-PK3B.png",import.meta.url).href;export{e as default};
2 |
--------------------------------------------------------------------------------
/popup/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | createApp(App).mount('#app')
5 |
--------------------------------------------------------------------------------
/popup/dist/assets/stripes-CrSqOAWt.js:
--------------------------------------------------------------------------------
1 | const t=""+new URL("stripes-BhwmhtOl.png",import.meta.url).href;export{t as default};
2 |
--------------------------------------------------------------------------------
/popup/dist/assets/waves-BYx-PK3B.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/dist/assets/waves-BYx-PK3B.png
--------------------------------------------------------------------------------
/popup/src/assets/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/fa-regular-400.ttf
--------------------------------------------------------------------------------
/popup/dist/assets/polka-dot-Bk_kYWko.js:
--------------------------------------------------------------------------------
1 | const o=""+new URL("polka-dot-vnfSTs_N.png",import.meta.url).href;export{o as default};
2 |
--------------------------------------------------------------------------------
/popup/dist/assets/stripes-BhwmhtOl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/dist/assets/stripes-BhwmhtOl.png
--------------------------------------------------------------------------------
/popup/src/assets/decorations/lego.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/decorations/lego.png
--------------------------------------------------------------------------------
/popup/src/assets/decorations/waves.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/decorations/waves.png
--------------------------------------------------------------------------------
/popup/src/stores/store.js:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 |
3 | export const store = reactive({
4 | currentlyHovered: ''
5 | })
--------------------------------------------------------------------------------
/popup/dist/assets/polka-dot-vnfSTs_N.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/dist/assets/polka-dot-vnfSTs_N.png
--------------------------------------------------------------------------------
/popup/src/assets/decorations/stripes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/decorations/stripes.png
--------------------------------------------------------------------------------
/popup/src/assets/decorations/zig-zag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/decorations/zig-zag.png
--------------------------------------------------------------------------------
/popup/src/assets/decorations/polka-dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/src/assets/decorations/polka-dot.png
--------------------------------------------------------------------------------
/popup/dist/assets/fa-regular-400-BMFokQJ2.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QkeleQ10/Study-Tools/HEAD/popup/dist/assets/fa-regular-400-BMFokQJ2.ttf
--------------------------------------------------------------------------------
/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Study Tools for Magister"
4 | },
5 | "appDesc": {
6 | "message": "An extension that improves various aspects of and solves many issues in Magister."
7 | }
8 | }
--------------------------------------------------------------------------------
/_locales/de/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Study Tools für Magister"
4 | },
5 | "appDesc": {
6 | "message": "Eine Erweiterung, die verschiedene Aspekte verbessert und viele Probleme in Magister löst."
7 | }
8 | }
--------------------------------------------------------------------------------
/_locales/fr/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Study Tools pour Magister"
4 | },
5 | "appDesc": {
6 | "message": "Une extension qui améliore divers aspects et résout de nombreux problèmes dans Magister."
7 | }
8 | }
--------------------------------------------------------------------------------
/_locales/nl/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Study Tools voor Magister"
4 | },
5 | "appDesc": {
6 | "message": "Een extensie die verschillende aspecten van Magister verbetert en problemen ermee oplost."
7 | }
8 | }
--------------------------------------------------------------------------------
/src/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "checkJs": true,
5 | "types": [
6 | "chrome"
7 | ]
8 | },
9 | "include": [
10 | "./**/*.js",
11 | "./globals.d.ts"
12 | ]
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
4 | # local env files
5 | .env.local
6 | .env.*.local
7 |
8 | # Log files
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/src/scripts/theme-store.js:
--------------------------------------------------------------------------------
1 | popstate()
2 | window.addEventListener('popstate', popstate)
3 | async function popstate() {
4 | // Only run on the theme store
5 | if (! await awaitElement('meta#theme-store-st')) return
6 | // Provide the page with this extension's ID
7 | element('meta', `st-${chrome.runtime.id}`, document.head)
8 | }
--------------------------------------------------------------------------------
/popup/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@/*": [
9 | "src/*"
10 | ]
11 | },
12 | "lib": [
13 | "esnext",
14 | "dom",
15 | "dom.iterable",
16 | "scripthost"
17 | ]
18 | }
19 | }
--------------------------------------------------------------------------------
/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Study Tools-configuratiepaneel
9 |
10 |
11 |
12 |
13 | JavaScript moet ingeschakeld zijn.
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/popup/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import vue from '@vitejs/plugin-vue';
3 | import path from 'path';
4 | import Components from 'unplugin-vue-components/vite';
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [
9 | vue(),
10 | Components({}),
11 | ],
12 | base: '',
13 | resolve: {
14 | alias: {
15 | '@': path.resolve(__dirname, './src')
16 | },
17 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
18 | }
19 | });
--------------------------------------------------------------------------------
/popup/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Study Tools-configuratiepaneel
9 |
10 |
11 |
12 |
13 |
14 |
15 | JavaScript moet ingeschakeld zijn.
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/popup/src/components/Icon.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/popup/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "study-tools-popup",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "host": "vite --host",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@vueuse/core": "^12.7.0",
13 | "vue": "^3.5.22",
14 | "vue-slider-component": "^4.1.0-beta.7"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-vue": "^5.2.1",
18 | "unplugin-vue-components": "^28.4.0",
19 | "vite": "^6.4.1"
20 | },
21 | "browserslist": [
22 | "last 2 Chrome versions",
23 | "last 2 Firefox versions",
24 | "last 2 Edge versions"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/dev.yml:
--------------------------------------------------------------------------------
1 | name: Create beta release
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v4
15 |
16 | - name: Indicate beta version
17 | run: |
18 | sed -i '3s/.*/"message": "Study Tools voor Magister BETA"/' _locales/nl/messages.json
19 | sed -i '3s/.*/"message": "Study Tools for Magister BETA"/' _locales/en/messages.json
20 |
21 | - name: Upload as artifacts
22 | uses: actions/upload-artifact@v4
23 | with:
24 | name: Beta release
25 | path: |
26 | _locales
27 | icons
28 | popup/dist
29 | src/scripts
30 | src/strings
31 | src/styles
32 | src/service-worker.js
33 | manifest.json
34 | updates.json
35 |
--------------------------------------------------------------------------------
/src/scripts/microsoft-login.js:
--------------------------------------------------------------------------------
1 | init()
2 |
3 | async function magisterLogin() {
4 | const forceLogoutTimestamp = await getFromStorage('force-logout', 'local')
5 |
6 | if (!syncedStorage['magisterLogin-enabled'] || !syncedStorage['magisterLogin-email'] || (forceLogoutTimestamp && Math.abs(new Date().getTime() - forceLogoutTimestamp) <= 30000)) return
7 |
8 | let signInButton = await awaitElement(`div.table[data-test-id="${syncedStorage['magisterLogin-email']}"]`)
9 | if (signInButton) signInButton.click()
10 | }
11 |
12 | // Run when the extension and page are loaded
13 | async function init() {
14 | popstate()
15 |
16 | window.addEventListener('popstate', popstate)
17 | window.addEventListener('hashchange', popstate)
18 | window.addEventListener('locationchange', popstate)
19 | }
20 |
21 | // Run when the URL changes
22 | async function popstate() {
23 | const href = document.location.href.split('?')[0]
24 |
25 | if (document.location.href.includes('accounts.magister.net')) magisterLogin()
26 | }
--------------------------------------------------------------------------------
/popup/src/components/setting-types/ColorSetting.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/popup/src/components/setting-types/LinkToOptionsTab.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 | ...
19 |
20 |
21 | chevron_right
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Quinten Althues
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/popup/src/components/sheets/ImageUrlSheet.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
33 |
35 | Afbeeldings-URL
36 |
37 | Geef de koppeling van de afbeelding die je wilt gebruiken.
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Create releases
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v4
15 |
16 | - name: Upload Chromium build
17 | uses: actions/upload-artifact@v4
18 | with:
19 | name: Chromium release
20 | path: |
21 | _locales
22 | icons
23 | popup/dist
24 | src/scripts
25 | src/strings
26 | src/styles
27 | src/service-worker.js
28 | manifest.json
29 | updates.json
30 |
31 | - name: Convert manifest for Firefox
32 | run: |
33 | mv manifest.json manifest-chromium.json
34 | mv manifest-firefox.json manifest.json
35 |
36 | - name: Upload Firefox build
37 | uses: actions/upload-artifact@v4
38 | with:
39 | name: Firefox release
40 | path: |
41 | _locales
42 | icons
43 | popup/dist
44 | src/scripts
45 | src/strings
46 | src/styles
47 | src/background.js
48 | manifest.json
49 | updates.json
50 |
51 | - name: Restore Chromium manifest
52 | run: |
53 | mv manifest.json manifest-firefox.json
54 | mv manifest-chromium.json manifest.json
--------------------------------------------------------------------------------
/popup/src/components/Chip.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/popup/src/components/TopAppBar.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
26 |
27 | restart_alt
28 | Voorkeuren wissen?
29 | Hiermee stel je alle instellingen van Study Tools in op de standaardwaarden.
30 |
31 | Annuleren
32 | Wissen
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "devDependencies": {
8 | "@types/chrome": "^0.0.326"
9 | }
10 | },
11 | "node_modules/@types/chrome": {
12 | "version": "0.0.326",
13 | "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.326.tgz",
14 | "integrity": "sha512-WS7jKf3ZRZFHOX7dATCZwqNJgdfiSF0qBRFxaO0LhIOvTNBrfkab26bsZwp6EBpYtqp8loMHJTnD6vDTLWPKYw==",
15 | "dev": true,
16 | "license": "MIT",
17 | "dependencies": {
18 | "@types/filesystem": "*",
19 | "@types/har-format": "*"
20 | }
21 | },
22 | "node_modules/@types/filesystem": {
23 | "version": "0.0.36",
24 | "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
25 | "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
26 | "dev": true,
27 | "license": "MIT",
28 | "dependencies": {
29 | "@types/filewriter": "*"
30 | }
31 | },
32 | "node_modules/@types/filewriter": {
33 | "version": "0.0.33",
34 | "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
35 | "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
36 | "dev": true,
37 | "license": "MIT"
38 | },
39 | "node_modules/@types/har-format": {
40 | "version": "1.2.16",
41 | "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
42 | "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
43 | "dev": true,
44 | "license": "MIT"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/globals.d.ts:
--------------------------------------------------------------------------------
1 | // globals.d.ts
2 | ///
3 | ///
4 |
5 | // globals.d.ts
6 | interface HTMLElement {
7 | /**
8 | * Creates a child element under this HTMLElement.
9 | * @template {keyof HTMLElementTagNameMap} K
10 | * @param {K} tagName - The element's tag name.
11 | * @param {CreateElementAttributes & Record} [attributes] - Attributes to set
12 | * @returns {HTMLElementTagNameMap[K]}
13 | */
14 | createChildElement(
15 | tagName: K,
16 | attributes?: CreateElementAttributes & Record
17 | ): HTMLElementTagNameMap[K];
18 |
19 | /**
20 | * Creates a sibling element under the parent of this element.
21 | * @template {keyof HTMLElementTagNameMap} K
22 | * @param {K} tagName - The element's tag name.
23 | * @param {CreateElementAttributes & Record} [attributes] - Attributes to set on the sibling.
24 | * @returns {HTMLElementTagNameMap[K]}
25 | */
26 | createSiblingElement(
27 | tagName: K,
28 | attributes?: CreateElementAttributes & Record
29 | ): HTMLElementTagNameMap[K];
30 |
31 | /**
32 | * Sets multiple attributes, properties, styles, classes, etc. on this element.
33 | * @param {CreateElementAttributes & Record} attributes - An object of attributes and properties.
34 | * @returns {void}
35 | */
36 | setAttributes(attributes: CreateElementAttributes & Record): void;
37 | }
38 |
39 | interface Date {
40 | getWeek(): number;
41 | getHoursWithDecimals(): number;
42 |
43 | getFormattedDay(): string;
44 | getFormattedTime(): string;
45 |
46 | addDays(days: number): Date;
47 |
48 | isToday(offset?: number): boolean;
49 | isTomorrow(offset?: number): boolean;
50 | isYesterday(offset?: number): boolean;
51 | }
52 |
53 | interface Array {
54 | random(seed);
55 | mode();
56 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Study Tools voor Magister
6 |
7 | Een extensie die verschillende aspecten van Magister verbetert en problemen ermee oplost.
8 |
9 | ## Functionaliteiten
10 |
11 | - Aangepaste uiterlijk: Instelbaar donker/licht thema, aangepaste accentkleur en speciale decoraties.
12 | - Start: Weg met het rommelige Vandaag, op pagina Start zie je met widgets alles in één oogopslag.
13 | - Automatisch inloggen: Compleet instelbaar op basis van jouw behoeften en je school.
14 | - Studiewijzers: Verschillende weergave-opties voor efficiëntie en overzichtelijkheid.
15 | - Cijfercalculator: Wat moet ik halen en wat kom ik te staan? Inclusief grafiek!
16 | - Statistieken: Vergelijk je cijfers en je rooster op verschillende manieren met vrienden.
17 | - Cijferback-up: Altijd bij je cijfers kunnen, zelfs als je school ze besluit te sluiten.
18 |
19 | En nog veel meer! Ik voeg regelmatig nieuwe functies toe. Feedback wordt op prijs gesteld!
20 |
21 | ## Screenshots
22 |
23 | 
24 | 
25 |
26 |
27 |
28 |
29 | ## Bètaversie installeren
30 | 1. Kopieer even je huidige instellingen naar je klembord. Dat kan via het tabblad 'Over' in de pop-up!
31 | 2. Deactiveer de stabiele extensie.
32 | 3. [Installeer de bèta-extensie](https://chromewebstore.google.com/u/1/detail/study-tools-voor-magister/dlmdgkhbbclpolcofdlhlpdpiobmklmd).
33 | 4. Plak je instellingen in de bèta-extensie. Dat kan onder hetzelfde menuutje in de pop-up.
34 |
35 | ## Licentie
36 |
37 | Het project is verkrijgbaar onder de MIT license.
38 |
--------------------------------------------------------------------------------
/src/scripts/login.js:
--------------------------------------------------------------------------------
1 | login();
2 |
3 | async function login() {
4 | chrome.runtime.sendMessage({ action: 'popstateDetected' });
5 |
6 | const footerNotice = element('div', 'bottom-st', null, {
7 | innerHTML: "\xa0|\xa0Autom. inloggen aan ",
8 | title: "Meer informatie over automatisch inloggen",
9 | style: "cursor: pointer;",
10 | });
11 | document.querySelector('footer>.bottom-company-logo, footer>*:last-child')?.before(footerNotice);
12 |
13 | let autoLoginDisclaimer = "Study Tools is actief en er wordt een poging gedaan om automatisch in te loggen. \n\nControleer je instellingen als het inloggen niet slaagt."
14 | footerNotice.addEventListener('click', () => notify('dialog', autoLoginDisclaimer, [{
15 | innerText: "Instellingen",
16 | primary: true,
17 | onclick: () => chrome.runtime.sendMessage({ action: 'openOptions', data: 'tab=login' }),
18 | }]));
19 |
20 | if (!syncedStorage['magisterLogin-enabled'] || !syncedStorage['magisterLogin-username']) {
21 | footerNotice.innerHTML = "\xa0|\xa0Autom. inloggen uit ";
22 | autoLoginDisclaimer = "Study Tools is actief, maar automatisch inloggen is nog niet ingesteld. \n\nGebruik onderstaande knop om de instellingen te openen.";
23 | return;
24 | }
25 |
26 | const forceLogoutTimestamp = await getFromStorage('force-logout', 'local')
27 | if (forceLogoutTimestamp && Math.abs(new Date().getTime() - forceLogoutTimestamp) <= 30000) {
28 | footerNotice.innerHTML = "\xa0|\xa0Autom. inloggen uit ";
29 | autoLoginDisclaimer = "Study Tools is actief, maar automatisch inloggen is tijdelijk gepauzeerd omdat je handmatig hebt uitgelogd. \n\nDe volgende keer zal er weer automatisch worden ingelogd.";
30 | return;
31 | }
32 |
33 | const usernameInput = await awaitElement('#username');
34 | usernameInput.value = syncedStorage['magisterLogin-username'];
35 | usernameInput.dispatchEvent(new Event('input'));
36 |
37 | const usernameSubmit = await awaitElement('#username_submit');
38 | usernameSubmit.click();
39 | }
--------------------------------------------------------------------------------
/popup/src/components/ThemeColors.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/popup/src/components/Dialog.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/popup/src/components/inputs/TextInput.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/updates.json:
--------------------------------------------------------------------------------
1 | {
2 | "3.12.0": "Nieuwe widget- en thema-editor.",
3 | "3.11.0": "Magister Wrapped reimagined",
4 | "3.10.0": "Geavanceerde thema-instellingen!",
5 | "3.9.0": "Studiewijzers vernieuwd, met nieuwe manier van rangschikken",
6 | "3.8.0": "Achtergrondafbeeldingen, betere gebruiksvriendelijkheid en aanbevelingen.",
7 | "3.7.0": "Widgets in tegel- en lijstvorm, hernoembare leermiddelen en een nieuwe themakiezer.",
8 | "3.6.0": "Magister Wrapped is er! Beter laat dan nooit?",
9 | "3.5.0": "Cijferstatistieken hebben een make-over gekregen!",
10 | "3.4.0": "De cijfercalculator heeft een make-over gekregen!",
11 | "3.3.5": "Neem eens een kijkje in de nieuwe optie- en statistiekschermen op pagina Start!",
12 | "3.3.0": "Pagina Start is wederom helemaal verbeterd. Je wordt niet meer gefeliciteerd wanneer het je verjaardag helemaal niet is.",
13 | "3.2.0": "Scherm 'Vandaag' is nu 'Start', dit keer met aanpasbare widgets. Ook véél andere wijzigingen!",
14 | "3.1.0": "Snelkoppelingen in de zijbalk zijn terug! Veel verbeteringen voor alle onderdelen.",
15 | "3.0.3": "Studiewijzers vernieuwd, nu met zoekbalk en verbergen (bedankt voor de suggesties!)",
16 | "3.0.0": "Configuratiepaneel vanaf nul opnieuw opgebouwd. Kleurinstellingen zijn zonder vernieuwen zichtbaar in Magister.",
17 | "2.6.6": "Keuze tussen foutloze en snelle back-ups.",
18 | "2.6.5": "Je kunt nu een aangepaste profielfoto uploaden (dankjewel voor de suggestie, Jonas van Leeuwen!).",
19 | "2.6.4": "Voorbereidingen voor toetsweek (cijferback-up en aankondigingen).",
20 | "2.6.2": "Veel kleine verbeteringen. Verbeterde ondersteuning voor andere scholen.",
21 | "2.6.1": "Hotfix voor een aantal instellingen en bestandsmappen in Studiewijzers.",
22 | "2.6.0": "Gamificatie en notities zijn vanaf nu in bèta. Er zijn ook heel veel uiterlijkverbeteringen.",
23 | "2.5.2": "Sneltoetsen! Houd 'S' ingedrukt en kies een nummer.",
24 | "2.5.1": "Bug fixes, stijlverbeteringen en meer begroetingen. Overlappende agenda-items gefikst.",
25 | "2.5.0": "Cijferstatistieken toegevoegd aan het cijferoverzicht en verscheidene updates aan uiterlijk en stabiliteit.",
26 | "2.4.1": "Verbeteringen aan stabiliteit en uiterlijk.",
27 | "2.4.0": "Je cijferoverzicht kan nu worden geback-upt! Ook is het configuratiepaneel helemaal opnieuw gebouwd.",
28 | "2.3.2": "Updates aan onder andere het configuratiepaneel en stabiliteit.",
29 | "2.3.1": "Cijfercalculator overzichtelijker, rooster 'Vandaag' updatet nu live, veel kleine aanpassingen.",
30 | "2.3.0": "Configuratiepaneel heeft nu twee delen en is overzichtelijker, het scala aan opties uitgebreid.",
31 | "2.2.2": "Verbeterde arcering van huidige afspraak, update-meldingen, problemen opgelost.",
32 | "2.2.1": "Problemen opgelost, stabiliteit verbeterd.",
33 | "2.2.0": "Optie om pagina 'Vandaag' een make-over te geven, cijfercalculator, HEEL VEEL kleinere aanpassingen."
34 | }
35 |
--------------------------------------------------------------------------------
/popup/src/components/setting-types/ColorOverrideSetting.vue:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
40 | {{ setting.subtitle }}
41 |
42 | format_color_fill
43 |
48 |
49 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/popup/src/components/NavigationBar.vue:
--------------------------------------------------------------------------------
1 |
52 |
53 |
54 |
55 |
57 |
58 | {{ item.icon }}
59 |
60 | {{ item.name }}
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/popup/src/components/MagisterThemePreview.vue:
--------------------------------------------------------------------------------
1 |
48 |
49 |
50 |
53 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/popup/src/components/DialogFullscreen.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/popup/src/components/InputText.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/scripts/books.js:
--------------------------------------------------------------------------------
1 | // Run at start and when the URL changes
2 | popstate()
3 | window.addEventListener('popstate', popstate)
4 | async function popstate() {
5 | if (document.location.href.split('?')[0].includes('/leermiddelen')) booksList()
6 | }
7 |
8 | async function booksList() {
9 | let bookNames = syncedStorage['books'] || {}
10 |
11 | const bookEntries = await awaitElement('#leermiddelen-container tr[data-ng-repeat="leermiddel in items"]', true)
12 |
13 | for (const bookEntry of bookEntries) {
14 | const ean = bookEntry.querySelector('td[data-ng-bind="leermiddel.EAN"]').innerText;
15 | const titleCell = bookEntry.querySelector('td>a[data-ng-bind="leermiddel.Titel"]');
16 | const originalTitle = `${titleCell.innerText}`;
17 |
18 | titleCell.title = originalTitle;
19 |
20 | if (bookNames[ean]?.length > 1) titleCell.innerText = bookNames[ean];
21 |
22 | titleCell.parentElement.createChildElement('button', {
23 | class: 'st-button icon',
24 | 'data-icon': '',
25 | title: i18n('renameX', { item: originalTitle }),
26 | style: 'position: absolute; top: 50%; right: 4px; translate: 0 -50%; opacity: .5;'
27 | })
28 | .addEventListener('click', () => {
29 | const dialog = new Dialog({ closeText: i18n('cancel') })
30 | dialog.body.createChildElement('h3', {
31 | class: 'st-section-heading',
32 | innerText: i18n('renameX', { item: originalTitle })
33 | });
34 | dialog.body.createChildElement('input', {
35 | class: 'st-input',
36 | type: 'text',
37 | value: bookNames[ean] || '',
38 | placeholder: originalTitle,
39 | style: 'width: 100%; box-sizing: border-box; margin-top: 8px; padding: 4px;'
40 | })
41 | dialog.buttonsWrapper.createChildElement('button', { innerText: i18n('save'), class: 'st-button primary', 'data-icon': '' }).addEventListener('click', () => {
42 | const input = dialog.body.querySelector('input');
43 | const result = input.value.trim();
44 | dialog.close();
45 | if (result?.length) {
46 | bookNames[ean] = result;
47 | titleCell.innerText = bookNames[ean];
48 | saveToStorage('books', bookNames);
49 | } else {
50 | delete bookNames[ean];
51 | titleCell.innerText = originalTitle;
52 | saveToStorage('books', bookNames);
53 | }
54 | sortBookEntries();
55 | });
56 | dialog.show();
57 | })
58 | }
59 |
60 | sortBookEntries();
61 |
62 | function sortBookEntries() {
63 | const container = bookEntries[0].parentElement;
64 | const entriesArray = Array.from(bookEntries);
65 | entriesArray.sort((a, b) => {
66 | const titleA = a.querySelector('td>a[data-ng-bind="leermiddel.Titel"]').innerText.toLowerCase();
67 | const titleB = b.querySelector('td>a[data-ng-bind="leermiddel.Titel"]').innerText.toLowerCase();
68 | return titleA.localeCompare(titleB);
69 | });
70 | entriesArray.forEach(entry => container.appendChild(entry));
71 | }
72 | }
--------------------------------------------------------------------------------
/popup/src/components/setting-types/Text.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/popup/src/components/setting-types/Slider.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 | {{ setting.icon }}
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/popup/src/components/SwitchInput.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | check
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/popup/src/components/BottomSheet.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
37 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/popup/src/components/IconInput.vue:
--------------------------------------------------------------------------------
1 |
47 |
48 |
49 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/popup/src/components/ThemePresets.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
36 |
38 |
39 |
40 | {{ preset.name }}
41 | {{ preset.author }}
42 |
43 |
44 |
45 |
46 |
47 | format_paint
48 | Let op!
49 |
50 | Je hebt wijzigingen aangebracht aan je thema. Als je dit thema nu toepast, dan gaan je huidige thema en
51 | al je aangepaste themavoorkeuren verloren.
52 |
53 |
54 | Toepassen
55 | Annuleren
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/popup/src/components/NavigationRail.vue:
--------------------------------------------------------------------------------
1 |
62 |
63 |
64 |
65 |
67 |
68 | {{ item.icon }}
69 |
70 |
71 | {{ item.name }}
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/popup/src/composables/chrome.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { ref, onMounted, watchEffect, isProxy, toRaw } from 'vue'
3 | import { useDebounceFn } from '@vueuse/core'
4 |
5 | import settings from '../../public/settings.js'
6 |
7 | const browser = window.browser || window.chrome
8 |
9 | export function useSyncedStorage() {
10 | let syncedStorage = ref({})
11 |
12 | onMounted(() => {
13 | if (browser?.storage?.sync) {
14 | browser.storage.sync.get()
15 | .then(value => {
16 | syncedStorage.value = value
17 |
18 | // Set all undefined settings to their default values
19 | settings.forEach(category => {
20 | category.settings.forEach(setting => {
21 | if (typeof syncedStorage.value[setting.id] === 'undefined') {
22 | syncedStorage.value[setting.id] = setting.default
23 | }
24 | })
25 | })
26 |
27 | browser.storage.sync.onChanged.addListener(changes => {
28 | for (let key in changes) {
29 | if (syncedStorage.value[key] !== changes[key].newValue)
30 | syncedStorage.value[key] = changes[key].newValue
31 | }
32 | })
33 | })
34 |
35 | // Store the current version number
36 | syncedStorage.value['v'] = browser?.runtime?.getManifest()?.version
37 | }
38 | })
39 |
40 | const debouncedFn = useDebounceFn(() => {
41 | if (browser?.storage?.sync) {
42 | let toStore = { ...syncedStorage.value }
43 | if (isProxy(toStore)) toStore = toRaw(toStore)
44 | browser.storage.sync.set(toStore)
45 | }
46 | }, 250, { maxWait: 2000 })
47 |
48 | const updateTheme = () => {
49 | const themeFixed = syncedStorage.value['ptheme']?.split(',')
50 | const themeAuto = themeFixed?.[0] === 'auto'
51 | let currentTheme = themeFixed
52 |
53 | if (themeAuto && window.matchMedia?.('(prefers-color-scheme: dark)').matches) { currentTheme[0] = 'dark' }
54 | else if (themeAuto) currentTheme[0] = 'light'
55 |
56 | document.documentElement.setAttribute('theme', (currentTheme?.[0] || 'light'))
57 | document.documentElement.style.setProperty('--palette-primary-hue', (currentTheme?.[1] || 207))
58 | document.documentElement.style.setProperty('--palette-primary-saturation', `${currentTheme?.[2] || 95}%`)
59 | document.documentElement.style.setProperty('--palette-primary-luminance', `${currentTheme?.[3] || 55}%`)
60 | }
61 |
62 | watchEffect(() => {
63 | let toStore = { ...syncedStorage.value }
64 | debouncedFn()
65 | updateTheme()
66 | })
67 |
68 | return syncedStorage
69 | }
70 |
71 | export function useLocalStorage() {
72 | let localStorage = ref({})
73 |
74 | onMounted(() => {
75 | if (browser?.storage?.local) {
76 | browser.storage.local.get()
77 | .then(value => {
78 | localStorage.value = value
79 | })
80 |
81 | browser.storage.local.onChanged.addListener(changes => {
82 | for (let key in changes) {
83 | if (localStorage.value[key] !== changes[key].newValue)
84 | localStorage.value[key] = changes[key].newValue
85 | }
86 | })
87 | }
88 | })
89 |
90 | watchEffect(() => {
91 | let toStore = { ...localStorage.value }
92 | if (isProxy(toStore)) toStore = toRaw(toStore)
93 | if (browser?.storage) browser.storage.local.set(toStore)
94 | })
95 |
96 | return localStorage
97 | }
98 |
99 | export function useManifest() {
100 | let manifest = ref({})
101 |
102 | onMounted(() => {
103 | if (browser?.runtime?.getManifest)
104 | manifest.value = browser.runtime.getManifest()
105 | })
106 |
107 | return { manifest }
108 | }
109 |
110 | export function useExtension() {
111 | let extension = ref({})
112 |
113 | onMounted(() => {
114 | extension.value = browser?.extension
115 | })
116 |
117 | return { extension }
118 | }
--------------------------------------------------------------------------------
/popup/dist/assets/zig-zag-BPQTWn5Y.js:
--------------------------------------------------------------------------------
1 | const e="data:image/png;base64,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";export{e as default};
2 |
--------------------------------------------------------------------------------
/popup/src/components/inputs/SegmentedButton.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "__MSG_appName__",
4 | "description": "__MSG_appDesc__",
5 | "version": "3.15.1",
6 | "default_locale": "nl",
7 | "icons": {
8 | "16": "icons/icon@16px.png",
9 | "32": "icons/icon@32px.png",
10 | "48": "icons/icon@48px.png",
11 | "64": "icons/icon@64px.png",
12 | "128": "icons/icon@128px.png",
13 | "256": "icons/icon@256px.png",
14 | "300": "icons/icon@300px.png",
15 | "512": "icons/icon@512px.png"
16 | },
17 | "minimum_chrome_version": "109",
18 | "background": {
19 | "service_worker": "src/service-worker.js",
20 | "type": "module"
21 | },
22 | "content_scripts": [
23 | {
24 | "matches": [
25 | "*://*.magister.net/*",
26 | "*://login.microsoftonline.com/*/oauth2/authorize*"
27 | ],
28 | "js": [
29 | "src/scripts/util.js",
30 | "src/scripts/api.js"
31 | ],
32 | "run_at": "document_start"
33 | },
34 | {
35 | "matches": [
36 | "*://*.magister.net/*"
37 | ],
38 | "js": [
39 | "src/scripts/style.js"
40 | ],
41 | "css": [
42 | "src/styles/main.css",
43 | "src/styles/today/today.css",
44 | "src/styles/today/schedule.css",
45 | "src/styles/today/widgets.css",
46 | "src/styles/gamification.css",
47 | "src/styles/grades.css",
48 | "src/styles/studyguide.css"
49 | ],
50 | "run_at": "document_start"
51 | },
52 | {
53 | "matches": [
54 | "*://*.magister.net/magister/*"
55 | ],
56 | "js": [
57 | "src/scripts/main.js",
58 | "src/scripts/today/schedule.js",
59 | "src/scripts/today/widgets.js",
60 | "src/scripts/today/today.js",
61 | "src/scripts/gamification.js",
62 | "src/scripts/grades/grades.js",
63 | "src/scripts/grades/list.js",
64 | "src/scripts/grades/backup.js",
65 | "src/scripts/grades/statistics.js",
66 | "src/scripts/grades/calculator.js",
67 | "src/scripts/studyguide.js",
68 | "src/scripts/books.js"
69 | ],
70 | "run_at": "document_end"
71 | },
72 | {
73 | "matches": [
74 | "*://accounts.magister.net/account/login*"
75 | ],
76 | "js": [
77 | "src/scripts/login.js"
78 | ],
79 | "run_at": "document_end"
80 | },
81 | {
82 | "matches": [
83 | "*://login.microsoftonline.com/*/oauth2/authorize*"
84 | ],
85 | "js": [
86 | "src/scripts/microsoft-login.js"
87 | ],
88 | "run_at": "document_end"
89 | },
90 | {
91 | "matches": [
92 | "*://study-tools.nl/*"
93 | ],
94 | "js": [
95 | "src/scripts/util.js",
96 | "src/scripts/theme-store.js"
97 | ]
98 | }
99 | ],
100 | "web_accessible_resources": [
101 | {
102 | "resources": [
103 | "src/strings/nl.json",
104 | "src/strings/en.json",
105 | "src/strings/fr.json",
106 | "src/strings/de.json",
107 | "src/strings/sv.json",
108 | "src/strings/la.json"
109 | ],
110 | "matches": [
111 | "*://*.magister.net/*",
112 | "*://study-tools.nl/*"
113 | ]
114 | }
115 | ],
116 | "action": {
117 | "default_icon": {
118 | "16": "icons/icon@16px.png",
119 | "32": "icons/icon@32px.png",
120 | "48": "icons/icon@48px.png",
121 | "64": "icons/icon@64px.png",
122 | "128": "icons/icon@128px.png",
123 | "256": "icons/icon@256px.png",
124 | "300": "icons/icon@300px.png",
125 | "512": "icons/icon@512px.png"
126 | },
127 | "default_popup": "popup/dist/index.html?type=popup",
128 | "default_title": "__MSG_appName__\nKlik om te configureren"
129 | },
130 | "options_page": "popup/dist/index.html?type=page",
131 | "options_ui": {
132 | "page": "popup/dist/index.html?type=options",
133 | "open_in_tab": true
134 | },
135 | "permissions": [
136 | "storage",
137 | "webRequest"
138 | ],
139 | "host_permissions": [
140 | "*://*.magister.net/*"
141 | ]
142 | }
--------------------------------------------------------------------------------
/manifest-firefox.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "__MSG_appName__",
4 | "description": "__MSG_appDesc__",
5 | "version": "3.15.1",
6 | "default_locale": "nl",
7 | "icons": {
8 | "16": "icons/icon@16px.png",
9 | "32": "icons/icon@32px.png",
10 | "48": "icons/icon@48px.png",
11 | "64": "icons/icon@64px.png",
12 | "128": "icons/icon@128px.png",
13 | "256": "icons/icon@256px.png",
14 | "300": "icons/icon@300px.png",
15 | "512": "icons/icon@512px.png"
16 | },
17 | "browser_specific_settings": {
18 | "gecko": {
19 | "id": "studytools@qkeleq10.dev"
20 | }
21 | },
22 | "background": {
23 | "scripts": [
24 | "src/background.js"
25 | ],
26 | "type": "module"
27 | },
28 | "content_scripts": [
29 | {
30 | "matches": [
31 | "*://*.magister.net/*",
32 | "*://login.microsoftonline.com/*/oauth2/authorize*"
33 | ],
34 | "js": [
35 | "src/scripts/api.js",
36 | "src/scripts/util.js"
37 | ],
38 | "run_at": "document_start"
39 | },
40 | {
41 | "matches": [
42 | "*://*.magister.net/*"
43 | ],
44 | "js": [
45 | "src/scripts/style.js"
46 | ],
47 | "css": [
48 | "src/styles/main.css",
49 | "src/styles/today/today.css",
50 | "src/styles/today/schedule.css",
51 | "src/styles/today/widgets.css",
52 | "src/styles/gamification.css",
53 | "src/styles/grades.css",
54 | "src/styles/studyguide.css"
55 | ],
56 | "run_at": "document_start"
57 | },
58 | {
59 | "matches": [
60 | "*://*.magister.net/magister/*"
61 | ],
62 | "js": [
63 | "src/scripts/main.js",
64 | "src/scripts/today/schedule.js",
65 | "src/scripts/today/widgets.js",
66 | "src/scripts/today/today.js",
67 | "src/scripts/gamification.js",
68 | "src/scripts/grades/grades.js",
69 | "src/scripts/grades/list.js",
70 | "src/scripts/grades/backup.js",
71 | "src/scripts/grades/statistics.js",
72 | "src/scripts/grades/calculator.js",
73 | "src/scripts/studyguide.js",
74 | "src/scripts/books.js"
75 | ],
76 | "run_at": "document_end"
77 | },
78 | {
79 | "matches": [
80 | "*://accounts.magister.net/account/login*"
81 | ],
82 | "js": [
83 | "src/scripts/login.js"
84 | ],
85 | "run_at": "document_end"
86 | },
87 | {
88 | "matches": [
89 | "*://login.microsoftonline.com/*/oauth2/authorize*"
90 | ],
91 | "js": [
92 | "src/scripts/microsoft-login.js"
93 | ],
94 | "run_at": "document_end"
95 | },
96 | {
97 | "matches": [
98 | "*://study-tools.nl/*"
99 | ],
100 | "js": [
101 | "src/scripts/util.js",
102 | "src/scripts/theme-store.js"
103 | ]
104 | }
105 | ],
106 | "web_accessible_resources": [
107 | {
108 | "resources": [
109 | "src/strings/nl.json",
110 | "src/strings/en.json",
111 | "src/strings/fr.json",
112 | "src/strings/de.json",
113 | "src/strings/sv.json",
114 | "src/strings/la.json"
115 | ],
116 | "matches": [
117 | "*://*.magister.net/*",
118 | "*://study-tools.nl/*"
119 | ]
120 | }
121 | ],
122 | "action": {
123 | "default_icon": {
124 | "16": "icons/icon@16px.png",
125 | "32": "icons/icon@32px.png",
126 | "48": "icons/icon@48px.png",
127 | "64": "icons/icon@64px.png",
128 | "128": "icons/icon@128px.png",
129 | "256": "icons/icon@256px.png",
130 | "300": "icons/icon@300px.png",
131 | "512": "icons/icon@512px.png"
132 | },
133 | "default_popup": "popup/dist/index.html?type=popup",
134 | "default_title": "__MSG_appName__\nKlik om te configureren"
135 | },
136 | "options_ui": {
137 | "page": "popup/dist/index.html?type=options",
138 | "open_in_tab": true
139 | },
140 | "permissions": [
141 | "storage",
142 | "webRequest"
143 | ],
144 | "host_permissions": [
145 | "*://*.magister.net/*"
146 | ]
147 | }
--------------------------------------------------------------------------------
/popup/dist/assets/lego-B9XBXV1o.js:
--------------------------------------------------------------------------------
1 | const A="data:image/png;base64,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";export{A as default};
2 |
--------------------------------------------------------------------------------
/popup/src/components/ShortcutsEditor.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | chevron_right
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Icoon
65 | URL
66 |
67 |
68 | editArray(i, { icon: v, href: value[i].href})" />
70 |
73 |
74 |
75 | delete
76 |
77 |
78 | keyboard_arrow_up
79 |
80 |
81 | keyboard_arrow_down
82 |
83 |
84 |
85 |
86 | Toevoegen
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/popup/src/components/inputs/ColorPicker.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
57 |
58 |
62 |
63 |
66 |
67 | palette
68 |
69 |
70 |
71 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/popup/src/assets/variables.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --palette-primary-hue: 207;
3 | --palette-primary-saturation: 95%;
4 | --palette-primary-luminance: 55%;
5 | --palette-primary: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) var(--palette-primary-luminance));
6 |
7 | --palette-secondary-hue: calc(var(--palette-primary-hue) + 30);
8 | --palette-secondary-saturation: 50%;
9 |
10 | --palette-neutral-hue: var(--palette-primary-hue);
11 | --palette-neutral-saturation: 30%;
12 |
13 | --palette-neutral-variant-hue: var(--palette-neutral-hue);
14 | --palette-neutral-variant-saturation: 30%;
15 |
16 | --color-primary: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 40%);
17 | --color-primary-container: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 90%);
18 | --color-on-primary: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 100%);
19 | --color-on-primary-container: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 10%);
20 | --color-secondary: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 40%);
21 | --color-secondary-container: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 90%);
22 | --color-on-secondary-container: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 10%);
23 | --color-surface: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 98%);
24 | --color-surface-container-lowest: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 100%);
25 | --color-surface-container-low: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 96%);
26 | --color-surface-container: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 94%);
27 | --color-surface-container-high: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 92%);
28 | --color-surface-container-highest: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 90%);
29 | --color-surface-variant: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 90%);
30 | --color-on-surface: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 10%);
31 | --color-on-surface-variant: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 30%);
32 | --color-outline: hsl(var(--palette-neutral-variant-hue) var(--palette-neutral-variant-saturation) 50%);
33 | --color-outline-variant: hsl(var(--palette-neutral-variant-hue) var(--palette-neutral-variant-saturation) 80%);
34 | --color-shadow: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 0%);
35 | --color-scrim: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 0%);
36 |
37 | --typescale-headline-small: 400 24px/32px 'Noto Sans', sans-serif;
38 | --typescale-title-large: 400 22px/28px 'Noto Sans', sans-serif;
39 | --typescale-title-small: 500 14px/20px 'Noto Sans', sans-serif;
40 | --typescale-label-large: 500 14px/20px 'Noto Sans', sans-serif;
41 | --typescale-label-medium: 500 12px/16px 'Noto Sans', sans-serif;
42 | --typescale-body-large: 400 16px/24px 'Noto Sans', sans-serif;
43 | --typescale-body-medium: 400 14px/20px 'Noto Sans', sans-serif;
44 | --typescale-body-small: 400 12px/16px 'Noto Sans', sans-serif;
45 |
46 | --mg-blue: hsl(207, 95%, 55%);
47 | --mg-orange: hsl(30, 100%, 51%);
48 | --mg-alt-green: hsl(161deg, 51%, 41%);
49 | --mg-alt-yellow: hsl(40deg, 51%, 41%);
50 | --mg-alt-red: hsl(360deg, 51%, 41%);
51 | --mg-alt-pink: hsl(331deg, 51%, 41%);
52 | --mg-alt-purple: hsl(266deg, 51%, 41%);
53 |
54 | --mg-bk-light-1: #ffffff;
55 | --mg-bk-light-2: #ffffff;
56 | --mg-fg-light: #000;
57 | --mg-br-light: #ededed;
58 | --mg-bk-dark-1: #121212;
59 | --mg-bk-dark-2: #161616;
60 | --mg-fg-dark: #fff;
61 | --mg-br-dark: #2e2e2e;
62 |
63 | color-scheme: only light;
64 | }
65 |
66 | :root[theme~=dark] {
67 | --color-primary: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 80%);
68 | --color-primary-container: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 30%);
69 | --color-on-primary: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 20%);
70 | --color-on-primary-container: hsl(var(--palette-primary-hue) var(--palette-primary-saturation) 90%);
71 | --color-secondary: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 80%);
72 | --color-secondary-container: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 30%);
73 | --color-on-secondary-container: hsl(var(--palette-secondary-hue) var(--palette-secondary-saturation) 90%);
74 | --color-surface: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 6%);
75 | --color-surface-container-lowest: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 4%);
76 | --color-surface-container-low: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 10%);
77 | --color-surface-container: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 12%);
78 | --color-surface-container-high: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 17%);
79 | --color-surface-container-highest: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 22%);
80 | --color-surface-variant: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 30%);
81 | --color-on-surface: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 90%);
82 | --color-on-surface-variant: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 80%);
83 | --color-outline: hsl(var(--palette-neutral-variant-hue) var(--palette-neutral-variant-saturation) 60%);
84 | --color-outline-variant: hsl(var(--palette-neutral-variant-hue) var(--palette-neutral-variant-saturation) 30%);
85 | --color-shadow: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 0%);
86 | --color-scrim: hsl(var(--palette-neutral-hue) var(--palette-neutral-saturation) 0%);
87 |
88 | color-scheme: only dark;
89 | }
--------------------------------------------------------------------------------
/popup/src/components/setting-types/SingleChoice.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/scripts/grades/list.js:
--------------------------------------------------------------------------------
1 | class GradeListPane extends Pane {
2 | id = 'cl';
3 | icon = '';
4 |
5 | #div1;
6 | #div2;
7 |
8 | sortingOptions = [
9 | {
10 | id: 'date_desc', label: i18n('cl.sortByDateDesc'), comparator: (a, b) => {
11 | const dateA = new Date(a.DatumIngevoerd);
12 | const dateB = new Date(b.DatumIngevoerd);
13 | return dateB.getTime() - dateA.getTime();
14 | }
15 | },
16 | {
17 | id: 'date_asc', label: i18n('cl.sortByDateAsc'), comparator: (a, b) => {
18 | const dateA = new Date(a.DatumIngevoerd);
19 | const dateB = new Date(b.DatumIngevoerd);
20 | return dateA.getTime() - dateB.getTime();
21 | }
22 | },
23 | {
24 | id: 'result_desc', label: i18n('cl.sortByResultDesc'), comparator: (a, b) => {
25 | const resultA = Number(a.CijferStr?.replace(',', '.'));
26 | const resultB = Number(b.CijferStr?.replace(',', '.'));
27 | if (isNaN(resultA) && isNaN(resultB)) return a.CijferStr.localeCompare(b.CijferStr);
28 | if (isNaN(resultA)) return 1;
29 | if (isNaN(resultB)) return -1;
30 | return resultB - resultA;
31 | }
32 | },
33 | {
34 | id: 'result_asc', label: i18n('cl.sortByResultAsc'), comparator: (a, b) => {
35 | const resultA = Number(a.CijferStr?.replace(',', '.'));
36 | const resultB = Number(b.CijferStr?.replace(',', '.'));
37 | if (isNaN(resultA) && isNaN(resultB)) return a.CijferStr.localeCompare(b.CijferStr);
38 | if (isNaN(resultA)) return 1;
39 | if (isNaN(resultB)) return -1;
40 | return resultA - resultB;
41 | }
42 | },
43 | ];
44 | sortingOption = this.sortingOptions[0];
45 |
46 | constructor(parentElement) {
47 | super(parentElement);
48 |
49 | this.element.id = 'st-grade-recents-pane';
50 | this.element.classList.remove('st-hidden');
51 |
52 | this.#div1 = this.element.createChildElement('div', { class: 'st-div', style: 'margin-bottom: 16px' });
53 | this.#div1.createChildElement('h3', { class: 'st-section-heading', innerText: i18n('cl.title') });
54 | const select = this.#div1.createChildElement('select', { class: 'st-select', style: 'width: 100%' });
55 | for (const option of this.sortingOptions) {
56 | const optionElement = select.createChildElement('option', { value: option.id, innerText: option.label });
57 | if (option === this.sortingOption) {
58 | optionElement.selected = true;
59 | }
60 | }
61 | select.addEventListener('change', () => {
62 | const selectedOption = this.sortingOptions.find(option => option.id === select.value);
63 | if (selectedOption && selectedOption !== this.sortingOption) {
64 | this.sortingOption = selectedOption;
65 | this.redraw();
66 | }
67 | });
68 | this.element.createChildElement('hr');
69 | this.#div2 = this.element.createChildElement('div', { class: 'st-div' });
70 | }
71 |
72 | show() {
73 | this.redraw();
74 | super.show();
75 | }
76 |
77 | async redraw() {
78 | this.progressBar.dataset.visible = 'true';
79 |
80 | this.#div2.innerHTML = '';
81 |
82 | this.#div1.firstElementChild.innerText = this.sortingOption.id === 'date_desc' ? i18n('cl.recents') : i18n('cl.title');
83 |
84 | const recentGrades = await magisterApi.gradesRecent(currentGradeTable.grades.length);
85 |
86 | const grades = currentGradeTable.grades.filter(g => g.CijferStr?.length > 0 && g.CijferKolom?.KolomSoort !== 2 && !syncedStorage['ignore-grade-columns'].includes(g.CijferKolom?.KolomKop || 'undefined'));
87 | grades.sort(this.sortingOption.comparator);
88 |
89 | // === EMPTY ===
90 |
91 | if (grades.length === 0) {
92 | this.#div2.createChildElement('p', { innerText: i18n('cl.emptyDesc') });
93 | this.progressBar.dataset.visible = 'false';
94 | return;
95 | }
96 |
97 | // === LIST ===
98 |
99 | const list = this.#div2.createChildElement('ul', { class: 'st-grade-list' });
100 | for (const grade of grades) {
101 | const recentGrade = recentGrades.find(rg => rg.kolomId === grade.CijferKolom.Id);
102 |
103 | const gradeItem = list.createChildElement('li', { class: 'st-grade-item' });
104 |
105 | const col1 = gradeItem.createChildElement('div')
106 | col1.createChildElement('div', { class: 'st-subject', innerText: grade.Vak?.Omschrijving || '-' })
107 | if (grade.CijferKolom.WerkInformatieOmschrijving || grade.CijferKolom.KolomOmschrijving || recentGrade?.omschrijving) col1.createChildElement('div', { innerText: grade.CijferKolom.WerkInformatieOmschrijving || grade.CijferKolom.KolomOmschrijving || recentGrade?.omschrijving || '-' })
108 | col1.createChildElement('div', { innerText: makeTimestamp(grade.DatumIngevoerd) });
109 |
110 | const col2 = gradeItem.createChildElement('div')
111 | col2.createChildElement('div', { innerText: grade.CijferStr, classList: grade.IsVoldoende === false ? ['st-insufficient'] : [] })
112 | if (grade.CijferKolom?.Weging ?? recentGrade) col2.createChildElement('div', { innerText: (grade.CijferKolom?.Weging ?? recentGrade?.weegfactor ?? '?') + 'x' });
113 |
114 | if (
115 | new Date(grade.DatumIngevoerd) >= new Date(new Date(localStorage['st-grade-last-viewed'] || 0))
116 | && new Date(grade.DatumIngevoerd) >= new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
117 | )
118 | gradeItem.classList.add('st-highlight');
119 |
120 | gradeItem.classList.add('st-clickable');
121 | gradeItem.addEventListener('click', () => {
122 | const dialog = new GradeDetailDialog(grade, currentGradeTable.identifier.year);
123 | dialog.show();
124 | });
125 | }
126 |
127 | localStorage['st-grade-last-viewed'] = new Date().toISOString();
128 |
129 | this.progressBar.dataset.visible = 'false';
130 | }
131 | }
--------------------------------------------------------------------------------
/popup/src/components/ImageInput.vue:
--------------------------------------------------------------------------------
1 |
62 |
63 |
64 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/styles/studyguide.css:
--------------------------------------------------------------------------------
1 | #st-sw-container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: flex-start;
5 | gap: 8px;
6 | height: auto;
7 | max-height: min(100%, calc(100vh - 125px));
8 | overflow-y: auto;
9 | }
10 |
11 | .st-sw-col {
12 | flex: 1 1 0px;
13 | display: flex;
14 | flex-wrap: wrap;
15 | gap: 8px;
16 | padding: 1px;
17 | width: 100%;
18 | }
19 |
20 | #st-sw-search {
21 | position: absolute;
22 | top: 70px;
23 | right: 25px;
24 | z-index: 1000;
25 | }
26 |
27 | .st-sw-item.hidden,
28 | .st-sw-subject.hidden {
29 | display: none;
30 | }
31 |
32 | .st-sw-item.hidden-item:before,
33 | .st-sw-item-default.hidden-item>.st-sw-item-default-desc:before {
34 | content: '';
35 | font-family: 'Font Awesome 6 Pro';
36 | font-weight: 500;
37 | font-size: 14px;
38 | vertical-align: -2px;
39 | margin-right: 8px;
40 | }
41 |
42 | #st-sw-item-hider {
43 | position: absolute;
44 | top: 73px;
45 | right: 154px;
46 | z-index: 2;
47 | }
48 |
49 | #st-sw-hidden-items {
50 | display: none;
51 | flex-direction: column;
52 | position: absolute;
53 | bottom: 20px;
54 | right: 25px;
55 | width: 300px;
56 | border-radius: var(--st-border-radius);
57 | overflow: hidden;
58 | }
59 |
60 | #st-sw-hidden-items.st-expanded {
61 | display: flex;
62 | border: var(--st-border);
63 | animation: expandIn 150ms both;
64 | }
65 |
66 | #studiewijzer-detail-container dna-page-header {
67 | clip-path: inset(0 15% 0 0);
68 | }
69 |
70 | #st-sw-hidden-items-button {
71 | position: absolute;
72 | bottom: 20px;
73 | right: 25px;
74 | border: none;
75 | background-color: transparent;
76 | opacity: .75;
77 | font: 14px var(--st-font-family-secondary);
78 | background-color: var(--st-background-secondary);
79 | border: var(--st-border);
80 | transition: background-color 200ms, color 200ms, border 200ms, padding 200ms, margin 200ms, transform 200ms;
81 | }
82 |
83 | #st-sw-hidden-items-button.st-collapsed {
84 | animation: shrinkOut 150ms, displayOut 150ms both;
85 | pointer-events: none;
86 | }
87 |
88 | .st-sw-subject {
89 | flex: 1 0 50%;
90 | box-sizing: border-box;
91 | display: flex;
92 | flex-direction: column;
93 | border-radius: var(--st-border-radius);
94 | border: var(--st-border);
95 | overflow: hidden;
96 | }
97 |
98 | .st-sw-subject:not(:has(button:not(.hidden))) {
99 | display: none;
100 | }
101 |
102 | .st-sw-items-wrapper {
103 | display: flex;
104 | flex-wrap: wrap;
105 | flex-direction: column;
106 | }
107 |
108 | .st-sw-items-wrapper>*:not(:first-child) {
109 | border-top: var(--st-border);
110 | }
111 |
112 | .st-sw-items-wrapper[data-flex-row=true] {
113 | flex-direction: row;
114 | }
115 |
116 | .st-sw-subject button,
117 | #st-sw-hidden-items button {
118 | position: relative;
119 | outline: 0;
120 | border: none;
121 | color: var(--st-foreground-primary);
122 | text-align: left;
123 | cursor: pointer;
124 | }
125 |
126 | .st-sw-item-default,
127 | .st-sw-subject-headline {
128 | --padding-block: 12px;
129 | display: flex;
130 | flex-direction: column;
131 | grid-column: 1 / -1;
132 | grid-row: 1;
133 | padding: 12px;
134 | padding-block: var(--padding-block);
135 | font: 600 16px/22px var(--st-font-family-secondary);
136 | background-color: var(--st-highlight-primary);
137 | color: var(--st-foreground-primary);
138 | }
139 |
140 | .st-sw-subject-headline {
141 | border-bottom: var(--st-border);
142 | }
143 |
144 | .st-sw-item-default-desc {
145 | font: 12px var(--st-font-family-secondary);
146 | }
147 |
148 | .st-sw-item-default-desc[data-2nd]:after {
149 | content: attr(data-2nd);
150 | position: absolute;
151 | width: 100%;
152 | bottom: var(--padding-block);
153 | left: 12px;
154 | background-color: var(--st-highlight-primary);
155 | opacity: 0;
156 | }
157 |
158 | .st-sw-item-default:hover .st-sw-item-default-desc[data-2nd]:after {
159 | opacity: 1;
160 | }
161 |
162 | .st-sw-item {
163 | flex: 1 1 0px;
164 | padding: 10px 12px;
165 | font: 12px var(--st-font-family-secondary);
166 | background-color: var(--st-background-secondary);
167 | }
168 |
169 | .st-sw-item[data-2nd]:hover {
170 | color: transparent;
171 | }
172 |
173 | .st-sw-item[data-2nd]:after {
174 | content: attr(data-2nd);
175 | position: absolute;
176 | height: 100%;
177 | width: 100%;
178 | top: 0;
179 | left: 0;
180 | padding: 10px 12px;
181 | background-color: var(--st-background-secondary);
182 | color: var(--st-foreground-primary);
183 | opacity: 0;
184 | }
185 |
186 | .st-sw-item[data-2nd]:hover:after {
187 | opacity: 1;
188 | }
189 |
190 | .st-current,
191 | .st-sw-2 {
192 | font-weight: 700;
193 | }
194 |
195 | .st-obsolete,
196 | .st-obsolete span,
197 | .st-sw-0 {
198 | color: #888 !important
199 | }
200 |
201 | #st-sw-container button:hover,
202 | #st-sw-container button:focus,
203 | #st-sw-container button.st-sw-selected {
204 | filter: brightness(var(--st-hover-brightness));
205 | }
206 |
207 | .st-current-sw>div>div>footer.endlink,
208 | .st-current-sw>div>h3,
209 | .st-current-sw>div>h3>b {
210 | background: var(--st-highlight-primary);
211 | font-weight: 700
212 | }
213 |
214 | .widget .st-sw-item-default,
215 | .widget .st-sw-subject-headline {
216 | --padding-block: 4px;
217 | font-size: 14px;
218 | }
219 |
220 | #st-hb-sheet>div {
221 | display: flex;
222 | flex-direction: column;
223 | gap: 6px;
224 | padding: 8px;
225 | margin-inline: -4px;
226 | box-shadow: 0 0 8px 0 rgba(var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-alpha));
227 | background-color: var(--st-background-secondary);
228 | border-radius: var(--st-border-radius);
229 | border: var(--st-border);
230 | }
231 |
232 | #st-hb-sheet-heading {
233 | align-items: start;
234 | gap: 4px;
235 | font-size: 16px;
236 | margin-bottom: 0;
237 | }
238 |
239 | #st-hb-sheet-heading[data-description]:after {
240 | font-size: 12px;
241 | text-align: left;
242 | }
243 |
244 | @media (height < 700px) {
245 |
246 | .st-sw-item-default,
247 | .st-sw-subject-headline {
248 | --padding-block: 8px;
249 | }
250 | }
251 |
252 | @media (width > 768px) {
253 | div.view>#st-sw-container {
254 | flex-direction: row;
255 | gap: 12px;
256 | margin-inline: 24px;
257 | }
258 | }
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | import settings from '../popup/dist/settings.js'
2 |
3 | let apiUserId,
4 | apiUserToken,
5 | apiUserTokenDate
6 |
7 | const settingsToClear = [
8 | 'auto-theme', 'theme-fixed', 'theme-day', 'theme-night', 'openedPopup', 'updates', 'beta', 'magister-shortcuts', 'magister-shortcuts-today', 'magister-sw-grid', 'magister-sw-sort', 'magister-sw-period', 'magister-sw-display', 'magister-ag-large', 'magister-subjects', 'magister-appbar-hidePicture', 'appbar-hide-actions', 'magister-appbar-zermelo', 'magister-appbar-zermelo-url', 'magister-css-border-radius', 'magister-css-dark-invert', 'magister-css-experimental', 'magister-css-hue', 'magister-css-luminance', 'magister-css-saturation', 'magister-css-theme', 'magister-op-oldgrey', 'magister-periods', 'periods', 'magister-shortcut-keys', 'magister-shortcut-keys-master', 'magister-shortcut-keys-today', 'magister-subjects', 'magister-sw-thisWeek', 'magister-vd-overhaul', 'magister-vd-enabled', 'magister-vd-subjects', 'magister-vd-grade', 'magister-vd-agendaHeight', 'magister-vd-deblue', 'magister-vd-gradewidget', 'magisterLogin-password', 'magisterLogin-method', 'magister-gamification-beta', 'gamification-enabled', 'magister-cf-calculator', 'magister-cf-statistics', 'magister-cf-backup', 'magister-cf-failred', 'notes-enabled', 'notes', 'st-notes', 'vd-enabled', 'vd-schedule-days', 'vd-schedule-extra-day', 'vd-schedule-zoom', 'vd-subjects-display', 'start-stats', 'teacher-names', 'version', 'disable-css', 'hotkeys-today', 'start-widgets', 'dark-image', 'light-image', 'subjects', 'hidden-studyguides', 'color', 'start-schedule-days', 'v', 'special'
9 | ]
10 |
11 | startListenCredentials()
12 | setDefaults()
13 | console.info("Service worker running!")
14 |
15 | async function startListenCredentials() {
16 | // Initialise the three variables
17 | apiUserId = (await browser.storage.sync.get('user-id'))?.['user-id'] || null
18 | apiUserToken = (await browser.storage.local.get('token'))?.['token'] || null
19 | apiUserTokenDate = (await browser.storage.local.get('token-date'))?.['token-date'] || null
20 |
21 | browser.webRequest.onBeforeSendHeaders.addListener(async e => {
22 | let userIdWas = apiUserId
23 | let userTokenWas = apiUserToken
24 | if (e.url.split('/personen/')[1]?.split('/')[0].length > 2) {
25 | apiUserId = e.url.split('/personen/')[1].split('/')[0]
26 | browser.storage.sync.set({ 'user-id': apiUserId })
27 | if (userIdWas !== apiUserId) console.info(`User ID changed from ${userIdWas} to ${apiUserId}.`)
28 | }
29 | let authObject = Object.values(e.requestHeaders).find(obj => obj.name === 'Authorization')
30 | if (authObject) {
31 | apiUserToken = authObject.value
32 | apiUserTokenDate = new Date()
33 | browser.storage.local.set({ 'token': apiUserToken })
34 | browser.storage.local.set({ 'token-date': apiUserTokenDate.getTime() })
35 | if (userTokenWas !== apiUserToken) console.info(`User token changed between ${new Date().toLocaleDateString()} and now.`)
36 | }
37 |
38 | }, { urls: ['*://*.magister.net/*'] }, ['requestHeaders'])
39 |
40 | console.info("Intercepting HTTP request information to extract token and userId...%c\n\nVrees niet, dit is alleen nodig zodat de extensie API-verzoeken kan maken naar Magister. Deze gegevens blijven op je apparaat. Dit wordt momenteel alleen gebruikt voor de volgende onderdelen:\n" + ["cijferexport", "widgets startpagina", "rooster startpagina", "puntensysteem"].join(', ') + "\n\nen in de toekomst eventueel ook voor:\n" + [].join(', '), "font-size: .8em")
41 | }
42 |
43 | async function setDefaults() {
44 | let syncedStorage = await browser.storage.sync.get()
45 | let diff = {}
46 |
47 | // Check each setting to see if its value has been defined. If not, set it to the default value.
48 | settings.forEach(category => {
49 | category.settings.forEach(setting => {
50 | if (typeof syncedStorage[setting.id] === 'undefined') {
51 | if (setting.id === 'wallpaper' && syncedStorage['backdrop']?.length > 5) diff[setting.id] = 'custom,' + syncedStorage['backdrop']
52 | else diff[setting.id] = setting.default
53 | }
54 | })
55 | })
56 |
57 | if (Object.keys(diff).length > 0) {
58 | setTimeout(() => browser.storage.sync.set(diff), 200)
59 | console.info("Set the following storage.sync keys to their default values:", diff)
60 | }
61 |
62 | if (settingsToClear.some(key => Object.keys(syncedStorage).includes(key))) {
63 | browser.storage.sync.remove(settingsToClear)
64 | console.info("Redundant storage.sync keys removed to free up space.")
65 | }
66 | }
67 |
68 | browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
69 | switch (request.action) {
70 | case 'popstateDetected':
71 | console.info("Popstate detected, service worker revived for 30 seconds.")
72 | return 0
73 |
74 | case 'waitForRequestCompleted':
75 | console.info(`Request completion notification requested by ${sender.url}.`)
76 | browser.webRequest.onCompleted.addListener((details) => {
77 | sendResponse({ status: 'completed', details: details })
78 | console.info(`Request completion notification sent to ${sender.url}.`)
79 | }, { urls: ['*://*.magister.net/api/personen/*/aanmeldingen/*/cijfers/extracijferkolominfo/*'] })
80 | setTimeout(() => {
81 | sendResponse({ status: 'timeout' })
82 | console.warn(`Request completion notification requested by ${sender.url} has timed out.`)
83 | }, 5000)
84 | return true
85 |
86 | case 'uninstallSelf':
87 | chrome.management.uninstallSelf({ showConfirmDialog: false }, () => { window.location.reload() })
88 | break
89 |
90 | case 'openOptions':
91 | chrome.tabs.create({ url: `index.html?${request.data}` });
92 | break;
93 |
94 | default:
95 | return 0
96 | }
97 | })
98 |
99 | browser.runtime.onMessageExternal.addListener(async (request, sender, sendResponse) => {
100 | switch (request.action) {
101 | case 'addPersonalTheme':
102 | const obj = request.obj
103 | const storedThemes = Object.values((await chrome.storage.local.get('storedThemes')).storedThemes)
104 | if (!storedThemes || storedThemes.length >= 9) return
105 |
106 | storedThemes.push(obj)
107 |
108 | //TODO: only if not exist
109 |
110 | await chrome.storage.local.set({ 'storedThemes': storedThemes })
111 | break
112 |
113 | default:
114 | return 0
115 | }
116 | })
117 |
--------------------------------------------------------------------------------
/src/styles/gamification.css:
--------------------------------------------------------------------------------
1 | #st-wrapped-invoke {
2 | width: 50px;
3 | padding: 0;
4 | margin-inline: auto;
5 | background-image: linear-gradient(35deg, #ffffff, #7cc4ff9c);
6 | background-clip: text;
7 | -webkit-background-clip: text;
8 | text-fill-color: transparent;
9 | color: transparent;
10 | border: none;
11 | border-radius: var(--st-border-radius);
12 | outline: none;
13 | font: 600 30px/50px 'Font Awesome 6 Pro';
14 | text-align: center;
15 | opacity: 0.5;
16 | cursor: pointer;
17 | transition: scale 200ms, opacity 200ms;
18 | }
19 |
20 | #st-wrapped-invoke.spinning {
21 | animation: spin 1s linear infinite;
22 | }
23 |
24 | @keyframes spin {
25 | to {
26 | rotate: 359.9deg;
27 | }
28 | }
29 |
30 | #st-wrapped-invoke:hover,
31 | #st-wrapped-invoke:focus-visible {
32 | scale: 1.1;
33 | opacity: 1;
34 | transition: scale 100ms, opacity 100ms;
35 | }
36 |
37 | #st-wrapped-invoke:focus-visible {
38 | outline: 2px solid var(--st-foreground-primary);
39 | }
40 |
41 | #st-wrapped-invoke-tip {
42 | position: absolute;
43 | top: 24px;
44 | left: 58px;
45 | height: auto;
46 | background-color: var(--st-accent-primary-dark);
47 | color: var(--st-contrast-accent);
48 | box-shadow: 0 0 8px 0 rgba(var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-alpha));
49 | z-index: 100;
50 | transform-origin: left center;
51 | transform: none;
52 | opacity: 1;
53 | padding: 7px 13px;
54 | border: 1px solid var(--st-contrast-accent);
55 | border-radius: var(--st-border-radius);
56 | font: 12px var(--st-font-family-secondary);
57 | transition: transform 200ms ease 0s, opacity 200ms ease 0s;
58 | }
59 |
60 | #st-wrapped-invoke-tip:after {
61 | content: "";
62 | position: absolute;
63 | top: 50%;
64 | right: 100%;
65 | margin-top: -5px;
66 | border-width: 5px;
67 | border-style: solid;
68 | border-color: transparent var(--st-contrast-accent) transparent transparent;
69 | }
70 |
71 | #st-wrapped-invoke-tip.hidden {
72 | transform: scale(0);
73 | opacity: 0;
74 | }
75 |
76 | #st-wrapped {
77 | padding: 0;
78 | border-radius: 16px;
79 | border: 1px solid #444;
80 | outline: none;
81 | background-color: transparent;
82 | user-select: none;
83 | }
84 |
85 | #st-wrapped::backdrop {
86 | background-color: #121212f7;
87 | }
88 |
89 | #st-wrapped-years-wrapper {
90 | width: min(calc(100vw - 64px), 1300px);
91 | height: min(calc(100vh - 64px), 850px);
92 | overflow: hidden;
93 |
94 | container-type: inline-size;
95 | display: flex;
96 | gap: 32px;
97 | background-color: transparent;
98 | }
99 |
100 | .st-wrapped-year {
101 | --pattern: linear-gradient(to right, transparent, transparent);
102 | --gradient: linear-gradient(to right, #485563, #29323c);
103 | min-height: min(calc(100vh - 64px), 850px);
104 | max-height: min(calc(100vh - 64px), 850px);
105 | flex: 100% 0 0;
106 |
107 | display: grid;
108 | grid-template-columns: repeat(3, 1fr);
109 | grid-auto-rows: 28px;
110 | grid-auto-flow: row dense;
111 | gap: 16px;
112 | padding: 32px;
113 | padding-top: 40px;
114 |
115 | overflow-y: auto;
116 | border-radius: 16px;
117 | background-image: radial-gradient(at left top, #20283110, #1218201c), var(--pattern), var(--gradient);
118 | color: #ffffff;
119 | }
120 |
121 | @container (max-width: 1000px) {
122 | .st-wrapped-year {
123 | grid-template-columns: repeat(2, 1fr);
124 | }
125 | }
126 |
127 | .st-wrapped-year-title {
128 | grid-column: 1 / -1;
129 | font: 500 28px/2rem arboria, sans-serif;
130 | translate: 0 -8px;
131 | }
132 |
133 | .st-wrapped-card {
134 | position: relative;
135 | display: flex;
136 | flex-direction: column;
137 | align-items: center;
138 | justify-content: center;
139 | gap: 8px;
140 | padding: 32px;
141 | padding-top: 36px;
142 |
143 | background: #ffffff33;
144 | border-radius: 16px;
145 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
146 | border: 1px solid #ffffff11;
147 |
148 | font: 500 22px arboria, sans-serif;
149 | color: #ffffff;
150 | text-align: left;
151 | text-wrap: balance;
152 | user-select: none;
153 | overflow: hidden;
154 | }
155 |
156 | .st-wrapped-card.grid {
157 | display: grid;
158 | gap: 12px;
159 | }
160 |
161 | .st-wrapped-card.interactable,
162 | .st-wrapped-card.external-link {
163 | cursor: pointer;
164 | transition: transform 200ms;
165 | }
166 |
167 | .st-wrapped-card.interactable:hover,
168 | .st-wrapped-card.external-link:hover {
169 | transform: scale(1.02);
170 | }
171 |
172 | .st-wrapped-card.interactable:active,
173 | .st-wrapped-card.external-link:active {
174 | transform: scale(0.97);
175 | }
176 |
177 | .st-wrapped-card.interactable[data-pages][data-page]::after {
178 | content: attr(data-page) '/' attr(data-pages);
179 | position: absolute;
180 | top: 12px;
181 | right: 12px;
182 | color: #ffffff66;
183 | font: 14px var(--st-font-family-secondary);
184 | }
185 |
186 | .st-wrapped-card.external-link::after {
187 | content: '';
188 | position: absolute;
189 | top: 12px;
190 | right: 12px;
191 | color: #ffffff66;
192 | font: 500 16px "Font Awesome 6 Pro";
193 | }
194 |
195 | .st-wrapped-card[data-icon]::before {
196 | content: attr(data-icon);
197 | position: absolute;
198 | top: 12px;
199 | left: 12px;
200 | color: #ffffff66;
201 | font: 600 16px "Font Awesome 6 Pro";
202 | }
203 |
204 | .st-wrapped-card .st-w-metric {
205 | font: 500 42px arboria, sans-serif;
206 | color: #ffffff;
207 | text-align: center;
208 | }
209 |
210 | .st-wrapped-card .st-w-metric-med {
211 | font: 500 36px arboria, sans-serif;
212 | color: #ffffff;
213 | text-align: center;
214 | }
215 |
216 | .st-wrapped-card .st-w-text {
217 | font: 500 22px arboria, sans-serif;
218 | color: #ffffff;
219 | text-align: center;
220 | }
221 |
222 | .st-wrapped-card .st-w-text-small {
223 | font: 500 18px arboria, sans-serif;
224 | color: #ffffffdd;
225 | text-align: center;
226 | }
227 |
228 | .st-wrapped-card .st-w-text-tiny {
229 | font: 500 16px arboria, sans-serif;
230 | color: #ffffffdd;
231 | text-align: center;
232 | }
233 |
234 | .st-wrapped-card .st-w-line-vertical {
235 | height: 100%;
236 | border-left: 1px solid #ffffff33;
237 | }
238 |
239 | #st-wrapped .st-button.icon {
240 | color: #ffffff;
241 | }
242 |
243 | #st-wrapped-esc {
244 | position: absolute;
245 | top: 32px;
246 | right: 32px;
247 | }
248 |
249 | #st-wrapped-info {
250 | position: absolute;
251 | top: 32px;
252 | right: 68px;
253 | }
254 |
255 | #st-wrapped-next {
256 | position: absolute;
257 | top: 32px;
258 | right: 104px;
259 | }
260 |
261 | #st-wrapped-prev {
262 | position: absolute;
263 | top: 32px;
264 | right: 140px;
265 | }
--------------------------------------------------------------------------------
/popup/src/components/About.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 |
49 |
Study Tools voor Magister (
51 | versie {{ manifest.version?.replace(/^([0-9]+(\.[0-9]+){2})(\.[1-9]+)$/gi, '$1-beta$3') || "onbekend" }}
52 | )
53 |
54 | Ontwikkeld door Quinten Althues
55 | Bedankt voor het gebruiken van Study Tools!
56 |
57 |
58 |
59 |
60 | captive_portal Website
61 |
62 |
63 | alternate_email E-mail
64 |
65 |
66 | forum Discord
67 |
68 |
69 | volunteer_activism PayPal
70 |
71 |
72 | shield_locked Privacybeleid
73 |
74 |
75 |
76 | info
77 | Informatie
78 |
79 | Deze extensie slaat gegevens over je identiteit, je accounts en je instellingen op in de
80 | browser. Afhankelijk van je browserinstellingen worden ze al dan niet opgeslagen in de cloud. Er wordt nooit
81 | informatie doorgestuurd naar de ontwikkelaar of naar derden.
82 |
83 | Ik kan onder geen enkele omstandigheid je gegevens zien. Ik kan alleen zien hoe veel gebruikers mijn
84 | extensie gebruiken en andere statistieken zoals percentages van besturingssystemen. Ik kan dus niet per
85 | gebruiker dingen zien en Magister-gegevens zijn compleet ontoegankelijk voor mij.
86 |
87 |
88 | Begrepen
89 |
90 |
91 |
92 |
93 |
94 | Voorkeuren wissen
95 |
96 |
97 | chevron_right
98 |
99 |
100 | restart_alt
101 | Voorkeuren wissen?
102 | Hiermee stel je alle instellingen van Study Tools in op de standaardwaarden.
103 |
104 | Annuleren
105 | Wissen
106 |
107 |
108 |
109 |
110 |
111 | Voorkeuren kopiëren/plakken
112 |
113 |
114 | chevron_right
115 |
116 |
117 | copy_all
118 | Voorkeuren kopiëren/plakken
119 | Kopieer de inhoud van het tekstvak om je voorkeuren op te slaan op je klembord. Plak in het
120 | tekstvak om voorkeuren te wijzigen. Als je plakt, dan gaan al je huidige voorkeuren verloren.
121 |
122 | Plak hier
123 |
124 |
125 |
126 | Sluiten
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/styles/today/today.css:
--------------------------------------------------------------------------------
1 | @property --hour-height {
2 | syntax: "";
3 | inherits: true;
4 | initial-value: 110px;
5 | }
6 |
7 | @property --progress {
8 | syntax: "";
9 | inherits: true;
10 | initial-value: 0;
11 | }
12 |
13 | #st-start button.st-widget-subitem:focus-visible,
14 | #st-start button.st-event:focus-visible {
15 | outline: 2px solid var(--st-foreground-accent);
16 | outline-offset: -2px;
17 | }
18 |
19 | #st-start {
20 | position: absolute;
21 | top: 0;
22 | left: 0;
23 | height: 100%;
24 | width: 100%;
25 | display: grid;
26 | grid-template:
27 | 'header widgets' auto
28 | 'schedule widgets' 1fr
29 | / 1fr 400px;
30 | overflow: hidden;
31 | transition: grid-template 200ms, grid-template-columns 200ms;
32 | }
33 |
34 | #st-start-header-text-wrapper {
35 | display: grid;
36 | grid-template-columns: 100% 100%;
37 | height: 36px;
38 | overflow: hidden;
39 |
40 | background-color: transparent;
41 | border: none;
42 | text-align: left;
43 |
44 | container-type: size;
45 |
46 | &>* {
47 | grid-area: 1 / 1 / 2 / 2;
48 | text-wrap: nowrap;
49 | text-overflow: ellipsis;
50 | cursor: pointer;
51 | height: 100%;
52 | margin-bottom: 0;
53 | overflow: hidden;
54 |
55 | display: block;
56 | clip-path: inset(0 0 0 0);
57 | transition: display 1000ms allow-discrete;
58 | animation: wipeIn 1000ms 50ms both;
59 |
60 | &.not-today {
61 | font-style: normal;
62 | font-weight: 500;
63 | color: var(--st-foreground-secondary);
64 | }
65 |
66 | &::first-letter {
67 | text-transform: capitalize;
68 | }
69 |
70 | :focus-visible {
71 | outline: 2px solid var(--st-foreground-accent);
72 | outline-offset: -2px;
73 | }
74 | }
75 | }
76 |
77 | #st-start-header-short-title,
78 | #st-start-header-greeting {
79 | display: none;
80 | animation: wipeOut 1000ms both;
81 | }
82 |
83 | @container (width < 280px) {
84 | #st-start-header-title {
85 | display: none;
86 | animation: wipeOut 1000ms both;
87 | }
88 |
89 | #st-start-header-short-title {
90 | display: block;
91 | animation: wipeIn 1000ms 50ms both;
92 | }
93 | }
94 |
95 | #st-start-header-text-wrapper.greet {
96 |
97 | #st-start-header-title,
98 | #st-start-header-short-title {
99 | display: none;
100 | animation: wipeOut 1000ms both;
101 | }
102 |
103 | #st-start-header-greeting {
104 | display: block;
105 | animation: wipeIn 1000ms 50ms both;
106 | }
107 | }
108 |
109 | @keyframes wipeIn {
110 | from {
111 | clip-path: inset(0 100% 0 0);
112 | }
113 |
114 | to {
115 | clip-path: inset(0 0 0 0);
116 | }
117 | }
118 |
119 | @keyframes wipeOut {
120 | from {
121 | clip-path: inset(0 0 0 0);
122 | }
123 |
124 | to {
125 | clip-path: inset(0 0 0 100%);
126 | }
127 | }
128 |
129 | #st-start-header-buttons {
130 | margin-left: auto;
131 | display: flex;
132 | flex-wrap: nowrap;
133 | align-items: center;
134 | gap: 4px;
135 | }
136 |
137 | #st-start-header-buttons .st-button[data-icon]:before {
138 | font-weight: 600;
139 | }
140 |
141 | #st-start-today-offset-zero.emphasise {
142 | animation: 200ms teeter 3 linear, 1000ms pulsebig;
143 | }
144 |
145 | @keyframes teeter {
146 | 33% {
147 | transform: translate(-7%, 0) rotate(-9deg);
148 | }
149 |
150 | 66% {
151 | transform: translate(-5%, 0) rotate(4deg);
152 | }
153 |
154 | 66% {
155 | transform: translate(7%, 0) rotate(9deg);
156 | }
157 | }
158 |
159 | @keyframes pulsebig {
160 |
161 | 10%,
162 | 60% {
163 | scale: 1.5;
164 | translate: 0 -8px;
165 | }
166 | }
167 |
168 | #st-start-today-view {
169 | display: flex;
170 | flex-wrap: nowrap;
171 | margin-left: 8px;
172 | }
173 |
174 | .st-start-icon {
175 | display: inline-block;
176 | position: sticky !important;
177 | top: 50%;
178 | left: 50%;
179 | translate: -50% calc(-50% - 48px);
180 | width: 120px;
181 | height: max-content;
182 | font-size: 90px;
183 | text-align: center;
184 | opacity: .5;
185 | color: var(--st-foreground-secondary);
186 | }
187 |
188 | .st-start-disclaimer {
189 | display: inline-block;
190 | position: sticky;
191 | top: 50%;
192 | left: 50%;
193 | translate: -50% calc(-50% + 32px);
194 | width: max-content;
195 | height: max-content;
196 | font: 600 14px var(--st-font-family-secondary);
197 | text-align: center;
198 | opacity: .5;
199 | color: var(--st-foreground-secondary);
200 | }
201 |
202 | #st-start[data-widgets-collapsed=true] {
203 | grid-template:
204 | 'schedule widgets' 1fr
205 | / 1fr 0px;
206 |
207 | #st-start-fab {
208 | min-width: 128px;
209 | border-top-left-radius: var(--st-border-radius);
210 | box-shadow: 0 0 8px 0 rgba(var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-value), var(--st-shadow-alpha));
211 | }
212 | }
213 |
214 | .st-teacher-names-list {
215 | display: grid;
216 | grid-auto-flow: column;
217 | grid-template-rows: repeat(20, auto);
218 | column-gap: 32px;
219 |
220 | div {
221 | display: flex;
222 | justify-content: space-between;
223 | align-items: baseline;
224 | gap: 8px;
225 | width: 100%;
226 | font-size: 12px;
227 |
228 | .st-input {
229 | height: auto;
230 | padding: 4px 8px;
231 | margin-bottom: 4px;
232 | font-size: 12px;
233 | }
234 | }
235 | }
236 |
237 | #st-start-fab {
238 | position: absolute;
239 | right: 0;
240 | bottom: 0;
241 | min-width: 400px;
242 | z-index: 2;
243 |
244 | display: flex;
245 | justify-content: center;
246 | align-items: center;
247 | gap: 4px;
248 | padding: 8px;
249 |
250 | color: var(--st-foreground-insignificant);
251 | background-color: var(--st-background-secondary);
252 | border-top: var(--st-border);
253 | border-left: var(--st-border);
254 | transition: min-width 200ms;
255 |
256 | .st-button {
257 | color: currentColor;
258 |
259 | &:hover {
260 | color: var(--st-foreground-accent);
261 | filter: brightness(var(--st-hover-brightness));
262 | }
263 | }
264 |
265 | .st-widget-controls-button-group {
266 | color: currentColor;
267 | display: flex;
268 | align-items: center;
269 | gap: 4px;
270 | padding: 4px;
271 |
272 | border-radius: var(--st-border-radius);
273 | background-color: hsl(from var(--st-background-secondary) h s calc(l - 1));
274 |
275 | #st-start-edit-zoom-reset {
276 | padding-inline: 0;
277 | width: 42px;
278 | background-color: hsl(from var(--st-background-secondary) h s calc(l - 1));
279 | }
280 | }
281 |
282 | #st-sch-collapse-widgets {
283 | margin-left: auto;
284 | }
285 | }
--------------------------------------------------------------------------------
/popup/src/components/CustomCssEditor.vue:
--------------------------------------------------------------------------------
1 |
80 |
81 |
82 |
83 |
100 |
102 |
103 |
104 | De maximale lengte van je CSS-code is {{ bytesUsable }} bytes. Als je code langer is, wordt deze niet
105 | opgeslagen. Optimaliseer je code en gebruik eventueel een CSS-minifier.
106 |
107 | Enkele CSS-:root-variabelen die je kunt overschrijven zijn:
108 | {{ v }},
110 |
111 | Er zijn er uiteraard meer, maar pas niet meer aan dan nodig is. Je kunt meer aanpassen via het
112 | configuratiepaneel dan je denkt.
113 | Als je je kleurenschema dynamisch wilt maken, stel je je thema in op 'automatisch' en gebruik je de
114 | CSS-functie light-dark().
115 |
116 | Een bijzondere :root-regel die je kunt gebruiken is
117 | --st-menu-collapse: disallow;. Deze variabele voorkomt dat
119 | de zijbalk kan worden ingeklapt.
120 |
121 |
122 | Sluiten
123 |
124 |
125 |
126 | warning
127 | Niet opgeslagen
128 |
129 | De maximale lengte van je CSS-code is {{ bytesUsable }} bytes. Je code is langer dan dit en je
130 | wijzigingen
131 | zijn dus
132 | niet opgeslagen! Optimaliseer je code en gebruik eventueel een CSS-minifier.
133 |
134 |
135 | Sluiten
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | import settings from '../popup/dist/settings.js'
2 |
3 | let userId,
4 | userToken,
5 | userTokenDate
6 |
7 | const settingsToClear = [
8 | 'auto-theme', 'theme-fixed', 'theme-day', 'theme-night', 'openedPopup', 'updates', 'beta', 'magister-shortcuts', 'magister-shortcuts-today', 'magister-sw-grid', 'magister-sw-sort', 'magister-sw-period', 'magister-sw-display', 'magister-ag-large', 'magister-subjects', 'magister-appbar-hidePicture', 'appbar-hide-actions', 'magister-appbar-zermelo', 'magister-appbar-zermelo-url', 'magister-css-border-radius', 'magister-css-dark-invert', 'magister-css-experimental', 'magister-css-hue', 'magister-css-luminance', 'magister-css-saturation', 'magister-css-theme', 'magister-op-oldgrey', 'magister-periods', 'periods', 'magister-shortcut-keys', 'magister-shortcut-keys-master', 'magister-shortcut-keys-today', 'magister-subjects', 'magister-sw-thisWeek', 'magister-vd-overhaul', 'magister-vd-enabled', 'magister-vd-subjects', 'magister-vd-grade', 'magister-vd-agendaHeight', 'magister-vd-deblue', 'magister-vd-gradewidget', 'magisterLogin-password', 'magisterLogin-method', 'magister-gamification-beta', 'gamification-enabled', 'magister-cf-calculator', 'magister-cf-statistics', 'magister-cf-backup', 'magister-cf-failred', 'notes-enabled', 'notes', 'st-notes', 'vd-enabled', 'vd-schedule-days', 'vd-schedule-extra-day', 'vd-schedule-zoom', 'vd-subjects-display', 'start-stats', 'teacher-names', 'version', 'disable-css', 'hotkeys-today', 'start-widgets', 'dark-image', 'light-image', 'subjects', 'hidden-studyguides', 'color', 'start-schedule-days', 'v', 'special'
9 | ]
10 |
11 | startListenCredentials()
12 | setDefaults()
13 | console.info("Service worker running!")
14 | addEventListener('activate', () => {
15 | startListenCredentials()
16 | setDefaults()
17 | console.info("Service worker running!")
18 | })
19 |
20 | chrome.runtime.onStartup.addListener(() => {
21 | startListenCredentials()
22 | setDefaults()
23 | console.info("Browser started, service worker revived.")
24 | })
25 |
26 | const keepAlive = () => setInterval(chrome.runtime.getPlatformInfo, 20e3)
27 | chrome.runtime.onStartup.addListener(keepAlive)
28 | keepAlive()
29 |
30 | async function startListenCredentials() {
31 | // Allow any context to use chrome.storage.session
32 | chrome.storage.session.setAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' })
33 |
34 | // Initialise the three variables
35 | userId = (await chrome.storage.sync.get('user-id'))?.['user-id'] || null
36 | userToken = (await chrome.storage.session.get('token'))?.['token'] || null
37 | userTokenDate = (await chrome.storage.session.get('token-date'))?.['token-date'] || new Date(0)
38 |
39 | chrome.webRequest.onBeforeSendHeaders.addListener(async e => {
40 | // Return if the request was made by Study Tools itself
41 | if (Object.values(e.requestHeaders).find(header => header.name === 'X-Request-Source')?.value === 'study-tools') return
42 |
43 | console.info('Request caught!')
44 |
45 | let userIdWas = userId
46 | let userTokenWas = userToken
47 | let userTokenDateWas = userTokenDate
48 |
49 | let urlUserId = e.url.split('/personen/')[1]?.split('/')[0]
50 | if (urlUserId?.length > 2 && !urlUserId.includes('undefined')) {
51 | userId = urlUserId || userIdWas
52 | chrome.storage.sync.set({ 'user-id': userId })
53 | if (userIdWas !== userId) console.info(`User ID changed from ${userIdWas} to ${userId}.`)
54 | }
55 |
56 | let authObject = Object.values(e.requestHeaders).find(header => header.name === 'Authorization')
57 | if (authObject) {
58 | userToken = authObject.value
59 | userTokenDate = new Date()
60 | chrome.storage.session.set({ 'token': userToken })
61 | chrome.storage.session.set({ 'token-date': userTokenDate.getTime() })
62 | if (userTokenWas !== userToken && new Date(userTokenDateWas).getTime() == 0) console.info(`User token gathered. Length: ${userToken.length}.`)
63 | else if (userTokenWas !== userToken) console.info(`User token changed since ${userTokenDate - userTokenDateWas} ms ago.`)
64 | }
65 |
66 | }, { urls: ['*://*.magister.net/*'] }, ['requestHeaders'])
67 |
68 | console.info("Intercepting HTTP request information to extract token and userId...%c\n\nVrees niet, dit is alleen nodig zodat de extensie API-verzoeken kan maken naar Magister. Deze gegevens blijven op je apparaat. Dit wordt momenteel alleen gebruikt voor de volgende onderdelen:\n" + ["cijferexport", "widgets startpagina", "rooster startpagina", "puntensysteem"].join(', ') + "\n\nen in de toekomst eventueel ook voor:\n" + [].join(', '), "font-size: .8em")
69 | }
70 |
71 | async function setDefaults() {
72 | let syncedStorage = await chrome.storage.sync.get()
73 | let diff = {}
74 |
75 | // Check each setting to see if its value has been defined. If not, set it to the default value.
76 | settings.forEach(category => {
77 | category.settings.forEach(setting => {
78 | if (typeof syncedStorage[setting.id] === 'undefined') {
79 | if (setting.id === 'wallpaper' && syncedStorage['backdrop']?.length > 5) diff[setting.id] = 'custom,' + syncedStorage['backdrop']
80 | else diff[setting.id] = setting.default
81 | }
82 | })
83 | })
84 |
85 | if (Object.keys(diff).length > 0) {
86 | setTimeout(() => chrome.storage.sync.set(diff), 200)
87 | console.info("Set the following storage.sync keys to their default values:", diff)
88 | }
89 |
90 | if (settingsToClear.some(key => Object.keys(syncedStorage).includes(key))) {
91 | chrome.storage.sync.remove(settingsToClear)
92 | console.info("Redundant storage.sync keys removed to free up space.")
93 | }
94 | }
95 |
96 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
97 | switch (request.action) {
98 | case 'popstateDetected':
99 | console.info("Popstate detected, service worker revived.")
100 | return 0
101 |
102 | case 'waitForRequestCompleted':
103 | console.info(`Request completion notification requested by ${sender.url}.`)
104 | chrome.webRequest.onCompleted.addListener((details) => {
105 | sendResponse({ status: 'completed', details: details })
106 | console.info(`Request completion notification sent to ${sender.url}.`)
107 | }, { urls: ['*://*.magister.net/api/personen/*/aanmeldingen/*/cijfers/extracijferkolominfo/*'] })
108 | setTimeout(() => {
109 | sendResponse({ status: 'timeout' })
110 | console.warn(`Request completion notification requested by ${sender.url} has timed out.`)
111 | }, 5000)
112 | return true
113 |
114 | case 'uninstallSelf':
115 | chrome.management.uninstallSelf({ showConfirmDialog: false }, () => { window.location.reload() })
116 | break
117 |
118 | case 'openOptions':
119 | chrome.tabs.create({ url: `popup/dist/index.html?${request.data}` });
120 | break;
121 |
122 | default:
123 | return 0
124 | }
125 | })
126 |
127 | chrome.runtime.onMessageExternal.addListener(async (request, sender, sendResponse) => {
128 | switch (request.action) {
129 | case 'addPersonalTheme':
130 | const obj = request.obj
131 | const storedThemes = Object.values((await chrome.storage.local.get('storedThemes')).storedThemes)
132 | if (!storedThemes || storedThemes.length >= 9) return
133 |
134 | storedThemes.push(obj)
135 |
136 | //TODO: only if not exist
137 |
138 | await chrome.storage.local.set({ 'storedThemes': storedThemes })
139 | break
140 |
141 | default:
142 | return 0
143 | }
144 | })
145 |
--------------------------------------------------------------------------------
/src/scripts/grades/backup.js:
--------------------------------------------------------------------------------
1 | class GradeBackupPane extends Pane {
2 | id = 'cb';
3 | icon = '';
4 |
5 | #div1;
6 | #div2;
7 |
8 | constructor(parentElement) {
9 | super(parentElement);
10 |
11 | this.element.id = 'st-grade-backup-pane';
12 | this.#div1 = this.element.createChildElement('div', { class: 'st-div' });
13 | this.element.createChildElement('hr');
14 | this.#div2 = this.element.createChildElement('div', { class: 'st-div' });
15 | }
16 |
17 | show() {
18 | this.redraw();
19 | super.show();
20 | }
21 |
22 | redraw() {
23 | this.#div1.innerHTML = '';
24 | this.#div2.innerHTML = '';
25 |
26 | this.#div1.createChildElement('h3', { class: 'st-section-heading', innerText: i18n('cb.export') });
27 | this.#div2.createChildElement('h3', { class: 'st-section-heading', innerText: i18n('cb.import') });
28 |
29 | const year = currentGradeTable?.identifier?.year;
30 | const { backupYear, backupDate } = currentGradeTable?.identifier || {};
31 |
32 | if (currentGradeTable?.identifier?.year?.id) {
33 | this.#div1.createChildElement('p', { innerText: i18n('cb.exportDesc', { study: year.studie.code, period: year.lesperiode.code }) });
34 | const exportButton = this.#div1.createChildElement('button', { id: 'st-grade-backup-export', class: 'st-button hero', innerText: i18n('cb.backUpThisTable'), 'data-icon': '' });
35 | exportButton.addEventListener('click', async () => {
36 | if (!currentGradeTable?.identifier?.year?.id) return;
37 | await this.#exportGrades(currentGradeTable);
38 | });
39 | } else if (backupYear && backupDate) {
40 | this.#div1.createChildElement('p', { innerText: i18n('cb.importedDesc', { study: backupYear.studie.code, period: backupYear.lesperiode.code, date: new Date(backupDate).toLocaleString(locale) }) });
41 | }
42 |
43 | this.#div2.createChildElement('p', { innerText: i18n('cb.importDesc') });
44 | const importButton = this.#div2.createChildElement('button', { id: 'st-grade-backup-import', class: 'st-button hero', innerText: i18n('cb.browse'), 'data-icon': '' });
45 | const input = this.#div2.createChildElement('input', { type: 'file', accept: '.stgrades,application/json', style: 'display:none' });
46 | importButton.addEventListener('click', () => input.click());
47 | input.addEventListener('change', async (event) => {
48 | const file = event.target.files[0];
49 | if (!file) return;
50 |
51 | const reader = new FileReader();
52 | reader.onload = async (e) => {
53 | const json = JSON.parse(typeof e.target.result === 'string' ? e.target.result : new TextDecoder().decode(e.target.result));
54 | await this.#importBackup(json);
55 | };
56 | reader.readAsText(file);
57 | });
58 |
59 | this.progressBar.dataset.visible = 'false';
60 | }
61 |
62 | async #exportGrades(gradeTable) {
63 | return new Promise(async (resolve, reject) => {
64 | try {
65 | this.#div1.classList.add('st-disabled');
66 |
67 | const grades = await this.#gatherGrades(gradeTable);
68 | const year = gradeTable.identifier.year;
69 |
70 | this.progressBar.dataset.visible = 'true';
71 |
72 | let uri = `data:application/json;base64,${window.btoa(unescape(encodeURIComponent(JSON.stringify(
73 | {
74 | date: new Date(),
75 | year: { groep: { omschrijving: year.groep.omschrijving, code: year.groep.code }, studie: { code: year.studie.code }, lesperiode: { code: year.lesperiode.code } },
76 | grades
77 | }
78 | ))))}`,
79 | a = element('a', 'st-grade-backup-temp', document.body, {
80 | download: `Cijferback-up ${year.studie.code} (${year.lesperiode.code}) ${(new Date).toLocaleString()}.stgrades`,
81 | href: uri,
82 | type: 'application/json'
83 | });
84 | a.click();
85 | a.remove();
86 |
87 | setTimeout(() => {
88 | this.progressBar.dataset.visible = 'false';
89 | }, 500);
90 |
91 | new Dialog({ innerText: i18n('cb.done') }).show();
92 |
93 | setTimeout(() => {
94 | this.#div1.classList.remove('st-disabled');
95 | resolve();
96 | }, 10000)
97 |
98 | } catch (error) {
99 | reject(error);
100 | new Dialog({ innerText: i18n('cb.error') }).show();
101 | }
102 | });
103 | }
104 |
105 | async #gatherGrades(gradeTable) {
106 | return new Promise(async (resolve, reject) => {
107 | try {
108 | this.progressBar.firstElementChild.classList.remove('indeterminate');
109 | this.progressBar.dataset.visible = 'true';
110 |
111 | const grades = gradeTable.grades.filter(grade => grade.CijferKolom?.Id > 0);
112 |
113 | for (let i = 0; i < grades.length; i++) {
114 | this.progressBar.firstElementChild.style.width = `${(i / grades.length) * 100}%`;
115 |
116 | await new Promise((resolve) => {
117 | let delay = 5;
118 | if (i > 0 && i % 100 === 0) {
119 | delay = 8000;
120 | const dialog = new Dialog({ innerText: i18n('cb.avoidingRateLimit'), allowClose: false });
121 | dialog.show();
122 | setTimeout(() => dialog.close(), 8000);
123 | } else if (grades.length > 100) {
124 | delay = 20;
125 | }
126 | setTimeout(resolve, delay);
127 | });
128 |
129 | const gradeColumnInfo = await magisterApi.gradesColumnInfo(gradeTable.identifier.year, grades[i].CijferKolom.Id);
130 |
131 | grades[i] = {
132 | ...grades[i],
133 | CijferKolom: { ...grades[i].CijferKolom, ...gradeColumnInfo }
134 | }
135 | }
136 |
137 | this.progressBar.firstElementChild.removeAttribute('style');
138 | this.progressBar.firstElementChild.classList.add('indeterminate');
139 | this.progressBar.dataset.visible = 'false';
140 |
141 | resolve(grades);
142 | } catch (error) {
143 | reject(error);
144 | }
145 | });
146 | }
147 |
148 | async #importBackup(json) {
149 | const { date, year, grades } = json;
150 |
151 | if (!date || !year || !grades || !Array.isArray(grades)) {
152 | new Dialog({
153 | innerText: "Je back-up is ongeldig of in een verouderde indeling.\n\nJe kunt oude back-ups importeren via onderstaande website.",
154 | buttons: [{ innerText: 'Importeren via website', primary: true, href: 'https://qkeleq10.github.io/studytools/grades#grades' }]
155 | }).show();
156 | return;
157 | }
158 |
159 | // this.close();
160 |
161 | const aside = await awaitElement('aside'), container = await awaitElement('.container[id$=container]'), asideResizer = document.querySelector('#st-aside-resize');
162 | aside.setAttribute('style', 'display:none;width:0px;');
163 | container.style.paddingRight = '20px';
164 | if (asideResizer) asideResizer.setAttribute('style', `display:none`);
165 |
166 | if (gradeTables.find(t => t.date?.getTime() === new Date(date).getTime())) {
167 | notify('snackbar', "Je hebt deze back-up al geïmporteerd.");
168 | return;
169 | }
170 |
171 | const newGradeTable = new GradeTable(grades, { backupDate: new Date(date), backupYear: year });
172 | gradeTables.push(newGradeTable);
173 |
174 | const label = document.getElementById('st-grades-year-filter')
175 | .createChildElement('label', {
176 | class: 'st-checkbox-label icon',
177 | for: `st-year-filter-yearimport${new Date(date).getTime()}`,
178 | innerText: '',
179 | title: `Back-up van ${year.studie.code} (${year.lesperiode.code}) ${new Date(date).toLocaleString()}`
180 | });
181 | const input = label.createChildElement('input', { id: `st-year-filter-yearimport${new Date(date).getTime()}`, class: 'st-checkbox-input', name: 'st-year-filter', type: 'radio' });
182 |
183 | input.checked = true;
184 |
185 | input.addEventListener('change', () => {
186 | if (!input.checked) return;
187 | currentGradeTable?.destroy();
188 | currentGradeTable = newGradeTable;
189 | currentGradeTable.draw();
190 | });
191 |
192 | currentGradeTable?.destroy();
193 | currentGradeTable = newGradeTable;
194 | currentGradeTable.draw();
195 | }
196 | }
--------------------------------------------------------------------------------
/popup/src/components/ThemePreviewImage.vue:
--------------------------------------------------------------------------------
1 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
--------------------------------------------------------------------------------