("span.navlink"); // first time login
82 | if (
83 | document.querySelector(
84 | 'span.navLink > a.navLink[href="?X=Main"]',
85 | ) ||
86 | (navlink && navlink.innerText.includes("Než"))
87 | ) {
88 | page = new Logged(settings);
89 | } else {
90 | page = new Main(settings);
91 | }
92 | }
93 | }
94 | } else {
95 | page = new Main(settings);
96 | }
97 |
98 | await page.initialise();
99 | } catch (e) {
100 | if (e instanceof Error) {
101 | page = new ErrorPage(e);
102 | await page.initialise();
103 | } else {
104 | throw e;
105 | }
106 | }
107 | return page;
108 | };
109 |
110 | const replaceStyles = (theme: string) => {
111 | if (theme === "orig") return Promise.resolve();
112 |
113 | document.head.querySelectorAll('link[href$="/css.css"]').forEach((e) => {
114 | e.remove();
115 | });
116 |
117 | const link = document.createElement("link");
118 | link.setAttribute("rel", "stylesheet");
119 | link.setAttribute("type", "text/css");
120 |
121 | link.setAttribute(
122 | "href",
123 | chrome.runtime.getURL("themes/" + theme + ".css"),
124 | );
125 | document.getElementsByTagName("head")[0].appendChild(link);
126 | return new Promise((resolve) => {
127 | link.onload = resolve;
128 | });
129 | };
130 |
131 | const addLoadingOffStyle = () => {
132 | const link = document.createElement("link");
133 | link.setAttribute("rel", "stylesheet");
134 | link.setAttribute("type", "text/css");
135 |
136 | link.setAttribute("href", chrome.runtime.getURL("themes/loading/off.css"));
137 | document.getElementsByTagName("head")[0].appendChild(link);
138 | return new Promise((resolve) => {
139 | link.onload = resolve;
140 | });
141 | };
142 |
143 | chrome.runtime.sendMessage(
144 | { type: MessageType.GET_SETTINGS },
145 | async (settings: ExtensionSettings) => {
146 | console.log("PTT end with settings:", settings);
147 | await Promise.all([
148 | replaceStyles(settings.theme).then(() => {
149 | console.log("PTT styles loaded");
150 | }),
151 | !["orig", "orig-dark"].includes(settings.theme) && main(settings),
152 | ]);
153 | await addLoadingOffStyle();
154 | console.log("PTT loaded");
155 | document.dispatchEvent(pttLoadedEvent);
156 | },
157 | );
158 |
--------------------------------------------------------------------------------
/src/content/highlightjs/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006, Ivan Sagalaev
2 | All rights reserved.
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of highlight.js nor the names of its contributors
12 | may be used to endorse or promote products derived from this software
13 | without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/src/content/loader.ts:
--------------------------------------------------------------------------------
1 | import Loader from "../components/loader.svelte";
2 |
3 | const loaderElement = document.createElement("pttloader");
4 | document.documentElement.appendChild(loaderElement);
5 | new Loader({
6 | target: loaderElement,
7 | });
8 |
--------------------------------------------------------------------------------
/src/content/pages/Course/Course.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
46 | {#each courseGroups as group}
47 |
48 | {/each}
49 | {#if taskItem}
50 |
51 | {/if}
52 |
53 |
54 |
118 |
--------------------------------------------------------------------------------
/src/content/pages/Course/course-group.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | {#if group.taskGrp.length > 0}
35 |
84 | {/if}
85 |
86 |
222 |
--------------------------------------------------------------------------------
/src/content/pages/Course/task-modal.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 | {#if showModal}
43 |
44 | {#await task then data}
45 |
46 |
47 |
52 |
✖️
53 |
54 |
77 |
108 |
109 | {/await}
110 |
111 | {/if}
112 |
113 |
319 |
--------------------------------------------------------------------------------
/src/content/pages/Error/Error.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | An unexpected error occurred
12 | The following error has occurred:
13 | {error.toString()}
14 | {#if error.stack}
15 | {#each error.stack.split("\n") as line}
16 | {line}
17 | {/each}
18 | {/if}
19 |
20 | Try going back, if
21 | that doesn't help,
22 | file an issue on GitHub.
25 |
26 |
27 |
28 |
39 |
--------------------------------------------------------------------------------
/src/content/pages/Error/Error.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "../Page";
2 | import ErrorComponent from "./Error.svelte";
3 |
4 | export class ErrorPage implements Page {
5 | constructor(private error: Error) {}
6 |
7 | async initialise() {
8 | document.title = "Error | ProgTest";
9 |
10 | const center = document.querySelector("body > center");
11 | if (center) {
12 | center.style.display = "none";
13 | }
14 |
15 | const container = document.createElement("div");
16 | document.body.insertBefore(container, center);
17 | new ErrorComponent({
18 | target: container,
19 | props: { error: this.error },
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/content/pages/Exam.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionSettings } from "../../settings";
2 | import { Logged } from "./Logged";
3 |
4 | export class Exam extends Logged {
5 | constructor(settings: ExtensionSettings) {
6 | super(settings);
7 | }
8 |
9 | async initialise(): Promise {
10 | await super.initialise();
11 |
12 | // normalize html
13 | document
14 | .querySelectorAll(
15 | 'form[name="form1"] table tr:nth-child(n+4) td.rCell, form[name="form1"] table tr:nth-child(n+4) td.rbCell',
16 | )
17 | .forEach((e) => {
18 | const radio = e.querySelector('input[type="radio"]');
19 | if (!radio) {
20 | return;
21 | }
22 | const dot = e.previousElementSibling?.querySelector(".redBox");
23 | if (dot) {
24 | dot.parentElement?.removeChild(dot);
25 | radio.classList.add("radio-red");
26 | }
27 | e.classList.add("radio");
28 | const label = document.createElement("span");
29 | label.classList.add("radio-label");
30 | const remove: HTMLElement[] = [];
31 | e.childNodes.forEach((f) => {
32 | if (f.nodeName == "#text") {
33 | label.innerHTML += f.textContent;
34 | e.replaceChild(label, f);
35 | } else if (f instanceof HTMLElement) {
36 | label.innerHTML += f.outerHTML;
37 | remove.push(f);
38 | }
39 | });
40 | remove.forEach((g) => g.remove());
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/content/pages/Logged.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionSettings } from "../../settings";
2 | import { Page } from "./Page";
3 |
4 | interface LoggedTask {
5 | subject: string;
6 | link: string;
7 | name: string;
8 | seen: boolean;
9 | }
10 |
11 | export class Logged implements Page {
12 | topButton = `
13 |
16 | `;
17 |
18 | tButton: HTMLElement;
19 | header: HTMLElement;
20 | oldScroll: number | undefined;
21 |
22 | constructor(protected settings: ExtensionSettings) {
23 | this.tButton = document.getElementById("upTop") as HTMLElement;
24 | const header = document.querySelector("body > table");
25 | if (!header) {
26 | throw new Error("Header not found");
27 | }
28 | this.header = header;
29 | }
30 |
31 | async initialise() {
32 | this.header.className += " navbar";
33 | // add scroll to top button
34 | document.body.innerHTML += this.topButton;
35 | if (this.tButton) {
36 | this.tButton.addEventListener("click", () => {
37 | this.tButton.removeAttribute("style");
38 | document.body.scrollIntoView({
39 | block: "start",
40 | behavior: "smooth",
41 | });
42 | });
43 | }
44 |
45 | if (typeof this.header != "undefined" && this.header != null) {
46 | window.onscroll = this.scrollCheck.bind(this);
47 | window.addEventListener("load", this.scrollCheck.bind(this));
48 | }
49 |
50 | window.addEventListener("beforeunload", this.scrollHigh.bind(this));
51 |
52 | this.displayBell();
53 | this.highlightCode();
54 | this.notifications();
55 | }
56 |
57 | displayBell() {
58 | if (!window.localStorage || !this.settings.showNotifications) {
59 | return;
60 | }
61 | const bell = document.createElement("div");
62 | bell.classList.add("notify", "off");
63 | bell.addEventListener("click", Logged.notifyToggle.bind(this));
64 | const logout = document.querySelector('.navLink[href*="Logout"]');
65 | logout?.parentNode?.insertBefore(bell, logout);
66 |
67 | document.addEventListener("click", (e) => {
68 | if (e.target != bell) {
69 | document
70 | .getElementsByClassName("notifications")[0]
71 | .classList.add("notifications-hide");
72 | }
73 | });
74 |
75 | const notify = document.createElement("div");
76 | notify.classList.add("notifications", "notifications-hide");
77 | notify.innerHTML = "Žádná upozornění";
78 | document.body.insertBefore(notify, document.body.firstElementChild);
79 | }
80 |
81 | scrollCheck() {
82 | if (
83 | document.body.scrollTop > 40 ||
84 | document.documentElement.scrollTop > 40
85 | ) {
86 | this.scrollLow();
87 | } else {
88 | this.scrollHigh();
89 | }
90 |
91 | this.oldScroll = window.scrollY;
92 | }
93 |
94 | scrollHigh() {
95 | if (this.header && this.header.getAttribute("style")) {
96 | this.header.removeAttribute("style");
97 | }
98 | if (this.tButton && this.tButton.getAttribute("style")) {
99 | this.tButton.removeAttribute("style");
100 | }
101 | }
102 |
103 | scrollLow() {
104 | if (this.header && !this.header.getAttribute("style")) {
105 | this.header.style.padding = "0px 16px";
106 | }
107 | if (
108 | this.tButton &&
109 | !this.tButton.getAttribute("style") &&
110 | this.oldScroll &&
111 | (this.oldScroll <= window.scrollY || !this.oldScroll)
112 | ) {
113 | this.tButton.style.transform = "scale(1)";
114 | }
115 | }
116 |
117 | highlightCode() {
118 | document
119 | .querySelectorAll("pre, code, tt")
120 | .forEach((block) => {
121 | if (this.settings.syntaxHighlighting) {
122 | window.hljs.highlightBlock(block);
123 | } else {
124 | block.classList.add("hljs");
125 | }
126 | });
127 | }
128 |
129 | async notifications() {
130 | if (!window.localStorage || !this.settings.showNotifications) {
131 | return;
132 | }
133 |
134 | const tasks = await Logged.taskSpider();
135 |
136 | if (!localStorage.tasks) {
137 | localStorage.tasks = JSON.stringify(
138 | tasks?.map((e) => {
139 | e["seen"] = true;
140 | return e;
141 | }),
142 | );
143 | } else {
144 | const localTasks = JSON.parse(localStorage.tasks) as LoggedTask[];
145 | const notify =
146 | tasks?.filter((t) => {
147 | return !localTasks.some((e) => e.link === t.link);
148 | }) ?? [];
149 | this.displayNotifications(
150 | notify?.concat(localTasks.filter((e) => e.seen == false)) ?? [],
151 | );
152 | localStorage.tasks = JSON.stringify(localTasks.concat(notify));
153 | }
154 | }
155 |
156 | displayNotifications(elems: LoggedTask[]) {
157 | if (!elems.length) {
158 | return;
159 | }
160 | document
161 | .getElementsByClassName("notify")[0]
162 | .classList.replace("off", "on");
163 | const frame = document.getElementsByClassName("notifications")[0];
164 | frame.innerHTML = "";
165 | elems.forEach((e) => {
166 | const node = document.createElement("a");
167 | node.href = e.link;
168 | node.innerHTML = `${e.subject} Nová úloha:
${e.name}`;
169 | node.addEventListener("click", Logged.notifySeen.bind(this));
170 | frame.appendChild(node);
171 | });
172 | }
173 |
174 | static getLinksFromHTML(text: string, href: string) {
175 | text = text.replace(/
38 |
39 |
40 | {#each sortedSubjects as [semester, subjects]}
41 |
42 |
50 | {#if expanded[semester]}
51 |
52 | {#each subjects as subject}
53 |
54 | {/each}
55 |
56 | {/if}
57 | {/each}
58 |
59 | {#each settings as item}
60 |
61 | {/each}
62 |
63 |
64 |
65 |
158 |
--------------------------------------------------------------------------------
/src/content/pages/Main/Main.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionSettings } from "../../../settings";
2 | import { Logged } from "../Logged";
3 | import MainComponent from "./Main.svelte";
4 |
5 | export type MenuItem = {
6 | title: string;
7 | text: string;
8 | icon: string;
9 | link: string;
10 | subjectHomepage?: string;
11 | footer?: string;
12 | };
13 |
14 | export type Subjects = Record;
15 |
16 | type ParsedItem = Pick