Wanikani Forums Lesson/Review Status

Shows status of your Wanikani lessons/reviews while in the forums.

当前为 2018-06-01 提交的版本,查看 最新版本

// ==UserScript==
// @name        Wanikani Forums Lesson/Review Status
// @namespace   rfindley
// @description Shows status of your Wanikani lessons/reviews while in the forums.
// @version     1.0.10
// @include     https://community.wanikani.com/*
// @require     https://greasyfork.org/scripts/27967-wanikani-forums-global-framework/code/Wanikani%20Forums%20Global%20Framework.js?version=186167
// @copyright   2017+, Robin Findley
// @license     MIT; http://opensource.org/licenses/MIT
// @run-at      document-end
// @grant       none
// ==/UserScript==

window.wkf_lrstatus = {};

(function(gobj) {

    var settings = {
        show_next_review: true,
        highlight_labels: false
    };

    var settings_url = '/scripts/lrstatus';
    var randomize_query = 300; // Randomize API query times over a 300 sec period to spread server load.
    var next_review = -1;

    //-------------------------------------------------------------------
    // Let the global script know about our settings page (if we have one)
    //-------------------------------------------------------------------
    var apikey = window.wkf_global.get_apikey();
    if (apikey === null) return; // Just in case the auto-redirect doesn't work

    // Don't run past this point if we are on a settings screen.
    if (window.location.pathname.match(/^\/scripts\//) !== null) return;

    //-------------------------------------------------------------------
    // Styling info for this script.
    //-------------------------------------------------------------------
    var css =
        '.float_wkappnav .d-header {height:inherit;}'+
        '.float_wkappnav .wanikani-app-nav-container {border-top:1px solid #ccc; line-height:2em;}'+
        '.float_wkappnav .wanikani-app-nav ul {padding-bottom:0; margin-bottom:0; border-bottom:inherit;}'+
        '.timeline-container:not(.timeline-docked) {margin-top:25px;}'+

        '.dashboard_bubble {color:#fff; background-color:#bdbdbd; font-size:0.8em; border-radius:0.5em; padding:0 6px; margin:0 0 0 4px; font-weight:bold;}'+
        'li[data-highlight="true"] .dashboard_bubble {background-color:#6cf;}'+
        'body[theme="dark"] .dashboard_bubble {color:#ddd; background-color:#646464;}'+
        'body[theme="dark"] li[data-highlight="true"] .dashboard_bubble {color:#000; background-color:#6cf;}'+
        'body[theme="dark"] .wanikani-app-nav[data-highlight-labels="true"] li[data-highlight="true"] a {color:#6cf;}'+
        '';

    //-------------------------------------------------------------------
    // Display a friendly relative time for the next review.
    //-------------------------------------------------------------------
    function update_time() {
        var timestamp = next_review;
        var nr = $('#next_review');
        if (timestamp === null) {
            nr.text('none').closest('li').attr('data-highlight','false');
            return;
        }

        var now = Math.trunc(new Date().getTime()/1000);
        var diff = Math.max(0, timestamp-now);
        var dd = Math.floor(diff / 86400);
        diff -= dd*86400;
        var hh = Math.floor(diff / 3600);
        diff -= hh*3600;
        var mm = Math.floor(diff / 60);
        diff -= mm*60;
        var ss = diff;
        var text, next_update;
        var is_now = false;

        if (dd > 0) {
            text = dd+' day'+(dd===1?'':'s')+', '+hh+' hour'+(hh===1?'':'s');
            next_update = mm*60+ss+1;
        } else if (hh > 0) {
            text = hh+' hour'+(hh===1?'':'s')+', '+mm+' min'+(mm===1?'':'s');
            next_update = ss;
        } else if (mm > 0 || ss > 15) {
            if (ss > 0) mm++;
            text = mm+' min'+(mm===1?'':'s');
            next_update = ss;
        } else {
            text = 'Now';
            next_update = -1;
            is_now = true;
        }
        nr.text(text);
        $('[data-name="next_review"]').attr('data-highlight',(is_now ? 'true' : 'false'));
        if (next_update >= 0) setTimeout(update_time, (next_update+1)*1000);
    }

    //-------------------------------------------------------------------
    // Update the lesson/review count info on the screen.
    //-------------------------------------------------------------------
    function update_counts(lessons, reviews) {
        var lc = $('#lesson_count');
        var rc = $('#review_count');
        lc.text(lessons);
        rc.text(reviews);
        $('[data-name="lesson_count"]').attr('data-highlight',(lessons > 0 ? 'true' : 'false'));
        $('[data-name="review_count"]').attr('data-highlight',(reviews > 0 ? 'true' : 'false'));
        if (settings.show_next_review === true)
            update_time();
    }

    //-------------------------------------------------------------------
    // Fetch lesson/review count info from the server.
    //-------------------------------------------------------------------
    function fetch_data() {
        var now = Math.round(new Date().getTime()/1000);
        window.wkf_global.query_api('/study-queue')
        .then(function(json){
            var lessons = json.requested_information.lessons_available;
            var reviews = json.requested_information.reviews_available;
            next_review = json.requested_information.next_review_date;
            update_counts(lessons, reviews);
        }, function(error) {
            alert('[Userscript - Wanikani Forum Lesson/Review Status]:\n\nWanikani is telling me, "'+error.message+'"\nMaybe check your API key?\nI\'ll take you to the settings page...');
            window.wkf_global.goto_settings(error.message);
            return;
        });
        var next_query = Math.trunc((now/900)+1) * 900 + Math.round(Math.random()*randomize_query) + 10 - now;
        setTimeout(fetch_data, next_query*1000);
    }

    //-------------------------------------------------------------------
    // Determine whether the user is using a dark theme.
    //-------------------------------------------------------------------
    function is_dark_theme() {
        // Grab the <html> background color, average the RGB.  If less than 50% bright, it's dark theme.
        return $('html').css('background-color').match(/\((.*)\)/)[1].split(',').slice(0,3).map(str => Number(str)).reduce((a, i) => a+i)/(255*3) < 0.5;
    }

    //-------------------------------------------------------------------
    // Startup. Runs at document 'load' event.
    //-------------------------------------------------------------------
    function startup() {

        if (is_dark_theme())
            $('body').attr('theme','dark');
        else
            $('body').attr('theme','light');

        // Attach the Dashboard menu to the stay-on-top menu.
        var wk_app_nav = $('.wanikani-app-nav').closest('.container');
        var top_menu = $('.d-header');
        var main_content = $('#main-outlet');
        $('body').addClass('float_wkappnav');
        wk_app_nav.addClass('wanikani-app-nav-container');
        top_menu.find('>.wrap > .contents:eq(0)').after(wk_app_nav);

        // Adjust the main content's top padding, so it won't be hidden under the new taller top menu.
        var main_content_toppad = Number(main_content.css('padding-top').match(/[0-9]*/)[0]);
        main_content.css('padding-top', (main_content_toppad + 25) + 'px');

        // Insert CSS.
        $('head').append('<style type="text/css">'+css+'</style>');

        // Add our content to the WK App Nav bar.
        $('.wanikani-app-nav > ul > li:contains("Lessons")').attr('data-name', 'lesson_count').attr('data-highlight','false').append('<span id="lesson_count" class="dashboard_bubble"></span>');
        $('.wanikani-app-nav > ul > li:contains("Reviews")').attr('data-name', 'review_count').attr('data-highlight','false').append('<span id="review_count" class="dashboard_bubble"></span>');
        if (settings.show_next_review === true)
            $('.wanikani-app-nav > ul').append('<li data-name="next_review" data-highlight="false"><a href="https://www.wanikani.com/review" title="Go to reviews">Next Review<span id="next_review" class="dashboard_bubble"></span></a></li>');
        $('.wanikani-app-nav').attr('data-highlight-labels', (settings.highlight_labels === true ? 'true' : 'false'));

        var now = Math.trunc(new Date().getTime()/1000);
        var last_qtr_hr = Math.trunc(now / 900) * 900;
        var last_query = Number(localStorage.getItem('wkf_lrstatus.last_query'));

        fetch_data();
    }

    // Run startup() after window.onload event.
    if (document.readyState === 'complete')
        startup();
    else
        window.addEventListener("load", startup, false);

})(window.wkf_lrstatus);