From ae09ad35402ec82f49fec49ddad23312cb6bd342 Mon Sep 17 00:00:00 2001 From: aritra-codes Date: Sat, 15 Jun 2024 18:07:51 +0100 Subject: [PATCH] Fixed auto-next not clicking 'Next task' button & Organised CSS selector constants and InnerText constants in contentScript.ts & Formatted files with prettier plugin --- .gitignore | 1 + package-lock.json | 195 +++++++++++++++++++------- package.json | 2 +- public/manifest.json | 2 +- src/App.tsx | 143 ++++++++++++------- src/background.ts | 74 +++++----- src/contentScript.ts | 323 ++++++++++++++++++++++++++----------------- tsconfig.json | 1 - vite.config.ts | 32 +++-- 9 files changed, 492 insertions(+), 281 deletions(-) diff --git a/.gitignore b/.gitignore index bd19500..f0dcd44 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ nodemon.json *.njsproj *.sln *.sw? +.prettierrc diff --git a/package-lock.json b/package-lock.json index b95342f..6f32d66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "sparx-bookwork-tracker", "version": "0.0.0", "dependencies": { - "html2canvas": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/chrome": "^0.0.268", "@types/node": "^20.12.12", "@types/react": "^18.2.66", @@ -241,18 +241,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1193,6 +1193,106 @@ "win32" ] }, + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", + "integrity": "sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==", + "dev": true, + "dependencies": { + "@babel/generator": "7.17.7", + "@babel/parser": "^7.20.5", + "@babel/traverse": "7.23.2", + "@babel/types": "7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1607,14 +1707,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1800,14 +1892,6 @@ "node": ">= 8" } }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2448,18 +2532,6 @@ "node": ">=4" } }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2574,6 +2646,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2670,6 +2748,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2987,6 +3071,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3221,6 +3321,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -3266,14 +3375,6 @@ "node": ">=4" } }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3410,14 +3511,6 @@ "punycode": "^2.1.0" } }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, "node_modules/vite": { "version": "5.2.12", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", diff --git a/package.json b/package.json index 7b1abc8..d375452 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,11 @@ "watch-build": "nodemon" }, "dependencies": { - "html2canvas": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/chrome": "^0.0.268", "@types/node": "^20.12.12", "@types/react": "^18.2.66", diff --git a/public/manifest.json b/public/manifest.json index 19939a7..f640263 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Sparx Bookwork Tracker", "version": "1.0.0", - "description": "Tracks your answers and shows you the answer to bookwork checks. Also, includes option to auto-click 'next' buttons. Less work, More progress! 💪", + "description": "Tracks your answers and shows you the answer to bookwork checks. Also includes more time-saving features! Less work, More progress! 💪", "icons": { "16": "images/icon16.png", "48": "images/icon48.png", diff --git a/src/App.tsx b/src/App.tsx index aaf21ac..d6b1d57 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,50 @@ import { useEffect } from "react"; + import "./App.css"; function App() { const search = () => { - const packageId = (document.querySelector("select#package-id-input") as HTMLSelectElement).value; - const bookworkCode = (document.querySelector("select#bookwork-code-input") as HTMLSelectElement).value; - const screenshotContainer = document.querySelector("img#screenshot") as HTMLImageElement; + const packageId = ( + document.querySelector("select#package-id-input") as HTMLSelectElement + ).value; + const bookworkCode = ( + document.querySelector("select#bookwork-code-input") as HTMLSelectElement + ).value; + const screenshotContainer = document.querySelector( + "img#screenshot", + ) as HTMLImageElement; chrome.storage.local.get("screenshotURLs", (res) => { - const screenshotURLs: {[key: string]: {[key: string]: string}} = res.screenshotURLs; + const screenshotURLs: { [key: string]: { [key: string]: string } } = + res.screenshotURLs; let screenshotURL; if (screenshotURLs instanceof Object && screenshotURLs[packageId]) { screenshotURL = screenshotURLs[packageId][bookworkCode]; } - screenshotContainer.src = screenshotURL || chrome.runtime.getURL("images/answer_not_found.png"); + screenshotContainer.src = + screenshotURL || chrome.runtime.getURL("images/answer_not_found.png"); }); - } + }; const clear = () => { - if (confirm("Are you sure you want to delete all your answer screenshots?")) { + if ( + confirm("Are you sure you want to delete all your answer screenshots?") + ) { chrome.storage.local.set({ screenshotURLs: {}, homeworkInfos: {} }); } - } + }; - const changeBookworkCodeSelect = (event: React.ChangeEvent) => { + const changeBookworkCodeSelect = ( + event: React.ChangeEvent, + ) => { chrome.storage.local.get("screenshotURLs", (res) => { const bookworkCodes = res.screenshotURLs[event.target.value]; - const bookworkCodeInput = document.querySelector("select#bookwork-code-input") as HTMLSelectElement; + const bookworkCodeInput = document.querySelector( + "select#bookwork-code-input", + ) as HTMLSelectElement; bookworkCodeInput.replaceChildren(document.createElement("option")); for (const bookworkCode in bookworkCodes) { @@ -39,56 +54,72 @@ function App() { bookworkCodeInput.appendChild(option); } }); - } + }; const updateAutoNext = () => { - const autoNext = (document.querySelector("input#auto-next") as HTMLInputElement).checked; + const autoNext = ( + document.querySelector("input#auto-next") as HTMLInputElement + ).checked; chrome.storage.local.set({ autoNext }); - } + }; const openURL = (event: React.MouseEvent) => { - chrome.runtime.sendMessage({ action: "openURL", url: (event.target as HTMLButtonElement).value }); - } + chrome.runtime.sendMessage({ + action: "openURL", + url: (event.target as HTMLButtonElement).value, + }); + }; // Everytime the popup is opened, the date & type select and auto next checkbox are updated. useEffect(() => { - chrome.storage.local.get(["screenshotURLs", "homeworkInfos", "autoNext"], (res) => { - const homeworkInfos: {[key: string]: string} = {} + chrome.storage.local.get( + ["screenshotURLs", "homeworkInfos", "autoNext"], + (res) => { + const homeworkInfos: { [key: string]: string } = {}; + + if (res.screenshotURLs instanceof Object) { + for (const packageId in res.screenshotURLs as { + [key: string]: { [key: string]: string }; + }) { + homeworkInfos[packageId] = "Unnamed"; + } + } - if (res.screenshotURLs instanceof Object) { - for (const packageId in (res.screenshotURLs as {[key: string]: {[key: string]: string}})) { - homeworkInfos[packageId] = "Unnamed"; + if (res.homeworkInfos instanceof Object) { + Object.entries( + res.homeworkInfos as { [key: string]: string }, + ).forEach(([packageId, homeworkInfo]) => { + if (packageId in homeworkInfos) { + homeworkInfos[packageId] = homeworkInfo; + } + }); } - } - - if (res.homeworkInfos instanceof Object) { - Object.entries((res.homeworkInfos as {[key: string]: string})).forEach(([packageId, homeworkInfo]) => { - if (packageId in homeworkInfos) { - homeworkInfos[packageId] = homeworkInfo; - } - }); - } - const packageIdInput = document.querySelector("select#package-id-input") as HTMLSelectElement; - packageIdInput.replaceChildren(document.createElement("option")); + const packageIdInput = document.querySelector( + "select#package-id-input", + ) as HTMLSelectElement; + packageIdInput.replaceChildren(document.createElement("option")); - Object.entries(homeworkInfos).forEach(([packageId, homeworkInfo]) => { - const option = document.createElement("option"); - option.value = packageId; - option.innerText = homeworkInfo; - packageIdInput.appendChild(option); - }); + Object.entries(homeworkInfos).forEach(([packageId, homeworkInfo]) => { + const option = document.createElement("option"); + option.value = packageId; + option.innerText = homeworkInfo; + packageIdInput.appendChild(option); + }); - let autoNext: boolean = res.autoNext; + let autoNext: boolean = res.autoNext; - if (!(typeof autoNext === "boolean")) { - autoNext = false; - } + if (!(typeof autoNext === "boolean")) { + autoNext = false; + } - const autoNextInput = document.querySelector("input#auto-next") as HTMLInputElement; - autoNextInput.checked = autoNext; - }); + const autoNextInput = document.querySelector( + "input#auto-next", + ) as HTMLInputElement; + autoNextInput.checked = autoNext; + }, + ); }); return ( @@ -113,18 +144,28 @@ function App() {
- - - - + + + +
- ) + ); } -export default App +export default App; diff --git a/src/background.ts b/src/background.ts index 3e3cf48..4c8103f 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,53 +1,55 @@ const checkInstance = (value: any, instance: any): any => { - if (!(value instanceof instance)) { - return new instance(); - } + if (!(value instanceof instance)) { + return new instance(); + } - return value; -} + return value; +}; const screenshotAnswer = (packageID: string, bookworkCode: string) => { - chrome.tabs.captureVisibleTab((screenshotURL) => { - chrome.storage.local.get("screenshotURLs", (res) => { - const screenshotURLs: {[key: string]: {[key: string]: string}} = checkInstance(res.screenshotURLs, Object); + chrome.tabs.captureVisibleTab((screenshotURL) => { + chrome.storage.local.get("screenshotURLs", (res) => { + const screenshotURLs: { [key: string]: { [key: string]: string } } = + checkInstance(res.screenshotURLs, Object); - screenshotURLs[packageID] ??= {}; - screenshotURLs[packageID][bookworkCode] = screenshotURL; + screenshotURLs[packageID] ??= {}; + screenshotURLs[packageID][bookworkCode] = screenshotURL; - chrome.storage.local.set({ screenshotURLs }); - }); + chrome.storage.local.set({ screenshotURLs }); }); -} + }); +}; chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { - if (request.action === "takeScreenshot") { - screenshotAnswer(request.packageId, request.bookworkCode); + if (request.action === "takeScreenshot") { + screenshotAnswer(request.packageId, request.bookworkCode); - sendResponse(); - } - else if (request.action === "setHomeworkInfos") { - chrome.storage.local.get("homeworkInfos", (res) => { - let homeworkInfos: {[key: string]: string} = checkInstance(res.homeworkInfos, Object); + sendResponse(); + } else if (request.action === "setHomeworkInfos") { + chrome.storage.local.get("homeworkInfos", (res) => { + let homeworkInfos: { [key: string]: string } = checkInstance( + res.homeworkInfos, + Object, + ); - homeworkInfos[request.packageId as string] = request.homeworkInfo as string; + homeworkInfos[request.packageId as string] = + request.homeworkInfo as string; - chrome.storage.local.set({ homeworkInfos }); - }); - } - else if (request.action === "openURL") { - chrome.tabs.create({ url: (request.url as string) }) - } + chrome.storage.local.set({ homeworkInfos }); + }); + } else if (request.action === "openURL") { + chrome.tabs.create({ url: request.url as string }); + } }); chrome.tabs.onUpdated.addListener((tabId, tab) => { - if (tab.url) { - const linkDirectories = tab.url.split("/"); - - if (linkDirectories.includes("wac")) { - chrome.tabs.sendMessage(tabId, { action: "showBookworkAnswer" }); - } - else if (linkDirectories.includes("summary")) { - chrome.tabs.sendMessage(tabId, { action: "clickNextTask" }); - } + if (tab.url) { + const linkDirectories = tab.url.split("/"); + + if (linkDirectories.includes("wac")) { + chrome.tabs.sendMessage(tabId, { action: "showBookworkAnswer" }); + } else if (linkDirectories.includes("summary")) { + chrome.tabs.sendMessage(tabId, { action: "clickNextTask" }); } + } }); diff --git a/src/contentScript.ts b/src/contentScript.ts index d340b61..639e645 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -1,146 +1,217 @@ -const waitForElement = (selector: string): Promise => { - return new Promise(resolve => { - if (document.querySelector(selector)) { - return resolve(document.querySelector(selector)); - } +// CSS selectors +const accuracyTextSelector = "div._AccuracyText_1gvq7_41"; +const actionAnchorSelector = "a._ButtonBase_nt2r3_1"; +const actionButtonSelector = "button._ButtonBase_nt2r3_1"; +const bookworkCodeContainerSelector = "div._Chip_bu06u_1"; +const resultPopoverSelector = "div#RESULT_POPOVER"; +const startTaskButtonSelector = "a._Task_1p2y5_1._TaskClickable_1p2y5_22"; +const summaryContainerSelector = "div._SummaryContainer_o2hat_1"; +const taskContainerSelector = "div._AccordionItem_9fvag_7"; +const taskInfoSelector = "div._PackageLeft_s1pvn_28 > span"; +const WACContainerSelector = "div._WACContainer_1cxo7_1"; + +// InnerTexts +const continueButtonText = "Continue"; +const correctPopoverText = "Correct!"; +const nextTaskButtonText = "Next task"; +const submitButtonText = "Submit"; +const summaryButtonText = "Summary"; - const observer = new MutationObserver(() => { - if (document.querySelector(selector)) { - observer.disconnect(); - resolve(document.querySelector(selector)); - } - }); +const waitForElement = (selector: string): Promise => { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)); + } - observer.observe(document.body, { - childList: true, - subtree: true - }); + const observer = new MutationObserver(() => { + if (document.querySelector(selector)) { + observer.disconnect(); + resolve(document.querySelector(selector)); + } }); -} - -const setScreenshotSrc = (img: HTMLImageElement, packageId: string, bookworkCode: string) => { - chrome.storage.local.get("screenshotURLs", (res) => { - const screenshotURLs: {[key: string]: {[key: string]: string}} = res.screenshotURLs; - let screenshotURL; - if (screenshotURLs instanceof Object && screenshotURLs[packageId]) { - screenshotURL = screenshotURLs[packageId][bookworkCode]; - } - - img.src = screenshotURL || chrome.runtime.getURL("images/answer_not_found.png"); + observer.observe(document.body, { + childList: true, + subtree: true, }); -} + }); +}; + +const setScreenshotSrc = ( + img: HTMLImageElement, + packageId: string, + bookworkCode: string, +) => { + chrome.storage.local.get("screenshotURLs", (res) => { + const screenshotURLs: { [key: string]: { [key: string]: string } } = + res.screenshotURLs; + let screenshotURL; + + if (screenshotURLs instanceof Object && screenshotURLs[packageId]) { + screenshotURL = screenshotURLs[packageId][bookworkCode]; + } + + img.src = + screenshotURL || chrome.runtime.getURL("images/answer_not_found.png"); + }); +}; const getPackageId = (url: string): string => { - const URLDirectories = url.split("/"); - return URLDirectories[URLDirectories.indexOf("package") + 1]; -} + const URLDirectories = url.split("/"); + return URLDirectories[URLDirectories.indexOf("package") + 1]; +}; chrome.runtime.onMessage.addListener((request) => { - // When the URL shows a bookwork check, background.js sends a message. - // Upon receiving it, the answer is displayed. - if (request.action === "showBookworkAnswer") { - waitForElement("div._WACContainer_1cxo7_1").then((bookworkContainer) => { - const bookworkCode = (document.querySelector("div._Chip_bu06u_1") as HTMLDivElement).innerText.split(" ")[1]; - - const heading = document.createElement("h2"); - heading.innerText = "Answer: "; - - const screenshot = document.createElement("img"); - screenshot.classList.add("screenshot"); - - setScreenshotSrc(screenshot, getPackageId(location.href), bookworkCode); - - const container = document.createElement("div"); - container.style.textAlign = "center"; - container.appendChild(heading); - container.appendChild(screenshot); - - (bookworkContainer as HTMLDivElement).appendChild(container); - - // If auto next is on, when the submit button is manually clicked, the continue button is clicked. - chrome.storage.local.get("autoNext", (res) => { - if (res.autoNext === true) { - document.querySelectorAll("button._ButtonBase_nt2r3_1").forEach((submitButton) => { - if ((submitButton as HTMLButtonElement).innerText === "Submit") { - (submitButton as HTMLButtonElement).addEventListener("click", () => { - waitForElement("div._AccuracyText_1gvq7_41").then(() => { - document.querySelectorAll("button._ButtonBase_nt2r3_1").forEach((continueButton) => { - if ((continueButton as HTMLButtonElement).innerText === "Continue") { - (continueButton as HTMLButtonElement).click(); - } - }); - }); - }); - } + // When the URL shows a bookwork check, background.js sends a message. + // Upon receiving it, the answer is displayed. + if (request.action === "showBookworkAnswer") { + waitForElement(WACContainerSelector).then((bookworkContainer) => { + const bookworkCode = ( + document.querySelector(bookworkCodeContainerSelector) as HTMLDivElement + ).innerText.split(" ")[1]; + + const heading = document.createElement("h2"); + heading.innerText = "Answer: "; + + const screenshot = document.createElement("img"); + screenshot.classList.add("screenshot"); + + setScreenshotSrc(screenshot, getPackageId(location.href), bookworkCode); + + const container = document.createElement("div"); + container.style.textAlign = "center"; + container.appendChild(heading); + container.appendChild(screenshot); + + (bookworkContainer as HTMLDivElement).appendChild(container); + + // If auto next is on, when the submit button is manually clicked, the continue button is clicked. + chrome.storage.local.get("autoNext", (res) => { + if (res.autoNext === true) { + document + .querySelectorAll(actionButtonSelector) + .forEach((submitButton) => { + if ( + (submitButton as HTMLButtonElement).innerText === + submitButtonText + ) { + (submitButton as HTMLButtonElement).addEventListener( + "click", + () => { + waitForElement(accuracyTextSelector).then(() => { + document + .querySelectorAll(actionButtonSelector) + .forEach((continueButton) => { + if ( + (continueButton as HTMLButtonElement).innerText === + continueButtonText + ) { + (continueButton as HTMLButtonElement).click(); + } + }); }); - } + }, + ); + } }); - }); - } - // When the URL shows a summary, background.js sends a message. - // Upon receiving it, if auto next is on, the next task button is clicked. - else if (request.action === "clickNextTask") { - chrome.storage.local.get("autoNext", (res) => { - if (res.autoNext === true) { - waitForElement("div._SummaryContainer_rlwap_1").then(() => { - document.querySelectorAll("a._ButtonBase_nt2r3_1").forEach((element) => { - const anchor = element as HTMLAnchorElement; - - if (anchor.innerText === "Next task" || anchor.innerText === "Continue") { - anchor.click(); - } - }); - }); + } + }); + }); + } + // When the URL shows a summary, background.js sends a message. + // Upon receiving it, if auto next is on, the next task button is clicked. + else if (request.action === "clickNextTask") { + chrome.storage.local.get("autoNext", (res) => { + if (res.autoNext === true) { + waitForElement(summaryContainerSelector).then(() => { + document.querySelectorAll(actionAnchorSelector).forEach((element) => { + const anchor = element as HTMLAnchorElement; + + if ( + anchor.innerText === nextTaskButtonText || + anchor.innerText === continueButtonText + ) { + anchor.click(); } + }); }); - } + } + }); + } }); -const observer = new MutationObserver(mutations => { - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.nodeType === 1) { - const element = node as HTMLElement; - - // Whenever the correct answer popover is displayed, a message is sent to background.js to take a screenshot. - if (element.matches("div#RESULT_POPOVER") && (element as HTMLDivElement).innerText.includes("Correct!")) { - const bookworkCode = (document.querySelector("div._Chip_bu06u_1") as HTMLDivElement).innerText.split(": ")[1]; - - setTimeout(() => { - chrome.runtime.sendMessage({ action: "takeScreenshot", packageId: getPackageId(location.href), bookworkCode }, () => { - // After the screenshot has been taken, if auto next is on, the continue button is clicked. - chrome.storage.local.get("autoNext", (res) => { - if (res.autoNext === true) { - const continueButton = document.querySelector("a._ButtonBase_nt2r3_1") as HTMLAnchorElement; - - if (continueButton && (continueButton.innerText === "Continue" || continueButton.innerText === "Summary")) { - continueButton.click(); - } - } - }); - }); - }, 250); - } - // Everytime a new task is started, the homework info (date and type) and package ID are saved. - else if (element.matches("a._Task_1p2y5_1._TaskClickable_1p2y5_22")) { - const anchor = element as HTMLAnchorElement; - - anchor.addEventListener("click", () => { - const homeworkInfo = ((anchor.closest("div._AccordionItem_9fvag_7") as HTMLDivElement).querySelector("div._PackageLeft_s1pvn_28 > span") as HTMLSpanElement).innerText; - const packageId = getPackageId(anchor.href); - - chrome.runtime.sendMessage({ action: "setHomeworkInfos", packageId, homeworkInfo }); - }); - } - } - }); +const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + const element = node as HTMLElement; + + // Whenever the correct answer popover is displayed, a message is sent to background.js to take a screenshot. + if ( + element.matches(resultPopoverSelector) && + (element as HTMLDivElement).innerText.includes(correctPopoverText) + ) { + const bookworkCode = ( + document.querySelector( + bookworkCodeContainerSelector, + ) as HTMLDivElement + ).innerText.split(": ")[1]; + + setTimeout(() => { + chrome.runtime.sendMessage( + { + action: "takeScreenshot", + packageId: getPackageId(location.href), + bookworkCode, + }, + () => { + // After the screenshot has been taken, if auto next is on, the continue button is clicked. + chrome.storage.local.get("autoNext", (res) => { + if (res.autoNext === true) { + const continueButton = document.querySelector( + actionAnchorSelector, + ) as HTMLAnchorElement; + + if ( + continueButton && + (continueButton.innerText === continueButtonText || + continueButton.innerText === summaryButtonText) + ) { + continueButton.click(); + } + } + }); + }, + ); + }, 250); + } + // Everytime a new task is started, the homework info (date and type) and package ID are saved. + else if (element.matches(startTaskButtonSelector)) { + const anchor = element as HTMLAnchorElement; + + anchor.addEventListener("click", () => { + const homeworkInfo = ( + ( + anchor.closest(taskContainerSelector) as HTMLDivElement + ).querySelector(taskInfoSelector) as HTMLSpanElement + ).innerText; + const packageId = getPackageId(anchor.href); + + chrome.runtime.sendMessage({ + action: "setHomeworkInfos", + packageId, + homeworkInfo, + }); + }); + } + } }); + }); }); const config = { - childList: true, - subtree: true -}; + childList: true, + subtree: true, +}; -observer.observe(document.body, config); \ No newline at end of file +observer.observe(document.body, config); diff --git a/tsconfig.json b/tsconfig.json index d31a158..e14e858 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "src/contentScript.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/vite.config.ts b/vite.config.ts index 7c4cab8..9984185 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react from "@vitejs/plugin-react"; + +import { defineConfig } from "vite"; + +const extraTsFilenames = ["contentScript", "background"]; // https://vitejs.dev/config/ export default defineConfig({ @@ -7,20 +10,21 @@ export default defineConfig({ build: { rollupOptions: { input: { - main: 'popup.html', - contentScript: 'src/contentScript.ts', - background: 'src/background.ts' + main: "popup.html", + ...Object.fromEntries( + extraTsFilenames.map((filename) => [filename, `src/${filename}.ts`]), + ), }, output: { entryFileNames: (chunkInfo) => { - if (['contentScript', 'background'].includes(chunkInfo.name)) { - return '[name].js'; + if (["contentScript", "background"].includes(chunkInfo.name)) { + return "[name].js"; } - return 'assets/[name].js'; + return "assets/[name].js"; }, - chunkFileNames: 'assets/[name].js', - assetFileNames: 'assets/[name].[ext]' - } - } - } -}) + chunkFileNames: "assets/[name].js", + assetFileNames: "assets/[name].[ext]", + }, + }, + }, +});