Greasy Fork is available in English.

Wanikani: Level Duration 2

Displays the number of days you have spent on the current level.

// ==UserScript==
// @name         Wanikani: Level Duration 2
// @namespace    Wanikani: Level Duration
// @version      2.8.1
// @description  Displays the number of days you have spent on the current level.
// @author       Kumirei
// @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 that WKOF is installed
    var script_name = 'Level Duration';
    if (!window.wkof) {
        if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?')) {
            window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        }
        return;
    }
    // If it is installed run the script
    else {
        wkof.include('Menu,Settings,Apiv2');
        wkof.ready('Menu,Settings,Apiv2')
            .then(load_settings)
            .then(install_menu)
            .then(install_css)
            .then(fetch_date)
            .then(install_display);
    }

    let date, level_duration;

    //------------------------------------------------------//
    //--------------------SET UP IN WKOF--------------------//
    //------------------------------------------------------//

    function load_settings() {
        var defaults = {
            format: "long",
        };
        return wkof.Settings.load('level_duration', defaults);
    }

    function install_menu() {
        var config = {
            name: 'level_duration_settings',
            submenu: 'Settings',
            title: 'Level Duration',
            on_click: open_settings
        };
        wkof.Menu.insert_script_link(config);
    }

    function open_settings(items) {
        var config = {
            script_id: 'level_duration',
            title: 'Level Duration',
            on_save: update_display,
            content: {
                format: {
                    type: 'list',
                    label: 'Format',
                    default: 'long',
                    hover_tip: 'Select how you want the duration to display',
                    size: 6,
                    content: {
                        long: format_date('long'),
                        decimal_long: format_date('decimal_long'),
                        decimal: format_date('decimal'),
                        short: format_date('short'),
                        japanese: format_date('japanese'),
                        japanese_custom: format_date('japanese_custom') + ' (FangSong font)',
                    },
                },
            },
        };
        let dialog = new wkof.Settings(config);
        dialog.open();
    }

    //------------------------------------------------------//
    //----------------------RUN SCRIPT----------------------//
    //------------------------------------------------------//

    function install_css() {
        document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', `
<style id="LevelDurationCSS">
    .global-header {padding-bottom: 14px;}
    #level-duration {
        color: #999 !important;
        text-shadow: 0 1px 0 #ffffff;
        font-size: 14px;
        margin-top: 4px;
        line-height: 0;
        text-align: center;
    }
    #level-duration-old {
        text-align: center;
        color: #999 !important;
        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        text-shadow: 0 1px 0 #ffffff;
        margin-top: -2px;
    }
    #level-duration.japanese_custom,
    #wkof_ds #level_duration_format option[name="japanese_custom"] {
        font-family: FangSong;
    }
</style>
`);
    }

    // Fetch the level's unlock date
    function fetch_date() {
        return wkof.Apiv2.fetch_endpoint('/level_progressions').then(data => {date = data.data.pop().data.unlocked_at;});
    }

    // Installs the display element and set it up to update every 15 minutes
    function install_display() {
        level_duration = document.createElement('div');
        level_duration.id = 'level-duration';
        level_duration.innerHTML = '<span></span>';
        update_display(level_duration, date);
        let target = document.querySelector('.sitemap .sitemap__section-header[aria-controls="sitemap__levels"').parentElement;
        let old_target = document.querySelector('.dropdown.levels');
        if (old_target) { // Support for old header script
            level_duration.id = 'level-duration-old';
            target = old_target;
        }
        target.append(level_duration);
        setInterval(update_display, 1000*60*15); // Update every 15 minutes
    }

    // Clears the element and inserts new info
    function update_display() {
        let text = format_date();
        level_duration.children[0].innerText = text;
        level_duration.className = wkof.settings.level_duration.format;
    }

    // Returns the date in the user's preferred format
    function format_date(format) {
        let msday = 1000*60*60*24;
        let diff = Date.now()-Date.parse(date);
        let days = Math.floor(diff/msday);
        let hours = Math.floor(diff/msday*24%24);
        let day_text, hour_text = '';
        switch (format || wkof.settings.level_duration.format) {
            case ('long'):
                day_text = days + ' day'+(days==1?'':'s');
                hour_text = ' ' + hours + ' hour'+(hours==1?'':'s');
                if (days >= 100) hour_text = ''; // Otherwise the text gets too long
                break;
            case ('decimal_long'):
                day_text = (days+hours/24).toFixed(1) + ' days on level';
                break;
            case ('decimal'):
                day_text = (days+hours/24).toFixed(1) + ' days';
                break;
            case ('short'):
                day_text = days + 'd';
                hour_text = ' ' + hours + 'h';
                break;
            case ('japanese'):
                day_text = days + '日';
                hour_text = hours + '時';
                break;
            case ('japanese_custom'):
                day_text = days + '日';
                hour_text = hours + '時';
                break;
        }
        return day_text + hour_text;
    }
})(window.wkof);