Wanikani Review Summary

Show a popup with statistics about the review session when returning to the dashboard

当前为 2023-10-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Wanikani Review Summary
  3. // @namespace https://tampermonkey.net/
  4. // @version 0.5.4
  5. // @license MIT
  6. // @description Show a popup with statistics about the review session when returning to the dashboard
  7. // @author leohumnew
  8. // @match https://www.wanikani.com/subjects/review
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. // Variables to store the statistics
  15. let questionsAnswered = 0;
  16. let itemsCorrect = 0;
  17. let itemsIncorrect = 0;
  18. let meaningCorrect = 0;
  19. let meaningIncorrect = 0;
  20. let readingCorrect = 0;
  21. let readingIncorrect = 0;
  22. let correctHistory = [];
  23. let incorrectEntered = new Map();
  24. let itemsList = []; // Array to store the items reviewed
  25. let currentQuestionType = "";
  26. let currentCategory = "";
  27. let currentWord = "";
  28. let currentSRSLevel = -1;
  29. let quizQueueSRS = [];
  30. // Other Variables
  31. let SRSLevelNames = ["Lesson", "Appr. I", "Appr. II", "Appr. III", "Appr. IV", "Guru I", "Guru II", "Master", "Enl.", "Burned", "Error"];
  32. const GRAPH_HEIGHT = 120;
  33.  
  34. // Create style element with popup styles and append it to the document head
  35. let style = document.createElement("style");
  36. style.textContent = ".summary-popup { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; color: var(--color-text); background-color: var(--color-dashboard-panel-content-background, #eee); padding: 50px; overflow-y: auto; font-size: var(--font-size-large); }";
  37. style.textContent += ".summary-popup > a { background-color: transparent; text-decoration: none; text-align: center; margin: 30px 50px; position: absolute; top: 0px; right: 0px; cursor: pointer; padding: 10px; border-radius: 5px; outline: 1px solid var(--color-tertiary, black); color: var(--color-text) } .summary-popup > a:hover { color: var(--color-tertiary, #bbb); }";
  38. style.textContent += ".summary-popup table { border-collapse: collapse; border-radius: 5px; width: 100%; background-color: var(--color-dashboard-panel-background, #000); } .summary-popup td { border: none; padding: 5px; text-align: center; }";
  39. style.textContent += ".summary-popup h1 { margin-bottom: 10px; font-weight: bold; font-size: var(--font-size-xlarge); } .summary-popup h2 { font-weight: bold; margin-top: 20px; padding: 20px; color: #fff; font-size: var(--font-size-large); border-radius: 5px 5px 0 0; }";
  40. style.textContent += ".summary-popup ul { background-color: var(--color-dashboard-panel-background, #fff); padding: 5px; border-radius: 0 0 5px 5px; } .summary-popup li { display: inline-block; } .summary-popup li a { display: block; margin: 10px 5px; padding: 10px; color: var(--color-text-dark, #fff); font-size: 1.5rem; height: 2.6rem; border-radius: 5px; text-decoration: none; position: relative; } .summary-popup li a img { height: 1em; vertical-align: middle; }";
  41. style.textContent += ".summary-popup .summary-popup__popup { background-color: var(--color-menu, #ddd); color: var(--color-text, #fff); text-decoration: none; padding: 10px; border-radius: 5px; position: fixed; z-index: 9999; display: none; font-size: var(--font-size-medium); box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5); width: max-content; line-height: 1.3; }";
  42. style.textContent += ".summary-popup .summary-popup__popup:after { content: ''; position: absolute; top: -8px; margin-left: -10px; width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; border-bottom: 10px solid var(--color-menu, #ddd); }";
  43. style.textContent += ".summary-popup .summary-popup__popup--left:after { right: 15px; } .summary-popup .summary-popup__popup--right:after { left: 25px; }";
  44. style.textContent += ".summary-popup .accuracy-graph { position: relative; height: " + (GRAPH_HEIGHT + 42) + "px; width: 100%; background-color: var(--color-dashboard-panel-background, #fff); padding: 25px 1% 15px 1%; border-radius: 0 0 5px 5px; }";
  45. style.textContent += ".summary-popup .accuracy-graph span { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: var(--font-size-xlarge); color: var(--color-text); }";
  46. style.textContent += ".summary-popup ul .wk-icon { position: absolute; top: -8px; right: -8px; font-size: var(--font-size-xsmall); width: 1.5em; text-align: center; color: white; background-color: var(--color-burned); padding: 3px; border-radius: 50%; border: white solid 1px; }";
  47. style.textContent += ".summary-popup ul .incorrect-text { color: var(--color-incorrect, #cc4343); font-size: var(--font-size-small); vertical-align: top; }";
  48. if(window.matchMedia('(prefers-color-scheme: dark)').matches) style.textContent += ".summary-popup ul .incorrect-text { filter: brightness(2) }";
  49.  
  50. document.head.appendChild(style);
  51.  
  52. // Function to calculate the percentage
  53. function percentage(numerator, denominator) {
  54. if(denominator == 0) return "--";
  55. return Math.round(numerator / denominator * 100) + "%";
  56. }
  57.  
  58. // Function to get quiz queue SRS
  59. function getQuizQueueSRS() {
  60. quizQueueSRS = JSON.parse(document.querySelectorAll("#quiz-queue script[data-quiz-queue-target='subjectIdsWithSRS']")[0].innerHTML);
  61. }
  62. getQuizQueueSRS();
  63.  
  64. function injectEndCode() {
  65. // Clear the data-quiz-queue-done-url-value and data-quiz-queue-completion-url-value parameters on #quiz-queue
  66. //document.getElementById("quiz-queue").setAttribute("data-quiz-queue-done-url-value", "");
  67. function get_controller(name) { // Thanks to @rfindley for this function
  68. return Stimulus.getControllerForElementAndIdentifier(document.querySelector(`[data-controller~="${name}"]`),name);
  69. }
  70. let quizQueueController = get_controller("quiz-queue");
  71. let quizOnDoneReplacement = function() { showStatistics(); };
  72. quizQueueController.onDone = quizOnDoneReplacement.bind(quizQueueController);
  73. quizQueueController.quizQueue.onDone = quizQueueController.onDone;
  74. }
  75.  
  76. // Function to create a popup element
  77. function createPopup(content) {
  78. // Create a div element with some styles
  79. let popup = document.createElement("div");
  80. popup.className = "summary-popup";
  81.  
  82. // Create a close button with some styles and functionality
  83. let closeButton = document.createElement("a");
  84. closeButton.textContent = "Dashboard";
  85. closeButton.href = "https://www.wanikani.com/dashboard";
  86.  
  87. // Append the content and the close button to the popup
  88. popup.appendChild(content);
  89. popup.appendChild(closeButton);
  90.  
  91. return popup;
  92. }
  93.  
  94. // Function to create a table element with some data
  95. function createTable(data) {
  96. // Create a table
  97. let table = document.createElement("table");
  98. let row = document.createElement("tr");
  99. let row2 = document.createElement("tr");
  100. let row3 = document.createElement("tr");
  101.  
  102. // Loop through the data array
  103. for (let i = 0; i < data.length; i++) {
  104. // Create table cell elements
  105. let cell = document.createElement("td");
  106. cell.textContent = data[i][0];
  107. cell.style.fontSize = "var(--font-size-xxlarge)";
  108. cell.style.fontWeight = "bold";
  109. cell.style.padding = "25px 0 0 0";
  110. row.appendChild(cell);
  111.  
  112. let cell2 = document.createElement("td");
  113. cell2.textContent = data[i][1];
  114. cell2.style.fontSize = "var(--font-size-small)";
  115. cell2.style.fontStyle = "italic";
  116. cell2.style.color = "var(--color-text-mid, #999)";
  117. cell2.style.padding = "4px 0 10px 0";
  118. row2.appendChild(cell2);
  119.  
  120. let cell3 = document.createElement("td");
  121. cell3.textContent = data[i][2];
  122. cell3.style.fontSize = "var(--font-size-medium)";
  123. cell3.style.paddingBottom = "25px";
  124. row3.appendChild(cell3);
  125. }
  126. // Append the rows to the table
  127. table.append(row, row2, row3);
  128.  
  129. // Return the table element
  130. return table;
  131. }
  132.  
  133. // Function to create summary section
  134. function createSummarySectionTitle(title, icon, bgColor) {
  135. let sectionTitle = document.createElement("h2");
  136. let sectionTitleIcon = document.createElement("span");
  137. sectionTitleIcon.classList = "wk-icon fa-solid " + icon;
  138. sectionTitle.appendChild(sectionTitleIcon);
  139. sectionTitle.innerHTML += " " + title;
  140. sectionTitle.style.backgroundColor = bgColor;
  141. return sectionTitle;
  142. }
  143.  
  144. // Function to create graph
  145. function createGraph(data, canvas, congratulationMessageText) {
  146. createGraph(data, canvas, congratulationMessageText, null);
  147. }
  148. function createGraph(data, canvas, congratulationMessageText, labels) {
  149. let graphWidth = canvas.getBoundingClientRect().width;
  150. canvas.height = GRAPH_HEIGHT + 2;
  151. canvas.width = graphWidth;
  152. let sidesOffset = parseFloat(window.getComputedStyle(document.documentElement).getPropertyValue('--font-size-small')); // Offset to apply to sides so that label text will fit - is applied to sides and bottom
  153. let bottomOffset = sidesOffset * 1.3;
  154. let graphStep = (graphWidth - (sidesOffset * 2)) / (data.length - 1);
  155. let isAllPerfect = true;
  156. let ctx = canvas.getContext("2d");
  157. // Draw background horizontal lines
  158. ctx.beginPath();
  159. if(window.getComputedStyle(document.documentElement).getPropertyValue('--color-text-mid') == "") ctx.strokeStyle = "#aaa";
  160. else ctx.strokeStyle = window.getComputedStyle(document.documentElement).getPropertyValue('--color-dashboard-panel-content-background');
  161. ctx.lineWidth = 1;
  162. for (let i = 0; i < 4; i++) {
  163. let y = Math.round((GRAPH_HEIGHT - bottomOffset) / 3 * i) + 0.5;
  164. ctx.moveTo(0, y);
  165. ctx.lineTo(graphWidth, y);
  166. }
  167. ctx.stroke();
  168. // Draw graph
  169. ctx.beginPath();
  170. ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--color-text');
  171. ctx.lineWidth = 2;
  172. for (let i = 0; i < data.length; i++) {
  173. let x = graphStep * i + sidesOffset;
  174. let y = (GRAPH_HEIGHT - bottomOffset) * (1 - data[i]) + 1;
  175. if(data[i] != 1) isAllPerfect = false;
  176. // Draw line
  177. if(i == 0) ctx.moveTo(x, y);
  178. else ctx.lineTo(x, y);
  179. }
  180. ctx.stroke();
  181. // Draw labels
  182. if(labels != null) {
  183. let lastLabel = "";
  184. let isDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].includes(labels[0]);
  185. ctx.fillStyle = window.getComputedStyle(document.documentElement).getPropertyValue('--color-text');
  186. ctx.font = window.getComputedStyle(document.documentElement).getPropertyValue('--font-size-small') + " sans-serif";
  187. ctx.textAlign = "center";
  188. for (let i = 0; i < data.length; i++) {
  189. if(labels[i] != lastLabel) {
  190. let x = graphStep * i + sidesOffset;
  191. let y = GRAPH_HEIGHT - 1;
  192. ctx.fillText(labels[i], x, y);
  193. lastLabel = labels[i];
  194. } else if(isDays && i == data.length - 1) {
  195. let x = graphStep * i + sidesOffset;
  196. let y = GRAPH_HEIGHT - 1;
  197. ctx.fillText("Now", x, y);
  198. }
  199. }
  200. }
  201. // Show congratulation message if all perfect
  202. if(isAllPerfect) {
  203. let congratulationMessage = document.createElement("span");
  204. congratulationMessage.textContent = congratulationMessageText;
  205. canvas.parentNode.appendChild(congratulationMessage);
  206. }
  207. }
  208.  
  209. // Function to show the statistics when returning to the dashboard
  210. function showStatistics() {
  211. console.log(itemsList);
  212. // Check if there are any items reviewed
  213. if (itemsList.length > 0) {
  214. // Create a heading element with some text and styles
  215. let headingText = document.createElement("h1");
  216. let headingImage = document.createElement("span");
  217. headingImage.classList = "wk-icon fa-solid fa-square-check";
  218. headingText.appendChild(headingImage);
  219. headingText.innerHTML += " Review Summary";
  220. let heading = document.createElement("div");
  221. heading.append(headingText);
  222.  
  223. // Create an unordered list element
  224. let listCorrect = document.createElement("ul");
  225. let listIncorrect = document.createElement("ul");
  226.  
  227. // Loop through the items list array
  228. let srsUpNum = 0;
  229. let typeNum = [0, 0, 0];
  230. for (let i = 0; i < itemsList.length; i++) {
  231. // Create a list item element with the character or image
  232. let listItem = document.createElement("li");
  233. let listItemLink = document.createElement("a");
  234. if(itemsList[i].characters.url == null) listItemLink.textContent = itemsList[i].characters;
  235. else {
  236. let listItemImage = document.createElement("img");
  237. listItemImage.src = itemsList[i].characters.url;
  238. listItemLink.appendChild(listItemImage);
  239. }
  240. if (itemsList[i].type === "Radical") {
  241. listItemLink.style.backgroundColor = "var(--color-radical, #00aaff)";
  242. listItemLink.href = "https://www.wanikani.com/radicals/" + itemsList[i].meanings[0];
  243. } else if (itemsList[i].type === "Kanji") {
  244. listItemLink.style.backgroundColor = "var(--color-kanji, #ff00aa)";
  245. listItemLink.href = "https://www.wanikani.com/kanji/" + itemsList[i].characters;
  246. }
  247. else {
  248. listItemLink.style.backgroundColor = "var(--color-vocabulary, #aa00ff)";
  249. listItemLink.href = "https://www.wanikani.com/vocabulary/" + itemsList[i].characters;
  250. }
  251.  
  252. // Make link open in new tab
  253. listItemLink.target = "_blank";
  254.  
  255. // Badge if burned or if warning
  256. if(itemsList[i].newSRS == 9) {
  257. let badge = document.createElement("span");
  258. badge.classList = "wk-icon fa-solid fa-fire";
  259. listItemLink.style.paddingRight = "15px";
  260. listItemLink.appendChild(badge);
  261. } else if(itemsList[i].isWarning) {
  262. let badge = document.createElement("span");
  263. badge.classList = "wk-icon fa-solid fa-skull-crossbones";
  264. listItemLink.style.paddingRight = "15px";
  265. listItemLink.appendChild(badge);
  266. }
  267.  
  268. // Create popup with meaning and reading info on hover
  269. let popup = document.createElement("div");
  270. popup.className = "summary-popup__popup";
  271. popup.innerHTML = "Meaning: <strong>" + itemsList[i].meanings.slice(0, 2).join(", ") + "</strong>";
  272. if(itemsList[i].incorrectEntered != null && itemsList[i].incorrectEntered[0].length > 0) popup.innerHTML += '<br><span class="incorrect-text">&nbsp;&nbsp;&nbsp;<strong>X</strong>&nbsp;' + itemsList[i].incorrectEntered[0].join(", ") + "</span>";
  273. if(itemsList[i].type == "Kanji") {
  274. typeNum[1]++;
  275. for (let k = 0; k < itemsList[i].readings.length; k++) { // Nanori, Onyomi, Kunyomi readings if kanji
  276. if (itemsList[i].readings[k] != null) {
  277. let label = "";
  278. switch (k) {
  279. case 0:
  280. label = "Nanori";
  281. break;
  282. case 1:
  283. label = "Onyomi";
  284. break;
  285. case 2:
  286. label = "Kunyomi";
  287. break;
  288. }
  289. popup.innerHTML += "<br>" + label + ": <strong>" + itemsList[i].readings[k].join(", ") + "</strong>";
  290. }
  291. }
  292. }
  293. else if(itemsList[i].type != "Radical" && itemsList[i].readings[0] != null) { // Reading if vocabulary
  294. typeNum[2]++;
  295. popup.innerHTML += "<br>Reading: <strong>" +
  296. itemsList[i].readings.map(r => r.reading).join(", ") +
  297. "</strong>";
  298. } else { // No reading if radical
  299. typeNum[0]++;
  300. }
  301. if(itemsList[i].incorrectEntered != null && itemsList[i].incorrectEntered[1].length > 0) popup.innerHTML += '<br><span class="incorrect-text">&nbsp;&nbsp;&nbsp;<strong>X</strong>&nbsp;' + itemsList[i].incorrectEntered[1].join(", ") + "</span>";
  302. popup.innerHTML += "<br>SRS: " + SRSLevelNames[itemsList[i].oldSRS] + " -> " + SRSLevelNames[itemsList[i].newSRS];
  303.  
  304. popup.style.display = "block";
  305. popup.style.visibility = "hidden";
  306. listItemLink.addEventListener("mouseover", function(e) {
  307. // Position the popup element relative to the parent item element: to the right of the parent unless that would cause the popup to go off the screen
  308. let infoPos = listItemLink.getBoundingClientRect();
  309. let popupPos = popup.getBoundingClientRect();
  310. if (infoPos.left + popupPos.width + 5 > window.innerWidth) {
  311. popup.style.right = window.innerWidth - infoPos.right + "px";
  312. popup.style.removeProperty("left");
  313. popup.className = "summary-popup__popup summary-popup__popup--left";
  314. } else {
  315. popup.style.left = infoPos.left + "px";
  316. popup.style.removeProperty("right");
  317. popup.className = "summary-popup__popup summary-popup__popup--right";
  318. }
  319. popup.style.top = infoPos.bottom + 5 + "px";
  320. popup.style.visibility = "visible";
  321. });
  322.  
  323. listItemLink.addEventListener("mouseout", function(e) {
  324. popup.style.visibility = "hidden";
  325. });
  326. popup.style.visibility = "hidden";
  327.  
  328. // Append the list item to the list
  329. listItemLink.appendChild(popup);
  330. listItem.appendChild(listItemLink);
  331. if (itemsList[i].SRSUp) {
  332. listCorrect.appendChild(listItem);
  333. srsUpNum++;
  334. }
  335. else listIncorrect.appendChild(listItem);
  336. }
  337.  
  338. // Create a header table with main stats
  339. let data = [
  340. [itemsList.length, "R: " + typeNum[0] + " / K: " + typeNum[1] + " / V: " + typeNum[2], "Items Completed"],
  341. [percentage(srsUpNum, itemsList.length), srsUpNum + " out of " + itemsList.length, "Items Correct"],
  342. [percentage(itemsCorrect, questionsAnswered), itemsCorrect + " out of " + questionsAnswered , "Questions Correct"],
  343. [percentage(meaningCorrect, meaningCorrect + meaningIncorrect), meaningCorrect + " out of " + (meaningCorrect + meaningIncorrect), "Meanings Correct"],
  344. [percentage(readingCorrect, readingCorrect + readingIncorrect), readingCorrect + " out of " + (readingCorrect + readingIncorrect), "Readings Correct"]
  345. ];
  346. let table = createTable(data);
  347.  
  348. // Create h2 titles for the lists
  349. let correctTitle = createSummarySectionTitle(srsUpNum + " Items SRS Up", "fa-circle-up", "var(--color-quiz-correct-background, #88cc00)");
  350. let incorrectTitle = createSummarySectionTitle((itemsList.length - srsUpNum) + " Items SRS Down", "fa-circle-down", "var(--color-quiz-incorrect-background, #ff0033)");
  351.  
  352. // Create a graph showing accuracy throughout the session using the correctHistory array, with an average of 3 elements
  353. let graphTitle, graphDiv;
  354. if(itemsList.length > 4) {
  355. graphTitle = createSummarySectionTitle(" Session Accuracy", "fa-chart-simple", "var(--color-menu, #777)");
  356. // Graph
  357. graphDiv = document.createElement("div");
  358. graphDiv.classList = "accuracy-graph";
  359. let graph = document.createElement("canvas");
  360. graph.style.width = "100%";
  361. graph.style.height = "100%";
  362. graphDiv.appendChild(graph);
  363. }
  364.  
  365. // Get existing accuracy history array from local storage or create new one, then append the current accuracy to it and store it again
  366. let accuracyArray = JSON.parse(localStorage.getItem("WKSummaryAccuracyHistory")) || [];
  367. if(accuracyArray != [] && !Array.isArray(accuracyArray[0])) accuracyArray = [];
  368. accuracyArray.push([Math.round(srsUpNum / itemsList.length * 100) / 100, new Date().toLocaleString("en-US", {weekday: "short"})]);
  369. if(accuracyArray.length > 15) accuracyArray.shift(); // If the array is longer than 10 elements, remove the first one
  370. localStorage.setItem("WKSummaryAccuracyHistory", JSON.stringify(accuracyArray));
  371.  
  372. // Create a graph showing accuracy throughout the last 10 (or less) sessions
  373. let graphTitle2, graphDiv2;
  374. if(accuracyArray.length > 3) {
  375. graphTitle2 = createSummarySectionTitle(" Accuracy History", "fa-clock-rotate-left", "var(--color-menu, #777)");
  376. // Graph
  377. graphDiv2 = document.createElement("div");
  378. graphDiv2.classList = "accuracy-graph";
  379. let graph = document.createElement("canvas");
  380. graph.style.width = "100%";
  381. graph.style.height = "100%";
  382. graphDiv2.appendChild(graph);
  383. }
  384.  
  385. // Create a div element to wrap everything
  386. let content = document.createElement("div");
  387. content.append(heading, table, incorrectTitle, listIncorrect, correctTitle, listCorrect, graphTitle ? graphTitle : "", graphDiv ? graphDiv : "", graphTitle2 ? graphTitle2 : "", graphDiv2 ? graphDiv2 : "");
  388. // Create a popup element with all the summary content
  389. let popup = createPopup(content);
  390. document.body.appendChild(popup);
  391.  
  392. // If it exists, fill the graph with the correctHistory array
  393. if(graphDiv) {
  394. let graph = graphDiv.querySelector("canvas");
  395. // Calculate graph data
  396. let graphData = [];
  397. for (let i = 1; i < correctHistory.length - 1; i++) {
  398. graphData.push((correctHistory[i-1] + correctHistory[i] + correctHistory[i+1]) / 3);
  399. }
  400. createGraph(graphData, graph, "🎊 Perfect session! 🎊");
  401. }
  402. // If it exists, fill the second graph with the accuracyArray array
  403. if(graphDiv2) {
  404. let graph = graphDiv2.querySelector("canvas");
  405. createGraph(accuracyArray.map(tuple => tuple[0]), graph, "🎊 Perfect history! 🎊", accuracyArray.map(tuple => tuple[1]));
  406. }
  407.  
  408. // Reset the statistics variables
  409. questionsAnswered = 0;
  410. itemsCorrect = 0;
  411. itemsIncorrect = 0;
  412. meaningCorrect = 0;
  413. meaningIncorrect = 0;
  414. readingCorrect = 0;
  415. readingIncorrect = 0;
  416. itemsList = [];
  417. } else {
  418. window.location.href = "https://www.wanikani.com/dashboard";
  419. }
  420. }
  421.  
  422. let eventToObserve = window.doublecheck == null ? "didAnswerQuestion" : "didFinalAnswer";
  423. // Add an event listener for the didAnswerQuestion event
  424. window.addEventListener(eventToObserve, function(e) {
  425. if(questionsAnswered == 0) {
  426. injectEndCode();
  427. }
  428. // Check if the answer was correct or not by looking for the correct attribute
  429. let correct = document.querySelector(".quiz-input__input-container[correct='true']") !== null;
  430. correctHistory.push(correct);
  431.  
  432. // Record the answer entered if incorrect
  433. if (!correct) {
  434. if(!incorrectEntered.has(e.detail.subjectWithStats.subject.id)) incorrectEntered.set(e.detail.subjectWithStats.subject.id, [[], []]);
  435. if(currentQuestionType === "meaning") incorrectEntered.get(e.detail.subjectWithStats.subject.id)[0].push(e.detail.answer);
  436. else if(currentQuestionType === "reading") incorrectEntered.get(e.detail.subjectWithStats.subject.id)[1].push(e.detail.answer);
  437. }
  438.  
  439. // Increment the questions answered and correct/incorrect counters
  440. questionsAnswered++;
  441. if (currentQuestionType === "meaning") {
  442. correct ? meaningCorrect++ : meaningIncorrect++;
  443. } else if (currentQuestionType === "reading") {
  444. correct ? readingCorrect++ : readingIncorrect++;
  445. }
  446. if (correct) itemsCorrect++;
  447. else itemsIncorrect++;
  448. });
  449.  
  450. // Add an event listener for the didCompleteSubject event
  451. window.addEventListener("didCompleteSubject", function(e) {
  452. // Get the subject data from the event detail
  453. let subject = e.detail.subjectWithStats.subject;
  454. let didSRSUp = e.detail.subjectWithStats.stats.meaning.incorrect === 0 && e.detail.subjectWithStats.stats.reading.incorrect === 0;
  455. let reading = null;
  456. if(subject.type == "Vocabulary" || subject.type == "KanaVocabulary") {
  457. reading = subject.readings;
  458. } else if (subject.type == "Kanji") {
  459. reading = [null, null, null];
  460. if(subject.nanori.length > 0) {
  461. reading[0] = subject.nanori;
  462. }
  463. if(subject.onyomi.length > 0) {
  464. reading[1] = subject.onyomi;
  465. }
  466. if(subject.kunyomi.length > 0) {
  467. reading[2] = subject.kunyomi;
  468. }
  469. }
  470.  
  471. let isWarning = e.detail.subjectWithStats.stats.meaning.incorrect + e.detail.subjectWithStats.stats.reading.incorrect > 2 || (e.detail.subjectWithStats.stats.meaning.incorrect > 0 && e.detail.subjectWithStats.stats.reading.incorrect > 0);
  472.  
  473. // Calculate the new SRS level
  474. let newSRSLevel = didSRSUp ? currentSRSLevel + 1 : (currentSRSLevel < 2 ? currentSRSLevel : (currentSRSLevel < 5 ? currentSRSLevel - 1 : currentSRSLevel - 2));
  475. console.log(subject.characters + " - Old SRS Level: " + SRSLevelNames[currentSRSLevel] + " New SRS Level: " + SRSLevelNames[newSRSLevel]);
  476.  
  477. // Push the subject data to the items list array
  478. let subjectInfoToSave = { characters: subject.characters, type: subject.type, id: subject.id, SRSUp: didSRSUp, meanings: subject.meanings, readings: reading, oldSRS: currentSRSLevel, newSRS: newSRSLevel, isWarning: isWarning, incorrectEntered: incorrectEntered.get(subject.id) };
  479. itemsList.push(subjectInfoToSave);
  480. });
  481.  
  482. // Add an event listener for the willShowNextQuestion event
  483. window.addEventListener("willShowNextQuestion", function(e) {
  484. // Set current question variables with event info
  485. currentQuestionType = e.detail.questionType;
  486. currentCategory = e.detail.subject.type;
  487. currentWord = e.detail.subject.characters;
  488.  
  489. currentSRSLevel = quizQueueSRS.find(function(element) { return element[0] == e.detail.subject.id; })[1];
  490. if(currentSRSLevel == null) {
  491. getQuizQueueSRS();
  492. currentSRSLevel = quizQueueSRS.find(function(element) { return element[0] == e.detail.subject.id; })[1];
  493. if(currentSRSLevel == null) currentSRSLevel = 10;
  494. }
  495. });
  496.  
  497. // Add an event listener for the turbo before-visit event
  498. window.addEventListener("turbo:before-visit", function(e) {
  499. // Show stats if .summary-popup is not already visible and there are items reviewed
  500. if (document.querySelector(".summary-popup") === null && questionsAnswered > 0) {
  501. e.preventDefault();
  502. showStatistics();
  503. }
  504.  
  505. });
  506.  
  507. // Home button override
  508. let homeButton = document.querySelector(".summary-button");
  509. homeButton.setAttribute("title", "Show statistics and return to dashboard");
  510. homeButton.addEventListener("click", function(e) {
  511. // Show stats if .summary-popup is not already visible and there are items reviewed
  512. if (document.querySelector(".summary-popup") === null && questionsAnswered > 0) {
  513. e.preventDefault();
  514. showStatistics();
  515. }
  516. });
  517.  
  518. // If statistics screen is open, set the right arrow key and the escape key to go back to the dashboard
  519. document.addEventListener("keydown", function(e) {
  520. if (document.querySelector(".summary-popup") !== null && (e.key === "ArrowRight" || e.key === "Escape")) {
  521. e.preventDefault();
  522. window.location.href = "https://www.wanikani.com/dashboard";
  523. }
  524. });
  525.  
  526. })();