[Userscript] Summary Page: Revival

0.5.7 released with a permanent fix (Custom Icons Library) for the broken icons!

That took much longer than it should have between trying to understand SVG namespaces and making a library script to actually work across scripts, all while at airports and on buses lmao but finally it’s fixed!

1 Like

The skull icons seem to be broken again, but the burn icons are somehow still working this time.

1 Like

Fixed and update released, thanks for letting me know!

1 Like

With the latest update, the link back to the dashboard is broken, and there is an overlap with the main content and the header from wanikani. The lesson page of the custom SRS script had the same overlap problem as well.

1 Like

Reporting the same problem, this is happening to me too.

1 Like

Currently out for the day but will get this fixed as soon as I get home tonight! Thanks for the reports :slight_smile:

1 Like

0.6.1 released - should hopefully fix the issues with the updated dashboard (including the script not loading when starting review from dashboard - although keep in mind many scripts are currently broken when starting reviews like that rather than directly loading the link so there may be issues if you have others active) @gijsc1 @vikitty

3 Likes

0.6.2 released - should fix some issues caused by today’s WK update. Let me know if anyone has issues with it still.

3 Likes

Just wanted to say thanks for making this! I installed it yesterday after my reviews and completely forgot about it, so it was a fun and pleasant surprise at the end of today’s session :smile:

3 Likes

I don’t think I’ve seen this before, so figured I’d report - for whatever reason, the yurt radical showed up in black at the end of my sesh:

1 Like

Ah thanks for reporting. I think it’s because that’s one of the very few radicals that are images rather than text characters, meaning the standard text styling doesn’t work. I’ll keep an eye out for if any appear for me so that I can get it fixed…

1 Like

I am really liking this UserScript. It really seems like something that should be a part of WaniKani by default.

Today I did some extra-study and noticed that the script works there too. Cool. However, since the extra-study does not effect the SRS it would be nice if the summary page reflected that. When the summary page popped up, I didn’t know that it would actually, it said I had ten item with SRS down. I thought for a moment I had actually went into reviews and not extra-study. :cold_sweat:

So, I hovered over a couple of them and found something I had not seen before:

and some are like:

Not all of them show up with an error. But out of the 38 in the session, 26 had one or the other of the two errors I showed above. I only had vocabulary and kanji in this batch and the errors were on both. I could not see an obvious pattern. All of the kanji were from level 10, were I am at now and the vocab was between 8, 9 and 10.

This is actually the first time I had hovered over a kanji/vocab and did not even know that feature was there so I can’t comment yet on if this is only in Extra-Study or also in my regular reviews. I have 36 reviews to do so I’ll edit this post later with what I find out there.

1 Like

Oh, it’s not actually intended to run in extra study, I must’ve accidentally enabled it there at some point! :sweat_smile: When I get the chance at some point I’ll make it not show SRS info when you’re in extra study as you say!
Let me know if you have that same issue with the normal reviews though, as it should be working fine there.

2 Likes

Hello, I did 60 reviews last night, each in batches of ten and in none of them were there those error messages when I hovered over the boxes. Looks like I have 190 to do today so I’ll continue to watch it.

I noticed it also runs for reviews recent mistakes and it runs when doing the extra-study associated with Reorder Omega, but I’m having issue with script unfortunately and did not even mean to do that extra-study. That was also weird, I used the home icon to exit and it did not go to your summary page but when I went back into it as I was trying to see how I got into in the first place, the summary page appeared and clicking dashboard just once put me into the Reorder Omega quiz. I have not looked into that weirdness any further. It has been working great in my normal reviews and that is really what I care about. Great script!! Thank you so much for it! :pray:

The only thing I have noticed is that sometimes I have to click the Dashboard button up to six times at the most so far. I

1 Like

Hey, me again :smiley: just wanted to note another rogue radical that’s exhibiting the coloring issue - tofu!

1 Like

