diff --git a/sensei-grading-bookmarklet/README.md b/sensei-grading-bookmarklet/README.md
index 7e2e66f..3a18534 100644
--- a/sensei-grading-bookmarklet/README.md
+++ b/sensei-grading-bookmarklet/README.md
@@ -1,7 +1,14 @@
# `sensei-grading-bookmarklet`
+[![Bookmarklet Size][bookmarklet_size_badge]][bookmarklet_file]
+
> A bookmarklet to enhance the grading experience of code ninja assignments.
+## Purpose
+
+Grading code ninjas' submissions is slow and tedious because the current site has a poor user experience.
+My goal is to make an entirely keyboard-navigable enhancement in the form of a bookmarklet.
+
## Usage
1. Visit the [grading console][grading_console] while logged in as a code sensei.
@@ -10,6 +17,16 @@
1. Click enter to execute the bookmarklet code.
1. Grade.
+## Keybinds
+
+| Key | Action |
+| ---------------- | ------------------------------ |
+| i | Marks assignment as incomplete |
+| 1 | Rates assignment as 1-star |
+| 2 | Rates assignment as 2-star |
+| 3 | Rates assignment as 3-star |
+| enter | Submits assigment grade |
+
## Development
This [bookmarklet][bookmarklet_definition] is compiled using [NodeJS][node_installation].
@@ -18,11 +35,6 @@ Upon cloning this project, execute `npm i` to install of the necessary dependenc
To run in development mode, run `npm run dev`.
To generate the bookmarklet, run `npm start`.
-## Purpose
-
-Grading code ninjas' submissions is slow and tedious because the current site has a poor user experience.
-My goal is to make an entirely keyboard-navigable enhancement in the form of a bookmarklet.
-
## Security
For security reasons, modern browsers will make you type out the prefix (`javascript:`) manually.
@@ -32,3 +44,5 @@ This code, however, is non-malicious, so no worries in this case.
[grading_console]: https://gdp.code.ninja/Grading
[bookmarklet_definition]: https://en.wikipedia.org/wiki/Bookmarklet
[node_installation]: https://nodejs.org/en/download/
+[bookmarklet_size_badge]: https://img.badgesize.io/EthanThatOneKid/code-sensei/main/sensei-grading-bookmarklet/bookmarklet.txt
+[bookmarklet_file]: bookmarklet.txt
diff --git a/sensei-grading-bookmarklet/app.js b/sensei-grading-bookmarklet/app.js
index 4801d7b..408932a 100644
--- a/sensei-grading-bookmarklet/app.js
+++ b/sensei-grading-bookmarklet/app.js
@@ -1,3 +1,112 @@
// https://gist.github.com/EthanThatOneKid/95e8393c0afc1a35efda6f514d4ee6ea
-alert("Hello, World!");
+const selectors = {
+ incompleteBtn: "#IsIncompleteYes",
+ notesTextarea:
+ "#gradingNode > div > div:nth-child(1) > table > tbody > tr:nth-child(8) > td:nth-child(2) > textarea",
+ star1:
+ "#gradingNode > div > div:nth-child(1) > table > tbody > tr:nth-child(7) > td:nth-child(2) > span.pull-left.ml-3.mt-1.stars > i:nth-child(1)",
+ star2:
+ "#gradingNode > div > div:nth-child(1) > table > tbody > tr:nth-child(7) > td:nth-child(2) > span.pull-left.ml-3.mt-1.stars > i:nth-child(2)",
+ star3:
+ "#gradingNode > div > div:nth-child(1) > table > tbody > tr:nth-child(7) > td:nth-child(2) > span.pull-left.ml-3.mt-1.stars > i:nth-child(3)",
+ submit: "#grade-form > button",
+ previewContainer: "#gradingNode > div > div:nth-child(2)",
+ assignmentBtn:
+ "body > div.body-content > div:nth-child(2) > div:nth-child(3) > table > tbody > tr > td:nth-child(7) > a",
+ assignmentLink: "#gradingNode > div > div:nth-child(2) > div > a",
+};
+
+let currentAssignmentOnPage = 0;
+
+const defaultIdleTimeout = 5e3,
+ assignmentLoadDelay = 1e3;
+
+const setIdleTimeout = (...args) => {
+ let timeoutId = setTimeout(...args);
+ const alertUserAction = () => {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(...args);
+ };
+ return alertUserAction;
+};
+
+const actions = {
+ incomplete() {
+ // Sensei declares game incomplete.
+ document.querySelector(selectors.incompleteBtn).click();
+ const textarea = document.querySelector(selectors.notesTextarea);
+ textarea.focus();
+ const updateTextarea = setIdleTimeout(
+ () => document.querySelector(selectors.submit).focus(),
+ defaultIdleTimeout
+ );
+ textarea.addEventListener("keypress", updateTextarea);
+ },
+ rate(stars) {
+ // Sensei gives a rating out of 3 stars.
+ const selectorKey = `star${stars}`;
+ if (selectorKey in selectors) {
+ document.querySelector(selectors[selectorKey]).click();
+ this.submit();
+ }
+ },
+ submit() {
+ document.querySelector(selectors.submit).click();
+ currentAssignmentOnPage++;
+ this.nextAssignment();
+ },
+ nextAssignment() {
+ document
+ .querySelectorAll(selectors.assignmentBtn)
+ [currentAssignmentOnPage].click();
+ setTimeout(() => {
+ const preview = document.createElement("iframe");
+ preview.width = "1600px";
+ preview.height = "1400px";
+ preview.src = document.querySelector(selectors.assignmentLink).href;
+ document.querySelector(selectors.previewContainer).innerHTML = "";
+ document.querySelector(selectors.previewContainer).appendChild(preview);
+ document
+ .querySelector(selectors.previewContainer)
+ .parentElement.scrollIntoView();
+ }, assignmentLoadDelay);
+ },
+};
+
+document.addEventListener("keypress", (event) => {
+ try {
+ switch (event.key) {
+ case "i":
+ actions.incomplete();
+ break;
+
+ case "1":
+ actions.rate(1);
+ break;
+
+ case "2":
+ actions.rate(2);
+ break;
+
+ case "3":
+ actions.rate(3);
+ break;
+
+ case "enter":
+ actions.submit();
+ break;
+
+ default:
+ break;
+ }
+ } catch (error) {
+ console.log(`Sensei Error: ${error}`);
+ }
+});
+
+const main = () => {
+ actions.nextAssignment();
+};
+
+main();
diff --git a/sensei-grading-bookmarklet/bookmarklet.txt b/sensei-grading-bookmarklet/bookmarklet.txt
new file mode 100644
index 0000000..f2fbc64
--- /dev/null
+++ b/sensei-grading-bookmarklet/bookmarklet.txt
@@ -0,0 +1 @@
+javascript:(function()%7B%22use%20strict%22%3Bvar%20selectors%3D%7BincompleteBtn%3A%22%23IsIncompleteYes%22%2CnotesTextarea%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(1)%20%3E%20table%20%3E%20tbody%20%3E%20tr%3Anth-child(8)%20%3E%20td%3Anth-child(2)%20%3E%20textarea%22%2Cstar1%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(1)%20%3E%20table%20%3E%20tbody%20%3E%20tr%3Anth-child(7)%20%3E%20td%3Anth-child(2)%20%3E%20span.pull-left.ml-3.mt-1.stars%20%3E%20i%3Anth-child(1)%22%2Cstar2%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(1)%20%3E%20table%20%3E%20tbody%20%3E%20tr%3Anth-child(7)%20%3E%20td%3Anth-child(2)%20%3E%20span.pull-left.ml-3.mt-1.stars%20%3E%20i%3Anth-child(2)%22%2Cstar3%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(1)%20%3E%20table%20%3E%20tbody%20%3E%20tr%3Anth-child(7)%20%3E%20td%3Anth-child(2)%20%3E%20span.pull-left.ml-3.mt-1.stars%20%3E%20i%3Anth-child(3)%22%2Csubmit%3A%22%23grade-form%20%3E%20button%22%2CpreviewContainer%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(2)%22%2CassignmentBtn%3A%22body%20%3E%20div.body-content%20%3E%20div%3Anth-child(2)%20%3E%20div%3Anth-child(3)%20%3E%20table%20%3E%20tbody%20%3E%20tr%20%3E%20td%3Anth-child(7)%20%3E%20a%22%2CassignmentLink%3A%22%23gradingNode%20%3E%20div%20%3E%20div%3Anth-child(2)%20%3E%20div%20%3E%20a%22%7D%2CcurrentAssignmentOnPage%3D0%2CdefaultIdleTimeout%3D5e3%2CassignmentLoadDelay%3D1e3%2CsetIdleTimeout%3Dfunction()%7Bfor(var%20e%3Darguments.length%2Ct%3DArray(e)%2Cn%3D0%3Bn%3Ce%3Bn%2B%2B)t%5Bn%5D%3Darguments%5Bn%5D%3Bvar%20i%3DsetTimeout.apply(void%200%2Ct)%3Breturn%20function()%7BclearTimeout(i)%2Ci%3DsetTimeout.apply(void%200%2Ct)%7D%7D%2Cactions%3D%7Bincomplete%3Afunction()%7Bdocument.querySelector(selectors.incompleteBtn).click()%3Bvar%20e%3Ddocument.querySelector(selectors.notesTextarea)%3Be.focus()%3Bvar%20t%3DsetIdleTimeout(function()%7Breturn%20document.querySelector(selectors.submit).focus()%7D%2CdefaultIdleTimeout)%3Be.addEventListener(%22keypress%22%2Ct)%7D%2Crate%3Afunction(e)%7Be%3D%22star%22%2Be%3Be%20in%20selectors%26%26(document.querySelector(selectors%5Be%5D).click()%2Cthis.submit())%7D%2Csubmit%3Afunction()%7Bdocument.querySelector(selectors.submit).click()%2CcurrentAssignmentOnPage%2B%2B%2Cthis.nextAssignment()%7D%2CnextAssignment%3Afunction()%7Bdocument.querySelectorAll(selectors.assignmentBtn)%5BcurrentAssignmentOnPage%5D.click()%2CsetTimeout(function()%7Bvar%20e%3Ddocument.createElement(%22iframe%22)%3Be.width%3D%221600px%22%2Ce.height%3D%221400px%22%2Ce.src%3Ddocument.querySelector(selectors.assignmentLink).href%2Cdocument.querySelector(selectors.previewContainer).innerHTML%3D%22%22%2Cdocument.querySelector(selectors.previewContainer).appendChild(e)%2Cdocument.querySelector(selectors.previewContainer).parentElement.scrollIntoView()%7D%2CassignmentLoadDelay)%7D%7D%3Bdocument.addEventListener(%22keypress%22%2Cfunction(e)%7Btry%7Bswitch(e.key)%7Bcase%22i%22%3Aactions.incomplete()%3Bbreak%3Bcase%221%22%3Aactions.rate(1)%3Bbreak%3Bcase%222%22%3Aactions.rate(2)%3Bbreak%3Bcase%223%22%3Aactions.rate(3)%3Bbreak%3Bcase%22enter%22%3Aactions.submit()%7D%7Dcatch(e)%7Bconsole.log(%22Sensei%20Error%3A%20%22%2Be)%7D%7D)%3Bvar%20main%3Dfunction()%7Bactions.nextAssignment()%7D%3Bmain()%3B%7D)()
\ No newline at end of file
diff --git a/sensei-grading-bookmarklet/index.js b/sensei-grading-bookmarklet/index.js
index 4d08cdd..2b63145 100644
--- a/sensei-grading-bookmarklet/index.js
+++ b/sensei-grading-bookmarklet/index.js
@@ -1,6 +1,5 @@
-// https://github.com/mrcoles/bookmarklet
const fs = require("fs");
-const bookmarklet = require("bookmarklet");
+const bookmarklet = require("bookmarklet"); // https://github.com/mrcoles/bookmarklet
fs.readFile("./app.js", "utf8", (error, content) => {
if (error !== null) {
@@ -8,5 +7,7 @@ fs.readFile("./app.js", "utf8", (error, content) => {
}
const { code, options } = bookmarklet.parseFile(content);
const dist = bookmarklet.convert(code, options);
- console.log(`Here is your bookmarklet:\n${dist}`);
+ fs.writeFile("bookmarklet.txt", dist, undefined, () => {
+ console.log(`Here is your bookmarklet:\n${dist}`);
+ });
});