WaniKani Review Hover Details

Show extensive breakdown of review count

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          WaniKani Review Hover Details
// @namespace     https://www.wanikani.com
// @description   Show extensive breakdown of review count
// @author        Kumirei
// @version       1.1.1
// @include       /^https://(www|preview).wanikani.com*/
// @grant         none
// ==/UserScript==
/*jshint esversion: 8 */

(function(wkof) {
    let header = document.getElementsByClassName('global-header')[0];
    if (!header) return;

    // Make sure WKOF is installed
    if (!wkof) {
        var response = confirm('WaniKani Review Hover Details script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');
        if (response) {
            window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        }
        return;
    }

    // Run script
    wkof.include('Apiv2,ItemData');
    wkof.ready('Apiv2,ItemData')
        .then(fetch_data)
        .then(count_reviews)
        .then(install_element);

    // Fetches the current reviews and the item database
    function fetch_data() {
        return fetch_reviews().then(fetch_subjects).then(combine_reviews_subjects);
    }

    // Fetches the current review queue
    function fetch_reviews() {
        return wkof.Apiv2.fetch_endpoint('summary').then(reviews=>reviews.data.reviews[0].subject_ids);
    }

    // Fetches the item database
    function fetch_subjects(reviews) {
        return wkof.ItemData.get_items('assignments').then(assignments=>[reviews, assignments]);
    }

    // Retrieves info from the data base and maches with the current review items
    function combine_reviews_subjects(data) {
        let [reviews, assignments] = data;
        let items = {};
        for (let i=0; i<reviews.length; i++) items[reviews[i]] = {};
        for (let i=0; i<assignments.length; i++) {
            let ass = assignments[i];
            if (items[ass.id]) items[ass.id] = {type: ass.object.slice(0,3), srs: ["Ini", "App", "App", "App", "App", "Gur", "Gur", "Mas", "Enl", "Bur"][ass.assignments.srs_stage], level: ass.data.level};
        }
        return items;
    }

    // Find the counts that are to be presented
    function count_reviews(items) {
        let counts = {
            total: 0,
            type: {rad: 0, kan: 0, voc: 0,},
            srs: {
                App: {total: 0, rad: 0, kan: 0, voc: 0,},
                Gur: {total: 0, rad: 0, kan: 0, voc: 0,},
                Mas: {total: 0, rad: 0, kan: 0, voc: 0,},
                Enl: {total: 0, rad: 0, kan: 0, voc: 0,},
            },
            levels: {},
        };
        for (let i=1; i<=60; i++) counts.levels[i] = {total: 0, rad: 0, kan: 0, voc: 0, App: 0, Gur: 0, Mas: 0, Enl: 0,};
        for (let id in items) {
            let item = items[id];
            counts.total++;
            counts.type[item.type]++;
            counts.srs[item.srs].total++;
            counts.srs[item.srs][item.type]++;
            counts.levels[item.level].total++;
            counts.levels[item.level][item.type]++;
            counts.levels[item.level][item.srs]++;
        }
        return counts;
    }

    // Present data
    function install_element(counts) {
        install_css();
        let levels = '', level_count = 0, level_cum = 0;
        for (let i=1; i<=60; i++) {
            if (counts.levels[i].total == 0) continue;
            level_cum += counts.levels[i].total;
            levels += table_row(counts, 'levels', i, level_cum);
            level_count++;
        }
        let HTML = `
        <div id="review_hover_details" class="${level_count>=53?"smallest":level_count>=45?"smaller":level_count>=40?"small":level_count>30?"smol":""}">
            <table>
                <tr class="table-header"><th></th><th>Tot</th><th>Rad</th><th>Kan</th><th>Voc</th></tr>
                ${table_row(counts, 'srs', 'App')}
                ${table_row(counts, 'srs', 'Gur')}
                ${table_row(counts, 'srs', 'Mas')}
                ${table_row(counts, 'srs', 'Enl')}
                <tr><th>Tot</th><td>${counts.total}</td><td>${counts.type.rad}</td><td>${counts.type.kan}</td><td>${counts.type.voc}</td></tr>
            </table>
            <table>
                <tr class="table-header"><th>Lvl</th><th>Tot</th><th>Rad</th><th>Kan</th><th>Voc</th><th>App</th><th>Gur</th><th>Mas</th><th>Enl</th><th>Cum</th></tr>
                ${levels}
            </table>
        </div>
        `;
        document.getElementsByClassName('navigation-shortcut--reviews')[0].insertAdjacentHTML('beforeend', HTML);
        document.getElementsByClassName('lessons-and-reviews__reviews-button')[0].insertAdjacentHTML('beforeend', HTML);
    }

    function table_row(counts, table_type, row_type, cumulative) {
        let count = counts[table_type][row_type];
        switch (table_type) {
            case ('type'): return `<tr><th>${row_type.slice(0,1).toUpperCase()+row_type.slice(1)}</th>${table_cell(count)}</tr>`;
            case ('srs'): return `<tr><th>${row_type}</th>${table_cell(count.total) + table_cell(count.rad) + table_cell(count.kan) + table_cell(count.voc)}</tr>`;
            case ('levels'): return `<tr><th>${row_type}</th>${table_cell(count.total) + table_cell(count.rad) + table_cell(count.kan) + table_cell(count.voc) + table_cell(count.App) + table_cell(count.Gur) + table_cell(count.Mas) + table_cell(count.Enl) + table_cell(cumulative)}</tr>`;
        }
    }

    function table_cell(count) {
        return '<td data-count="'+count+'">'+count+'</td>';
    }

    function install_css() {
        document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', `
        <style id="ReviewHoverDetailsCSS">
        .navigation-shortcut--reviews {
            position: relative;
        }
        .navigation-shortcut--reviews:hover #review_hover_details,
        #review_hover_details:hover,
        .lessons-and-reviews__reviews-button:hover #review_hover_details {
            display: table;
        }
        .lessons-and-reviews__reviews-button #review_hover_details {
            top: 100%;
        }
        #review_hover_details {
            display: none;
            position: absolute;
            padding: 10px;
            border-radius: 5px;
            background-color: #4d4d4d !important;
            width: max-content;
            text-align: center;
            margin-top: 10px;
            z-index: 11;
            left: 50%;
            transform: translateX(-50%);
            box-shadow: 2px 2px rgba(0,0,0,0.3);
        }
        #review_hover_details::before {
            position: absolute;
            border-bottom: 20px solid #4d4d4d;
            border-right: 20px solid transparent;
            border-left: 20px solid transparent;
            content: " ";
            top: -10px;
            transform: translateX(-50%);
        }
        #review_hover_details table tr:nth-child(2n+3) {
            background-color: rgba(240, 240, 240, 0.15) !important;
        }
        #review_hover_details table:not(:last-child) {
            margin-bottom: 20px;
        }
        #review_hover_details table tr:first-child th {
            border-bottom: 1px solid rgb(240, 240, 240);
            color: rgb(240, 240, 240);
        }
        #review_hover_details table:first-child tr:last-child * {
            border-top: 1px solid rgb(240, 240, 240);
        }
        #review_hover_details th {
            color: rgb(240, 240, 240);
            width: 30px;
        }
        #review_hover_details td {
            color: #ddd;
        }
        #review_hover_details table tr :first-child,
        #review_hover_details table tr :nth-child(2),
        #review_hover_details table:nth-child(2) tr :nth-child(5),
        #review_hover_details table:nth-child(2) tr :nth-child(9) {
            border-right: 1px solid white;
        }
        #review_hover_details td[data-count="0"] {
            color: transparent;
        }
        #review_hover_details {
            font-size: 14px;
            line-height: 20px;
            font-weight: normal;
        }
        #review_hover_details.smol {
            line-height: 16px;
        }
        #review_hover_details.small {
            font-size: 12px;
            line-height: 14px;
        }
        #review_hover_details.smaller {
            font-size: 12px;
            line-height: 12px;
        }
        #review_hover_details.smallest {
            font-size: 12px;
            line-height: 10px;
        }
        </style>
        `);
    }
})(window.wkof);