diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..11586a5
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,22 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
+{
+ "name": "Node.js",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm",
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ "postCreateCommand": "npm install"
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8214165
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,27 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [4.0.0] - 2023-10-16
+
+### Added
+
+- Settings caching to display the average faster.
+- Dev container for contributors.
+
+### Fixed
+- NaN average if the only assessments were non-numerical.
+
+### Changed
+
+- Settings UI.
+- Many functions were rewritten from scratch.
+- Readme file.
+
+### Removed
+
+- Console logs.
+- Unused css styling.
\ No newline at end of file
diff --git a/README.md b/README.md
index 514ac5a..08c5bd3 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,24 @@
-[![Chrome Web Store](https://img.shields.io/chrome-web-store/v/oggojknablgjgacijcjbioanonlkmfeg)](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
-[![Chrome Web Store](https://img.shields.io/chrome-web-store/users/oggojknablgjgacijcjbioanonlkmfeg)](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
-# ![Logo](assets/icon/32.png) Librus Średnia
-Rozszerzenie do najpopularniejszych przeglądarek dodające możliwość wyświetlenia średniej z ocen, która nie zaśmieca strony.
-
-Wtyczka posiada ustawienia z możliwością konfigurowania każdej funkcji.
+
Librus średnia
+
+
+
+
+
+
+
+
+
+
+
+
+
+> Rozszerzenie do najpopularniejszych przeglądarek dodające możliwość wyświetlenia średniej z ocen, która nie zaśmieca strony.
| Przed | Po |
| ------------------------- | --------------------- |
| ![przed](assets/img/before.png) | ![po](assets/img/after.png) |
-# Funkcje w wersji 3.0.X
+## ✨ Funkcje
* Wyświetlanie średniej dla każdego przedmiotu w tabeli z ocenami.
* Wyświetlanie średniej wszystkich ocen na dole tabeli.
* Wyświetlanie średniej ocen śródrocznych i rocznych.
@@ -20,32 +29,34 @@ Wtyczka posiada ustawienia z możliwością konfigurowania każdej funkcji.
* Synchronizowanie ustawień między przeglądarkami, gdy użytkownik jest do niej zalogowany (jeśli przeglądarka wspiera synchronizację).
* Automatyczne przełączanie planu lekcji na najbliższy tydzień w weekend po otwarciu go.
-# Sposób liczenia średniej
+**Wtyczka posiada ustawienia z możliwością konfigurowania każdej funkcji.**
+
+## 🧮 Sposób liczenia średniej
Do średniej domyślnie liczą się tylko oceny oznaczone `Licz do średniej: tak`. Pod uwagę jest brana waga. Domyślnie `+` dodaje 0.5 do wartości oceny, a `-` odejmuje 0.25. Oceny niezawierające *normalnej* liczby nie są brane pod uwagę.
Parametry liczenia można zmienić w ustawieniach wtyczki (wciskając ikonkę wtyczki).
-# Zgłaszanie błędów
-Wejdź w zakładkę `Issues` i utwórz nowy wątek.
-
-# Instalacja
+## 🚀 Instalacja
-## [Chrome](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
+### [Chrome](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
-## [Opera (Przez chrome web store)](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
+### [Opera (Przez chrome web store)](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
-## Firefox
+### Firefox
Wtyczka w firefoxie **obecnie nie działa**.
-## Przeglądarki oparte na chrominium
+### Przeglądarki oparte na chrominium
Jeśli się da to przez [chrome web store](https://chrome.google.com/webstore/detail/librus-średnia/oggojknablgjgacijcjbioanonlkmfeg)
### Instalacja ręczna
1. Sklonuj repozytorium.
-2. Uruchom w konsoli polecenie `npm install` (wymagane Node.js v18 i npm (testowane na 9.8)).
+2. Uruchom w konsoli polecenie `npm install` (wymagane Node.js v18 i npm (testowane na 9.8)). Można użyć `dev container`.
3. Uruchom w konsoli polecenie `npm run dev`.
4. W Twojej przeglądarce wejdź w Rozszerzenia -> Zarządzaj rozszerzeniami.
5. Włącz tryb dewelopera.
6. Wciśnij "Załaduj rozpakowane" i wybierz folder `extensions/combined/dist/chrome`, gdzie znajduje się rozpakowana wtyczka.
-# Mogę wesprzeć projekt?
+## ❗Zgłaszanie błędów
+Wejdź w zakładkę `Issues` i utwórz nowy wątek.
+
+## 🤝 Mogę wesprzeć projekt?
Tak. Możesz pomóc go rozwijać lub możesz wesprzeć projekt finansowo. Zerknij na link po prawej stronie pod napisem `Sponsor this project`.
\ No newline at end of file
diff --git a/extensions/combined/background.js b/extensions/combined/background.js
index 7121f9b..2698851 100644
--- a/extensions/combined/background.js
+++ b/extensions/combined/background.js
@@ -1,21 +1,2 @@
-const filter = {
- url: [
- {
- urlMatches: "https://synergia.librus.pl/przegladaj_plan_lekcji"
- },
- {
- urlMatches: "http://synergia.librus.pl/przegladaj_plan_lekcji"
- },
- ],
-};
-
-chrome.webNavigation.onCreatedNavigationTarget.addListener(async (details) => {
- let nextWeekAtWeekend;
- await chrome.storage.sync.get(["nextWeekAtWeekend"]).then((result) => {nextWeekAtWeekend = result.nextWeekAtWeekend ?? true});
- if (!nextWeekAtWeekend) return;
- chrome.scripting.executeScript({
- target: { tabId: details.tabId },
- files: ["przegladaj_plan_lekcji.inject-script.js"],
- world: "MAIN"
- });
-}, filter);
\ No newline at end of file
+import "./src/background/messageHandler"
+import "./src/background/przegladaj_plan_lekcji_injector"
\ No newline at end of file
diff --git a/extensions/combined/manifest-chrome.json b/extensions/combined/manifest-chrome.json
index d4f7d16..ac7b8d6 100644
--- a/extensions/combined/manifest-chrome.json
+++ b/extensions/combined/manifest-chrome.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Librus Średnia",
- "version": "3.0.0",
+ "version": "4.0.0",
"description": "Wtyczka wyświetlająca średnią ocen dla każdego przedmiotu, mimo że ta funkcja została wyłączona przez administratora szkoły.",
"author": "Krzysztof Kwiatkowski",
"icons": {
diff --git a/extensions/combined/src/background/messageHandler.js b/extensions/combined/src/background/messageHandler.js
new file mode 100644
index 0000000..1c841c4
--- /dev/null
+++ b/extensions/combined/src/background/messageHandler.js
@@ -0,0 +1,14 @@
+"use strict";
+import storage from "./Storage";
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.message == "saveSettings") {
+ storage.save(request.body);
+ }
+ else if (request.message == "getSetting") {
+ (async () => {
+ sendResponse(await storage.get(request.key));
+ })();
+ return true;
+ }
+})
\ No newline at end of file
diff --git a/extensions/combined/src/background/przegladaj_plan_lekcji_injector.js b/extensions/combined/src/background/przegladaj_plan_lekcji_injector.js
new file mode 100644
index 0000000..647817c
--- /dev/null
+++ b/extensions/combined/src/background/przegladaj_plan_lekcji_injector.js
@@ -0,0 +1,20 @@
+"use strict";
+import storage from "./Storage";
+
+const filter = {
+ url: [
+ {
+ hostSuffix: "synergia.librus.pl",
+ pathPrefix: "/przegladaj_plan_lekcji"
+ }
+ ],
+};
+
+chrome.webNavigation.onCreatedNavigationTarget.addListener(async (details) => {
+ if (!await storage.get("nextWeekAtWeekend")) return;
+ chrome.scripting.executeScript({
+ target: { tabId: details.tabId },
+ files: ["przegladaj_plan_lekcji.inject-script.js"],
+ world: "MAIN"
+ });
+}, filter);
\ No newline at end of file
diff --git a/extensions/combined/src/background/storage.js b/extensions/combined/src/background/storage.js
new file mode 100644
index 0000000..621efa6
--- /dev/null
+++ b/extensions/combined/src/background/storage.js
@@ -0,0 +1,56 @@
+"use strict";
+// https://stackoverflow.com/questions/1479319/simplest-cleanest-way-to-implement-a-singleton-in-javascript
+let storage = (function () {
+ // Private methods
+ const corrrectSettingsObject = {
+ plus: 0.5,
+ minus: 0.25,
+ tylkoLiczDoSredniej: true,
+ schowajZachowanie: true,
+ ignoreCorrectedGrades: true,
+ nextWeekAtWeekend: true
+ };
+ const correctSettingsObjectLength = Object.keys(corrrectSettingsObject).length;
+
+ let cache = {};
+
+ function checkSettingsObject(settingsObject) {
+ let settingsObjectLength = Object.keys(settingsObject).length;
+ if (settingsObjectLength != correctSettingsObjectLength) {
+ throw new Error(`Settings object length is ${settingsObjectLength}. Expected ${correctSettingsObjectLength}`)
+ }
+
+ Object.keys(corrrectSettingsObject).forEach((property) => {
+ if (!settingsObject.hasOwnProperty(property)) {
+ throw new Error(`Settings object has no ${property} property.`)
+ };
+ })
+
+ return true;
+ }
+
+ // Public methods
+ return {
+ save: function (settingsObject) {
+ if (checkSettingsObject(settingsObject)) {
+ chrome.storage.sync.set(settingsObject);
+ cache = settingsObject;
+ } else {
+ throw new Error("Incorrect settings object");
+ }
+ },
+
+ get: async function (key) {
+ if (cache[key]) {
+ return cache[key];
+ } else {
+ await chrome.storage.sync.get([key]).then((result) => {cache[key] = result[key] ?? corrrectSettingsObject[key] });
+ return cache[key];
+ }
+ }
+ }
+})
+
+Object.freeze(storage);
+
+module.exports = storage();
\ No newline at end of file
diff --git a/extensions/combined/src/popup/popup.css b/extensions/combined/src/popup/popup.css
new file mode 100644
index 0000000..abbd093
--- /dev/null
+++ b/extensions/combined/src/popup/popup.css
@@ -0,0 +1,67 @@
+body {
+ background-color: #282922;
+ color: white;
+ width: 300px;
+}
+
+table {
+ border-collapse: collapse;
+ font-size: 12px;
+}
+
+tr {
+ border-bottom: 1px #a4c800 solid;
+}
+
+tr:last-child {
+ border-bottom: none !important;
+}
+
+tr:hover {
+ background-color: #303030;
+}
+
+tr:last-child:hover {
+ background-color: #282922 !important;
+}
+
+input[type=number], input[type=submit] {
+ background-color: #303030;
+ color: white;
+ border-radius: 5px;
+ border: 1px #a4c800 solid;
+ width: 50px;
+}
+
+input[type=submit]:hover {
+ background-color: #a4c800;
+}
+
+input[type=submit] {
+ margin-top: 5px;
+ padding: 0px;
+ height: 25px;
+ cursor: pointer;
+}
+
+input[type=checkbox] {
+ height: 25px;
+ width: 25px;
+ cursor: pointer;
+}
+
+/* unvisited and visited link */
+a:link, a:visited {
+ color: #a4c800;
+}
+
+a:hover, a:active {
+ color: #e2fb72;
+}
+
+footer {
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ margin-top: 10px;
+}
\ No newline at end of file
diff --git a/extensions/combined/src/popup/popup.html b/extensions/combined/src/popup/popup.html
index ad5081d..efe0a35 100644
--- a/extensions/combined/src/popup/popup.html
+++ b/extensions/combined/src/popup/popup.html
@@ -3,93 +3,50 @@
-
+
Ustawienia
-
- Po zapisaniu ustawień odśwież stronę
-
-
- GitHub
-
-
- tipply
-
-
- Docent Company
+
+
+