Fyi yesterday’s WK maintenance updates didn’t break too much, but right now the summary screen doesn’t show meaning/reading details correctly:


I’ll get it fixed this weekend - I’m hoping it won’t be anything too major!

2 Likes

If you want a head start, here’s what I did to update it (including the structural changes from yesterday) and a manual fix for the Turbo loading. Though it doesn’t really take into account any persistence logic, so yeah.

Edit: Updated the initial Turbo handler so that it will continue to observe review sessions after the first one when the initial page loaded was a review session.

Edit 2: Updated it so the event listeners are only added once, even if review sessions are left and reentered. Oh, and made it so Enter also exits the review summary (though I don’t think I did it right, so I’ll need to fix that).

Edit 3: Was able to get the Enter key to work by moving showStatistics() calls to be inside a setTimeout().

// ==UserScript==
// @name Wanikani Review Summary
// @namespace https://tampermonkey.net/
// @version 0.6.2.1
// @license MIT
// @description Show a popup with statistics about the review session when returning to the dashboard
// @author leohumnew
// @match https://www.wanikani.com/*
// @require https://greasyfork.org/scripts/489759-wk-custom-icons/code/CustomIcons.js?version=1417568
// @grant none
// ==/UserScript==

(function() {
    'use strict';

    let eventListenersAdded = false;

    window.addEventListener("turbo:load", function(e) {
        if (e.detail.url === "https://www.wanikani.com/subjects/review") {
            setTimeout(runScript, 0);
        }
    });
    if ("https://www.wanikani.com/subjects/review" === (window.Turbo?.session.history.pageLoaded ? window.Turbo.session.history.location.href : (document.readyState === "complete" ? window.location.href : null))) {
        runScript();
    }
    function keyListener(e) {
        // If statistics screen is open, set the right arrow key and the escape key to go back to the dashboard
        if (document.getElementById("summary-popup") != null) {
            switch (e.key) {
                case "ArrowRight":
                case "Enter":
                case "Escape":
                    e.preventDefault();
                    document.body.removeEventListener("keydown", keyListener);
                    if (window.Turbo) window.Turbo.visit("https://www.wanikani.com/dashboard");
                    else window.location.href = "https://www.wanikani.com/dashboard";
                    break;
            }
        }
    }

    async function runScript() {
        // Variables to store the statistics
        let questionsAnswered = 0;
        let itemsCorrect = 0;
        let itemsIncorrect = 0;
        let meaningCorrect = 0;
        let meaningIncorrect = 0;
        let readingCorrect = 0;
        let readingIncorrect = 0;
        let correctHistory = [];
        let incorrectEntered = new Map();
        let itemsList = []; // Array to store the items reviewed
        let currentQuestionType = "";
        let currentCategory = "";
        let currentWord = "";
        let currentSRSLevel = -1;
        let quizQueueSRS = [];
        // Other Variables
        let SRSLevelNames = ["Lesson", "Appr. I", "Appr. II", "Appr. III", "Appr. IV", "Guru I", "Guru II", "Master", "Enl.", "Burned", "Error"];
        const GRAPH_HEIGHT = 120;

        // Create style element with popup styles and append it to the document head
        let style = document.createElement("style");
        style.id = "summary-popup-styles";
        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-wk-panel-content-background, #eee); padding: 50px; overflow-y: auto; font-size: var(--font-size-large); }";
        style.textContent += ".summary-popup .wk-icon { vertical-align: bottom; }";
        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); }";
        style.textContent += ".summary-popup table { border-collapse: collapse; border-radius: 5px; width: 100%; background-color: var(--color-wk-panel-background, #000); } .summary-popup td { border: none; padding: 5px; text-align: center; }";
        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; }";
        style.textContent += ".summary-popup ul { background-color: var(--color-wk-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; }";
        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; }";
        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); }";
        style.textContent += ".summary-popup .summary-popup__popup--left:after { right: 15px; } .summary-popup .summary-popup__popup--right:after { left: 25px; }";
        style.textContent += ".summary-popup .accuracy-graph { position: relative; height: " + (GRAPH_HEIGHT + 42) + "px; width: 100%; background-color: var(--color-wk-panel-background, #fff); padding: 25px 1% 15px 1%; border-radius: 0 0 5px 5px; }";
        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); }";
        style.textContent += ".summary-popup ul .wk-icon { position: absolute; top: -8px; right: -8px; text-align: center; color: white; background-color: var(--color-burned); padding: 3px; border-radius: 50%; border: white solid 1px }";
        style.textContent += ".summary-popup ul .incorrect-text { color: var(--color-incorrect, #cc4343); font-size: var(--font-size-small); vertical-align: top; }";
        if(window.matchMedia('(prefers-color-scheme: dark)').matches) style.textContent += ".summary-popup ul .incorrect-text { filter: brightness(2) }";

        if(!document.getElementById("summary-popup-styles")) document.head.appendChild(style);

        // Function to calculate the percentage
        function percentage(numerator, denominator) {
            if(denominator == 0) return "--";
            return Math.round(numerator / denominator * 100) + "%";
        }

        // Function to get quiz queue SRS
        async function getQuizQueueSRS() {
            let elementArr = document.querySelector("#quiz-queue script[data-quiz-queue-target='subjectIdsWithSRS']");
            if(elementArr) {
                quizQueueSRS = JSON.parse(elementArr.innerHTML);
                quizQueueSRS = quizQueueSRS.subject_ids_with_srs_info;
            } else setTimeout(getQuizQueueSRS, 500);
        }
        getQuizQueueSRS();

        function injectEndCode() {
            // Clear the data-quiz-queue-done-url-value and data-quiz-queue-completion-url-value parameters on #quiz-queue
            function get_controller(name) { // Thanks to @rfindley for this function
                return Stimulus.getControllerForElementAndIdentifier(document.querySelector(`[data-controller~="${name}"]`),name);
            }
            let quizQueueController = get_controller("quiz-queue");
            let quizOnDoneReplacement = function() { setTimeout(showStatistics, 0); };
            quizQueueController.onDone = quizOnDoneReplacement.bind(quizQueueController);
            quizQueueController.quizQueue.onDone = quizQueueController.onDone;
        }

        // Function to create a popup element
        function createPopup(content) {
            // Create a div element with some styles
            let popup = document.createElement("div");
            popup.id = "summary-popup";
            popup.className = "summary-popup";

            // Create a close button with some styles and functionality
            let closeButton = document.createElement("a");
            closeButton.textContent = "Dashboard";
            closeButton.href = "https://www.wanikani.com/dashboard";
            closeButton.addEventListener('click', function() {
                document.body.removeEventListener("keydown", keyListener);
                document.getElementById('summary-popup')?.remove();
            });

            // Append the content and the close button to the popup
            popup.append(content, closeButton);

            return popup;
        }

        // Function to create a table element with some data
        function createTable(data) {
            // Create a table
            let table = document.createElement("table");
            let row = document.createElement("tr");
            let row2 = document.createElement("tr");
            let row3 = document.createElement("tr");

            // Loop through the data array
            for (let i = 0; i < data.length; i++) {
                // Create table cell elements
                let cell = document.createElement("td");
                cell.textContent = data[i][0];
                cell.style.fontSize = "var(--font-size-xxlarge)";
                cell.style.fontWeight = "bold";
                cell.style.padding = "25px 0 0 0";
                row.appendChild(cell);

                let cell2 = document.createElement("td");
                cell2.textContent = data[i][1];
                cell2.style.fontSize = "var(--font-size-small)";
                cell2.style.fontStyle = "italic";
                cell2.style.color = "var(--color-text-mid, #999)";
                cell2.style.padding = "4px 0 10px 0";
                row2.appendChild(cell2);

                let cell3 = document.createElement("td");
                cell3.textContent = data[i][2];
                cell3.style.fontSize = "var(--font-size-medium)";
                cell3.style.paddingBottom = "25px";
                row3.appendChild(cell3);
            }
            // Append the rows to the table
            table.append(row, row2, row3);

            // Return the table element
            return table;
        }

        // Function to create summary section
        function createSummarySectionTitle(title, icon, bgColor) {
            let sectionTitle = document.createElement("h2");
            sectionTitle.appendChild(Icons.customIcon(icon));
            sectionTitle.innerHTML += " " + title;
            sectionTitle.style.backgroundColor = bgColor;
            return sectionTitle;
        }

        // Function to create graph
        function createGraph(data, canvas, congratulationMessageText) {
            createGraph(data, canvas, congratulationMessageText, null);
        }
        function createGraph(data, canvas, congratulationMessageText, labels) {
            let graphWidth = canvas.getBoundingClientRect().width;
            canvas.height = GRAPH_HEIGHT + 2;
            canvas.width = graphWidth;
            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
            let bottomOffset = sidesOffset * 1.3;
            let graphStep = (graphWidth - (sidesOffset * 2)) / (data.length - 1);
            let isAllPerfect = true;
            let ctx = canvas.getContext("2d");
            // Draw background horizontal lines
            ctx.beginPath();
            if(window.getComputedStyle(document.documentElement).getPropertyValue('--color-text-mid') == "") ctx.strokeStyle = "#aaa";
            else ctx.strokeStyle = window.getComputedStyle(document.documentElement).getPropertyValue('--color-wk-panel-content-background');
            ctx.lineWidth = 1;
            for (let i = 0; i < 4; i++) {
                let y = Math.round((GRAPH_HEIGHT - bottomOffset) / 3 * i) + 0.5;
                ctx.moveTo(0, y);
                ctx.lineTo(graphWidth, y);
            }
            ctx.stroke();
            // Draw graph
            ctx.beginPath();
            ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--color-text');
            ctx.lineWidth = 2;
            for (let i = 0; i < data.length; i++) {
                let x = graphStep * i + sidesOffset;
                let y = (GRAPH_HEIGHT - bottomOffset) * (1 - data[i]) + 1;
                if(data[i] != 1) isAllPerfect = false;
                // Draw line
                if(i == 0) ctx.moveTo(x, y);
                else ctx.lineTo(x, y);
            }
            ctx.stroke();
            // Draw labels
            if(labels != null) {
                let lastLabel = "";
                let isDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].includes(labels[0]);
                ctx.fillStyle = window.getComputedStyle(document.documentElement).getPropertyValue('--color-text');
                ctx.font = window.getComputedStyle(document.documentElement).getPropertyValue('--font-size-small') + " sans-serif";
                ctx.textAlign = "center";
                for (let i = 0; i < data.length; i++) {
                    if(labels[i] != lastLabel) {
                        let x = graphStep * i + sidesOffset;
                        let y = GRAPH_HEIGHT - 1;
                        ctx.fillText(labels[i], x, y);
                        lastLabel = labels[i];
                    } else if(isDays && i == data.length - 1) {
                        let x = graphStep * i + sidesOffset;
                        let y = GRAPH_HEIGHT - 1;
                        ctx.fillText("Now", x, y);
                    }
                }
            }
            // Show congratulation message if all perfect
            if(isAllPerfect) {
                let congratulationMessage = document.createElement("span");
                congratulationMessage.textContent = congratulationMessageText;
                canvas.parentNode.appendChild(congratulationMessage);
            }
        }

        // Function to show the statistics when returning to the dashboard
        function showStatistics() {
            console.log(itemsList);
            // Check if there are any items reviewed
            if (itemsList.length > 0) {
                // Create a heading element with some text and styles
                let headingText = document.createElement("h1");
                headingText.appendChild(Icons.customIcon("check-checked"));
                headingText.innerHTML += " Review Summary";
                let heading = document.createElement("div");
                heading.append(headingText);

                // Create an unordered list element
                let listCorrect = document.createElement("ul");
                let listIncorrect = document.createElement("ul");

                // Loop through the items list array
                let srsUpNum = 0;
                let typeNum = [0, 0, 0];
                for (let i = 0; i < itemsList.length; i++) {
                    // Create a list item element with the character or image
                    let listItem = document.createElement("li");
                    let listItemLink = document.createElement("a");
                    if(itemsList[i].characters.url == null) listItemLink.textContent = itemsList[i].characters;
                    else {
                        let listItemImage = document.createElement("img");
                        listItemImage.src = itemsList[i].characters.url;
                        listItemLink.appendChild(listItemImage);
                    }
                    if (itemsList[i].type === "Radical") {
                        listItemLink.style.backgroundColor = "var(--color-radical, #00aaff)";
                        listItemLink.href = "https://www.wanikani.com/radicals/" + itemsList[i].meanings[0];
                    } else if (itemsList[i].type === "Kanji") {
                        listItemLink.style.backgroundColor = "var(--color-kanji, #ff00aa)";
                        listItemLink.href = "https://www.wanikani.com/kanji/" + itemsList[i].characters;
                    }
                    else {
                        listItemLink.style.backgroundColor = "var(--color-vocabulary, #aa00ff)";
                        listItemLink.href = "https://www.wanikani.com/vocabulary/" + itemsList[i].characters;
                    }

                    // Make link open in new tab
                    listItemLink.target = "_blank";

                    // Badge if burned or if warning
                    if(itemsList[i].newSRS == 9) {
                        listItemLink.style.paddingRight = "15px";
                        listItemLink.appendChild(Icons.customIcon("fire"));
                    } else if(itemsList[i].isWarning) {
                        listItemLink.style.paddingRight = "15px";
                        listItemLink.appendChild(Icons.customIcon("warning"));
                    }

                    // Create popup with meaning and reading info on hover
                    let popup = document.createElement("div");
                    popup.className = "summary-popup__popup";
                    popup.innerHTML = "Meaning: <strong>" + itemsList[i].meanings.slice(0, 2).join(", ") + "</strong>";
                    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>";
                    if(itemsList[i].type == "Kanji") {
                        typeNum[1]++;
                        for (let k = 0; k < itemsList[i].readings.length; k++) { // Nanori, Onyomi, Kunyomi readings if kanji
                            if (itemsList[i].readings[k] != null) {
                                let label = "";
                                switch (k) {
                                case 0:
                                    label = "Nanori";
                                    break;
                                case 1:
                                    label = "Onyomi";
                                    break;
                                case 2:
                                    label = "Kunyomi";
                                    break;
                                }
                                popup.innerHTML += "<br>" + label + ": <strong>" + itemsList[i].readings[k].join(", ") + "</strong>";
                            }
                        }
                    }
                    else if(itemsList[i].type != "Radical" && itemsList[i].readings[0] != null) { // Reading if vocabulary
                        typeNum[2]++;
                        popup.innerHTML += "<br>Reading: <strong>" +
                                        itemsList[i].readings.map(r => r.reading).join(", ") +
                                        "</strong>";
                    } else { // No reading if radical
                        typeNum[0]++;
                    }
                    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>";
                    popup.innerHTML += "<br>SRS: " + SRSLevelNames[itemsList[i].oldSRS] + " -> " + SRSLevelNames[itemsList[i].newSRS];

                    popup.style.display = "block";
                    popup.style.visibility = "hidden";
                    listItemLink.addEventListener("mouseover", function(e) {
                        // 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
                        let infoPos = listItemLink.getBoundingClientRect();
                        let popupPos = popup.getBoundingClientRect();
                        if (infoPos.left + popupPos.width + 5 > window.innerWidth) {
                            popup.style.right = window.innerWidth - infoPos.right + "px";
                            popup.style.removeProperty("left");
                            popup.className = "summary-popup__popup summary-popup__popup--left";
                        } else {
                            popup.style.left = infoPos.left + "px";
                            popup.style.removeProperty("right");
                            popup.className = "summary-popup__popup summary-popup__popup--right";
                        }
                        popup.style.top = infoPos.bottom + 5 + "px";
                        popup.style.visibility = "visible";
                    });

                    listItemLink.addEventListener("mouseout", function(e) {
                        popup.style.visibility = "hidden";
                    });
                    popup.style.visibility = "hidden";

                    // Append the list item to the list
                    listItemLink.appendChild(popup);
                    listItem.appendChild(listItemLink);
                    if (itemsList[i].SRSUp) {
                        listCorrect.appendChild(listItem);
                        srsUpNum++;
                    }
                    else listIncorrect.appendChild(listItem);
                }

                // Create a header table with main stats
                let data = [
                    [itemsList.length, "R: " + typeNum[0] + " / K: " + typeNum[1] + " / V: " + typeNum[2], "Items Completed"],
                    [percentage(srsUpNum, itemsList.length), srsUpNum + " out of " + itemsList.length, "Items Correct"],
                    [percentage(itemsCorrect, questionsAnswered), itemsCorrect + " out of " + questionsAnswered , "Questions Correct"],
                    [percentage(meaningCorrect, meaningCorrect + meaningIncorrect), meaningCorrect + " out of " + (meaningCorrect + meaningIncorrect), "Meanings Correct"],
                    [percentage(readingCorrect, readingCorrect + readingIncorrect), readingCorrect + " out of " + (readingCorrect + readingIncorrect), "Readings Correct"]
                ];
                let table = createTable(data);

                // Create h2 titles for the lists
                let correctTitle = createSummarySectionTitle(srsUpNum + " Items SRS Up", "srs-up", "var(--color-quiz-correct-background, #88cc00)");
                let incorrectTitle = createSummarySectionTitle((itemsList.length - srsUpNum) + " Items SRS Down", "srs-down", "var(--color-quiz-incorrect-background, #ff0033)");

                // Create a graph showing accuracy throughout the session using the correctHistory array, with an average of 3 elements
                let graphTitle, graphDiv;
                if(itemsList.length > 4) {
                    graphTitle = createSummarySectionTitle(" Session Accuracy", "chart-line", "var(--color-menu, #777)");
                    // Graph
                    graphDiv = document.createElement("div");
                    graphDiv.classList = "accuracy-graph";
                    let graph = document.createElement("canvas");
                    graph.style.width = "100%";
                    graph.style.height = "100%";
                    graphDiv.appendChild(graph);
                }

                // Get existing accuracy history array from local storage or create new one, then append the current accuracy to it and store it again
                let accuracyArray = JSON.parse(localStorage.getItem("WKSummaryAccuracyHistory")) || [];
                if(accuracyArray != [] && !Array.isArray(accuracyArray[0])) accuracyArray = [];
                accuracyArray.push([Math.round(srsUpNum / itemsList.length * 100) / 100, new Date().toLocaleString("en-US", {weekday: "short"})]);
                if(accuracyArray.length > 15) accuracyArray.shift(); // If the array is longer than 10 elements, remove the first one
                localStorage.setItem("WKSummaryAccuracyHistory", JSON.stringify(accuracyArray));

                // Create a graph showing accuracy throughout the last 10 (or less) sessions
                let graphTitle2, graphDiv2;
                if(accuracyArray.length > 3) {
                    graphTitle2 = createSummarySectionTitle(" Accuracy History", "chart-line", "var(--color-menu, #777)");
                    // Graph
                    graphDiv2 = document.createElement("div");
                    graphDiv2.classList = "accuracy-graph";
                    let graph = document.createElement("canvas");
                    graph.style.width = "100%";
                    graph.style.height = "100%";
                    graphDiv2.appendChild(graph);
                }

                // Create a div element to wrap everything
                let content = document.createElement("div");
                content.append(heading, table, incorrectTitle, listIncorrect, correctTitle, listCorrect, graphTitle ? graphTitle : "", graphDiv ? graphDiv : "", graphTitle2 ? graphTitle2 : "", graphDiv2 ? graphDiv2 : "");
                // Create a popup element with all the summary content
                let popup = createPopup(content);
                (document.getElementById('turbo-body') ?? document.body).appendChild(popup);

                document.body.addEventListener("keydown", keyListener);

                // If it exists, fill the graph with the correctHistory array
                if(graphDiv) {
                    let graph = graphDiv.querySelector("canvas");
                    // Calculate graph data
                    let graphData = [];
                    for (let i = 1; i < correctHistory.length - 1; i++) {
                        graphData.push((correctHistory[i-1] + correctHistory[i] + correctHistory[i+1]) / 3);
                    }
                    createGraph(graphData, graph, "🎊 Perfect session! 🎊");
                }
                // If it exists, fill the second graph with the accuracyArray array
                if(graphDiv2) {
                    let graph = graphDiv2.querySelector("canvas");
                    createGraph(accuracyArray.map(tuple => tuple[0]), graph, "🎊 Perfect history! 🎊", accuracyArray.map(tuple => tuple[1]));
                }

                // Reset the statistics variables
                questionsAnswered = 0;
                itemsCorrect = 0;
                itemsIncorrect = 0;
                meaningCorrect = 0;
                meaningIncorrect = 0;
                readingCorrect = 0;
                readingIncorrect = 0;
                itemsList = [];
            } else {
                if (window.Turbo) window.Turbo.visit("https://www.wanikani.com/dashboard");
                else window.location.href = "https://www.wanikani.com/dashboard";
            }
        }

        function addEventListeners() {
            if (eventListenersAdded) return;
            eventListenersAdded = true;

            let eventToObserve = window.doublecheck == null ? "didAnswerQuestion" : "didFinalAnswer";
            // Add an event listener for the didAnswerQuestion event
            window.addEventListener(eventToObserve, function(e) {
                if(questionsAnswered == 0) {
                    injectEndCode();
                }
                // Check if the answer was correct or not by looking for the correct attribute
                let correct = document.querySelector(".quiz-input__input-container[correct='true']") !== null;
                correctHistory.push(correct);

                // Record the answer entered if incorrect
                if (!correct) {
                    if(!incorrectEntered.has(e.detail.subjectWithStats.subject.id)) incorrectEntered.set(e.detail.subjectWithStats.subject.id, [[], []]);
                    if(currentQuestionType === "meaning") incorrectEntered.get(e.detail.subjectWithStats.subject.id)[0].push(e.detail.answer);
                    else if(currentQuestionType === "reading") incorrectEntered.get(e.detail.subjectWithStats.subject.id)[1].push(e.detail.answer);
                }

                // Increment the questions answered and correct/incorrect counters
                questionsAnswered++;
                if (currentQuestionType === "meaning") {
                    correct ? meaningCorrect++ : meaningIncorrect++;
                } else if (currentQuestionType === "reading") {
                    correct ? readingCorrect++ : readingIncorrect++;
                }
                if (correct) itemsCorrect++;
                else itemsIncorrect++;
            });

            // Add an event listener for the didCompleteSubject event
            window.addEventListener("didCompleteSubject", function(e) {
                // Get the subject data from the event detail
                let subject = e.detail.subjectWithStats.subject;
                let didSRSUp = e.detail.subjectWithStats.stats.meaning.incorrect === 0 && e.detail.subjectWithStats.stats.reading.incorrect === 0;
                let reading = null;
                if(subject.type == "Vocabulary" || subject.type == "KanaVocabulary") {
                    reading = subject.readings;
                } else if (subject.type == "Kanji") {
                    reading = [
                        subject.readings?.filter(r => r.type === "nanori").map(r => r.text),
                        subject.readings?.filter(r => r.type === "onyomi").map(r => r.text),
                        subject.readings?.filter(r => r.type === "kunyomi").map(r => r.text),
                    ].map(r => r?.length > 0 ? r : null);
                }

                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);

                // Calculate the new SRS level
                let newSRSLevel = didSRSUp ? currentSRSLevel + 1 : (currentSRSLevel < 2 ? currentSRSLevel : (currentSRSLevel < 5 ? currentSRSLevel - 1 : currentSRSLevel - 2));
                console.log(subject.characters + " - Old SRS Level: " + SRSLevelNames[currentSRSLevel] + " New SRS Level: " + SRSLevelNames[newSRSLevel]);

                // Push the subject data to the items list array
                let subjectInfoToSave = { characters: subject.characters, type: subject.type, id: subject.id, SRSUp: didSRSUp, meanings: subject.meanings?.filter(m => ["primary", "alternative"].includes(m.kind)).map(m => m.text), readings: reading, oldSRS: currentSRSLevel, newSRS: newSRSLevel, isWarning: isWarning, incorrectEntered: incorrectEntered.get(subject.id) };
                itemsList.push(subjectInfoToSave);
            }, {passive: true});

            // Add an event listener for the willShowNextQuestion event
            window.addEventListener("willShowNextQuestion", function(e) {
                // Set current question variables with event info
                currentQuestionType = e.detail.questionType;
                currentCategory = e.detail.subject.type;
                currentWord = e.detail.subject.characters;

                currentSRSLevel = quizQueueSRS.find(function(element) { return element[0] == e.detail.subject.id; });
                if(currentSRSLevel == undefined) {
                    getQuizQueueSRS();
                    currentSRSLevel = quizQueueSRS.find(function(element) { return element[0] == e.detail.subject.id; });
                    if(currentSRSLevel == undefined) currentSRSLevel = [e.detail.subject.id, 10];
                }
                currentSRSLevel = currentSRSLevel[1];
                if(currentSRSLevel == null) {
                    getQuizQueueSRS();
                    currentSRSLevel = quizQueueSRS.find(function(element) { return element[0] == e.detail.subject.id; })[1];
                    if(currentSRSLevel == null) currentSRSLevel = 10;
                }
            }, {passive: true});

            // Add an event listener for the turbo before-visit event
            window.addEventListener("turbo:before-visit", function(e) {
                // Show stats if .summary-popup is not already visible and there are items reviewed
                if (questionsAnswered > 0 && document.getElementById("summary-popup") == null) {
                    e.preventDefault();
                    setTimeout(showStatistics, 0);
                }
            });
        }

        // Home button override
        async function getHomeButton() {
            let homeButton = document.querySelector(".summary-button");
            if(!homeButton) setTimeout(getHomeButton, 500);
            else {
                homeButton.setAttribute("title", "Show statistics and return to dashboard");
                homeButton.addEventListener("click", function(e) {
                    // Show stats if .summary-popup is not already visible and there are items reviewed
                    if (questionsAnswered > 0 && document.getElementById("summary-popup") == null) {
                        e.preventDefault();
                        setTimeout(showStatistics, 0);
                    }
                });
            }
        }

        addEventListeners();
        getHomeButton();
    }
})();
1 Like

0.6.5 released - should fix the issues, let me know if not!

@Inserio Thanks! I took your fixes and additionally fixed the readings which still weren’t working (which had also been broken by the structure changes), and have pushed an update with it all.

3 Likes

Back with another heads up - the radical for “creeper” could use an update at some point (currently not inverting to white on the results screen). Continuing to love this script!