Wanikani Burn Reviews

Adds a space on the main page that reviews random burned items. This is a maintained fork of the original script by Samuel Harbord

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Wanikani Burn Reviews
// @namespace   wkburnreviewnew
// @description Adds a space on the main page that reviews random burned items. This is a maintained fork of the original script by Samuel Harbord
// @version     2.2.6
// @author      Jonny Dark
// @license     Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0); http://creativecommons.org/licenses/by-nc/4.0/
// @include     http://www.wanikani.com/
// @include     https://www.wanikani.com/
// @include     http://www.wanikani.com/dashboard
// @include     https://www.wanikani.com/dashboard
// @require     https://greasyfork.org/scripts/19781-wanakana/code/WanaKana.js?version=126349
// @grant       none
// @require 		http://code.jquery.com/jquery-1.12.4.min.js

// Use site jquery and other variables
// Todo fix this properly to work with Greasmonkey
try {
  localStorage = unsafeWindow.localStorage;
  window = unsafeWindow;
}
catch {}

// CONSTANTS
var RADICAL   = 0;
var KANJI     = 1;
var VOCAB     = 2;

var UNDEFINED = -1;
var MEANING   = 0;
var READING   = 1;

var DEBUG   = 7;
var WARNING = 8;
var ERROR   = 9;

var LITEBLUE = "#00a0f1";
var PINK     = "#f100a0";
var PURPLE   = "#a000f1";


// Globals....ewww
var BRLoggingEnabled = (localStorage.getItem("BRLoggingEnabled") == "true");

BRData = { Radicals: [], Kanji: [], Vocab: [] };
BRConfig = { RadicalsEnabled: true, KanjiEnabled: true, VocabEnabled: true };

BRQuestion = {
    Item      : {},
    askingFor : UNDEFINED,
    itemType  : UNDEFINED,
    progress  : 0,
    answered  : false,

    IsRadical : function() { return this.itemType === RADICAL; },
    IsKanji   : function() { return this.itemType === KANJI; },
    IsVocab   : function() { return this.itemType === VOCAB; },

    IsAskingForMeaning: function() { return this.askingFor === MEANING || this.IsRadical(); },
    IsAskingForReading: function() { return this.askingFor === READING; },

    IsAnswered : function() { return this.answered; },
    SetAnswered: function(answered) { this.answered = answered; },

    GetAnswers : function() {
        if (this.IsAskingForMeaning()) {
            return this.Item.meaning;
        }
        else {
            if (BRQuestion.IsKanji()) {
                var importantReading = BRQuestion.Item.important_reading;
                return BRQuestion.Item[importantReading];
            }
            else {
                return BRQuestion.Item.kana;
            }
        }
    },

    Started    : function() { return this.progress > 0; },
    IsComplete : function() { return (this.IsRadical() && this.Started()) || this.progress >= 2; },
    Restart    : function() { this.progress = 0; },
    Skip       : function() { this.progress = 2; },
    NextPart   : function() {
        this.progress++;
        this.askingFor = this.IsAskingForMeaning() ? READING : MEANING;
    },

    DependingOnTypeUse : function (ifRadical, ifKanji, ifVocab) {
        return this.IsRadical() ? ifRadical : this.IsKanji() ? ifKanji : ifVocab;
    },

    Reset : function() {
        this.askingFor = UNDEFINED;
        this.itemType  = UNDEFINED;
        this.Item      = {};
        this.progress  = 0;
        this.answered  = false;
    }
};

function BRLog(logdata, level) {
    level = (typeof level == "undefined") ? DEBUG : level;
    if (!BRLoggingEnabled && level < WARNING) return;
    if (!console) return;

    var logmethod = console.log.bind(console);
    if (typeof level !== "undefined" && level !== DEBUG) {
        logmethod = (level == WARNING ? console.warn.bind(console) :
                     level == ERROR ? console.error.bind(console) :
                     logmethod);
    }

    logmethod("WKBurnReview: " + logdata);
    if (typeof logdata != "string") {
        logmethod(logdata);
    }
}

window.BREnableLogging = function() {
    BRLoggingEnabled = true;
    localStorage.setItem("BRLoggingEnabled", true);
};

window.BRDisableLogging = function() {
    BRLoggingEnabled = false;
    localStorage.removeItem("BRLoggingEnabled");
};

function getApiKeyThen(callback) {
    // First check if the API key is in local storage.
    var api_key = localStorage.getItem('apiKey');
    if (typeof api_key === 'string' && api_key.length === 32) {
        return callback(api_key);
    }
    else {
        // We don't have the API key.  Fetch it from the /settings/personal_access_tokens page.
      	$.get('https://www.wanikani.com/settings/personal_access_tokens')
            .done(function(page) {
                if (typeof page !== 'string') return callback(null);

                // Extract the API key.
                api_key = $(page).find('#user_api_key').val();
                if (typeof api_key == 'string' && api_key.length == 32) {
                    	// Store the updated user info.
                    	localStorage.setItem('apiKey', api_key);
                }
                else {
                    BRLog("Failed to get API key :( instead got: '" + api_key + "'", ERROR);
                }
                return callback(api_key);
            });

    }
}

function addBurnReviewStylesThen(callback) {

    if (stylesAlreadyAdded()) {
        BRLog("Styles already added, don't need to add again");
        return callback();
    }

    BRLog("Getting the review page stylesheet...");
    $.ajax({url:"https://www.wanikani.com/review", dataType:"html"}).done(
        function(data) {
            BRLog("Got the review page document. Extracting styles");
            var parser = new DOMParser();
            var reviewsdoc = parser.parseFromString(data, "text/html");
            var links = reviewsdoc.head.getElementsByTagName("link");
            for (var i = 0; i < links.length; i++)
            {
                var link = links[i];
                if (link.href.endsWith(".css"))
                {
                    BRLog("Adding " + link.outerHTML + " to document head");
                    $("head").append(link);
                }
            }
            appendExternalBurnReviewStylesheetThen(callback);
        });
}

function stylesAlreadyAdded() {
    return $('#burnReviewStyles').length > 0;
}

function appendExternalBurnReviewStylesheetThen(callback) {
    BRLog("Adding additional CSS");
    // TODO - tie query string to release version
    var cssFile = "https://cdn.rawgit.com/jonnydark/wanikani-burn/master/BurnReviews.css?v=2.2";

    $.get(cssFile, function(content) {

        $('head').append('<style id="burnReviewStyles"></style>');
        $('#burnReviewStyles').text(content);

    callback();
    });
}

// This is for dumping CSS that must be present before loading main stylesheet
function appendPriorityCSS() {
    var priorityStyles =
    '<style type="text/css">'                                                                                             +
        '.burn-review-container { float:left;}'                                                                           +
        '#loadingBR { position: relative; background-color: #d4d4d4; margin-top: 0px; padding-top: 42px; height: 99px; }' +
        '#dim-overlay { position: fixed; background-color: black; opacity: 0.75; width: 100%; height: 100%; z-index: 1; margin-top: -122px; padding-bottom: 122px; display: none; }' +
    '</style>';
    $(priorityStyles).appendTo($("head"));
}

function injectWidgetHtmlWrapper() {
        $(".low-percentage.kotoba-table-list.dashboard-sub-section").parent().wrap('<div class="col burn-review-container"></div>');
        $("<br />" + wrapperHTML() + "<!-- span4 -->").insertAfter($(".low-percentage.kotoba-table-list.dashboard-sub-section"));
        setLanguage();
}

function wrapperHTML() {
    var html =
        '<div class="">'                                                                                                                     +
            '<section class="burn-reviews kotoba-table-list dashboard-sub-section one-second-transition" style="z-index: 2; position: relative">' +
                '<h3 class="small-caps">'                                                                                                         +
                    '<span class="br-en">BURN REVIEWS</span>'                                                                                     +
                    '<span class="br-jp">焦げた復習</span>'                                                                                       +
                '</h3>'                                                                                                                           +
                '<div id="loadingBR" align="center" style=""></div>'                                                                              +
                '<div class="see-more" style="margin-top: -1px">'                                                                                 +
                    '<a href="javascript:void(0)" id="new-item" class="small-caps">'                                                              +
                        '<span class="br-en">NEW ITEM</span>'                                                                                     +
                        '<span class="br-jp">新しい項目</span>'                                                                                   +
                    '</a>'                                                                                                                        +
                '</div>'                                                                                                                          +
            '</section>'                                                                                                                          +
        '</div>';
    return html;
}

function constructBurnReviewHtml() {

    BRLog("Constructing Burn Review HTML");
    $("#user-response").attr("disabled", false).val("").focus();

    $("body").prepend('<div id="dim-overlay"></div>');
    BRLog("Overlay applied");

    var strReview =
       '<div class="answer-exception-form" id="answer-exception" align="center">'                                                                                                       +
           '<span>Answer goes here</span>'                                                                                                                                              +
        '</div>'                                                                                                                                                                        +
        '<div id="question" class="br-question">'                                                                                                                                       +
            '<div class="item-toggle-buttons">'                                                                                                                                         +
                '<div class="radicals-toggle' + ((BRConfig.RadicalsEnabled) ? ' on' : '') +'">'                                                                                         +
                    '<span lang="ja">部</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
                '<div class="kanji-toggle' + ((BRConfig.KanjiEnabled) ? ' on' : '') +'" style="padding-top: 1px">'                                                                      +
                    '<span lang="ja">漢</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
                '<div class="vocab-toggle' + ((BRConfig.VocabEnabled) ? ' on' : '') +'">'                                                                                               +
                    '<span lang="ja">語</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
            '<div class="left-side-action-buttons">'                                                                                                                                    +
                '<div class="load-button">'                                                                                                                                             +
                    '<span class="br-en" lang="ja">Load</span>'                                                                                                                         +
                    '<span class="br-jp" lang="ja">ロード</span>'                                                                                                                       +
                '</div>'                                                                                                                                                                +
                '<div class="start-button-toggle' + ((localStorage.getItem('BRStartButton') !== null) ? ' on' : '') +'">'                                                               +
                    '<span lang="ja" class="br-en">Start Button</span>'                                                                                                                 +
                    '<span lang="ja" class="br-jp">開始<br />ボタン</span>'                                                                                                             +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
            '<div class="right-side-toggle-buttons">'                                                                                                                                   +
                '<div class="toggle-language-button">'                                                                                                                                  +
                    '<span lang="ja" class="br-en">日本語</span>'                                                                                                                       +
                    '<span lang="ja" class="br-jp">English</span>'                                                                                                                      +
                '</div>'                                                                                                                                                                +
                '<div class="resize-button">'                                                                                                                                           +
                    '<span lang="ja" class="br-en">Resize</span>'                                                                                                                       +
                    '<span lang="ja" class="br-jp">拡大する</span>'                                                                                                                     +
                '</div>'                                                                                                                                                                +
                '</div>'                                                                                                                                                                +
                '<div class="review-item-container">'                                                                                                                                   +
                    '<span class="review-item" lang="ja">' + BRQuestion.Item.character +'</span>'                                                                                       +
                '</div>'                                                                                                                                                                +
                '<div id="question-type"><h1 id="question-type-text" align="center">' + getReviewTypeText() +'</h1></div>'                                                              +
                '<div id="answer-form" tabindex="10">'                                                                                                                                  +
                    '<form onSubmit="return false">'                                                                                                                                    +
                        '<fieldset>'                                                                                                                                                    +
                            '<input autocapitalize="off" autocomplete="off" autocorrect="off" id="user-response" name="user-response" placeholder="Your Response" type="text"></input>' +
                            '<button id="answer-button" type="button"><i class="icon-chevron-right"></i></button>'                                                                      +
                        '</fieldset>'                                                                                                                                                   +
                    '</form>'                                                                                                                                                           +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
        '</div>';

    BRLog(strReview);
    $(strReview).insertAfter($(".burn-reviews.kotoba-table-list.dashboard-sub-section h3"));
    setLanguage();
}

function rand(low, high) {
    return Math.floor(Math.random()*(high + 1)) + low;
}

function enableKanaInput() {
    wanakana.bind(document.getElementById('user-response'));
}

function disableKanaInput() {
    wanakana.unbind(document.getElementById('user-response'));
}

function nextQuestion() {

    BRLog("Getting burn review");

    BRQuestion.NextPart();
    BRQuestion.SetAnswered(false);

    $("#user-response").attr("disabled", false).val("").focus();
    $(".answer-exception-form").css("display", "none");

    if (BRQuestion.IsComplete()) {
        newBRItem();
        updateBRItem(true);
    }

    configureInputForEnglishOrJapanese();
    $("#question-type-text").html(getReviewTypeText());
    setLanguage(); //TODO See if there's a way to bind this to jquery.html();

    document.getElementById('user-response').value = "";
    $("#answer-form fieldset").removeClass("correct").removeClass("incorrect");

}

function getReviewTypeText() {
    var reviewTypeTextEng;
    var reviewTypeTextJp;

    if (BRQuestion.IsAskingForMeaning()) {
        reviewTypeTextEng = "Meaning";
        reviewTypeTextJp  = "意味";
    }
    else {
        if (BRQuestion.Item.important_reading == "onyomi") {
            reviewTypeTextEng = "onyomi";
            reviewTypeTextJp  = "音";
        }
        else {
            reviewTypeTextEng = "kunyomi";
            reviewTypeTextJp  = "訓";
        }
        reviewTypeTextEng += " Reading";
        reviewTypeTextJp  += "読み";
    }
    return '<span class="br-en">' + reviewTypeTextEng + '</span><span class="br-jp">' + reviewTypeTextJp + '</span>';
}

function setLanguage() {
    var langToHide = BRLangJP ? ".br-en" : ".br-jp";
    $(langToHide).addClass("br-hide");
}

function newBRItem() {
    BRLog("Getting new burn item");

    // Need to get a weighted Random
    var itemTypeArray = [];
    if (BRConfig.RadicalsEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Radicals.length).fill(RADICAL));
    }
    if (BRConfig.KanjiEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Kanji.length).fill(KANJI));
    }
    if (BRConfig.VocabEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Vocab.length).fill(VOCAB));
    }
    BRQuestion.itemType = itemTypeArray[rand(0, itemTypeArray.length)];

    var dataBank = [BRData.Radicals, BRData.Kanji, BRData.Vocab][BRQuestion.itemType];
    BRQuestion.ItemIndex = rand(0, dataBank.length - 1);

    BRQuestion.Item = dataBank[BRQuestion.ItemIndex];

    BRQuestion.askingFor = BRQuestion.IsRadical() ? MEANING : rand(MEANING, READING);

    BRQuestion.Restart();

    BRLog("Burn item type: " + BRQuestion.itemType);
    BRLog("Burn item: " + BRQuestion.Item);

}

function updateBRItem(updateText) {

    BRLog("Updating Burn review item");
    if (updateText) {
        $(".review-item").html(BRQuestion.Item.character);
        setItemFontSize();
    }

    var bg = BRQuestion.DependingOnTypeUse(LITEBLUE, PINK, PURPLE);
    var bgi = "linear-gradient(to bottom, ";

    bgi += BRQuestion.DependingOnTypeUse("#0af, #0093dd", "#f0a, #dd0093", "#a0f, #9300dd");
    $(".review-item-container").css({"background-color": bg, "background-image": bgi });
}

function setItemFontSize() {
    var itemLength = BRQuestion.Item.character.length;
    var fontSize = 48;
    switch(itemLength) {
        case 1:
        case 2:
        case 3:
            fontSize = 48;
            break;
        case 4:
            fontSize = 38;
            break;
        case 5:
            fontSize = 28;
            break;
        default:
            fontSize = 24;
            break;
    }
    $(".review-item").css("font-size", fontSize + "px");
}

function skipItem() {
   	BRQuestion.Skip();
    nextQuestion();
    return false;
}

function displayStartMessage() {
    var text = BRLangJP ? "開始" : "Start";
    $("#loadingBR").html('<a lang="ja" href="javascript:void(0)" style="font-size: 52px; color: #434343; text-decoration: none">' + text + '</a>');
}

function bindStartButtonClickEvent() {
    $("#loadingBR a").click(function() {
        startWaniKaniBurnReviews();
    });
}

function displayLoadingMessage(color, english, japanese) {
    $("#loadingBR").html('<h3 style="color:' + color + '">' + (BRLangJP ? japanese : english)  + '</h3>');
}

function displayRadicalLoadingMessage() {
    displayLoadingMessage(LITEBLUE, "Retrieving radical data...", "部首データを検索中…");
}

function displayKanjiLoadingMessage() {
    displayLoadingMessage(PINK,"Retrieving kanji data...", "漢字データを検索中…");
}

function displayVocabLoadingMessage() {
    displayLoadingMessage(PURPLE,"Retrieving vocabulary data...", "単語データを検索中…");
}


function getRadicalCharacter(radical) {
    return radical.character ? radical.character :
            "<img class=\"radical-question\" src=\"" + radical.image + "\" />";
}

function itemIsBurned(item) {
    return item.user_specific ? item.user_specific.burned : false;
}

function getMeaning(item) {
    var usyn = item.user_specific ? item.user_specific.user_synonyms : null;
    var meaning = item.meaning.split(', ');
    return usyn !== null ? meaning.concat(usyn) : meaning;
}

function fetchAndCacheBurnedRadicalsThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "radicals", "Radicals", "burnedRadicals",
        function(radical) {
            return { character : getRadicalCharacter(radical),
                     meaning   : getMeaning(radical)
            };
        });
}

function fetchAndCacheBurnedKanjiThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "kanji", "Kanji", "burnedKanji",
        function(kanji) {
            return { character         : kanji.character,
                     meaning           : getMeaning(kanji),
                     onyomi            : kanji.onyomi ? kanji.onyomi.split(", ") : null,
                     kunyomi           : kanji.kunyomi ? kanji.kunyomi.split(", ") : null,
                     important_reading : kanji.important_reading,
            };
        });
}

function fetchAndCacheBurnedVocabThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "vocabulary", "Vocab", "burnedVocab",
        function(vocab) {
            return { character : vocab.character,
                     meaning   : getMeaning(vocab),
                     kana      : vocab.kana.split(", "),
            };
        });
}

function fetchAndCacheBurnedItemsThen(callback, requestedResource, type, storageKey, mapFunction) {
    $.ajax({url:"https://www.wanikani.com/api/user/" + apiKey + "/" + requestedResource, dataType:"json"})
        .done(function(response) {
            // vocabulary for some reason has everything in a child called general, kanji and radicals do not
            var requestData = response.requested_information.general ?
                                response.requested_information.general : response.requested_information;
            var burnedItems = requestData.filter(itemIsBurned);
            BRData[type] = burnedItems.map(mapFunction);

            localStorage.setItem(storageKey, JSON.stringify(BRData[type]));
            callback();
        })
        .fail(function() {
            BRLog("Request to WaniKani API failed. Catastrophic failure ermagerd D:", ERROR);
        });
}

function maybeGetBurnedItemsThen(callback, storageKey, type, fetchFunction) {
    var RawBRData = localStorage.getItem(storageKey);
    if (RawBRData !== null) {
        try {
            BRData[type] = JSON.parse(RawBRData);
            if (BRData[type].length > 0) {
                return callback();
            }
            BRLog("No burned " + type + " in cache. Refectching...", WARNING);
        }
        catch(e) {
            BRLog("Could not parse cached radical data. Refetching...", WARNING);
        }
    }
    return fetchFunction(callback);
}


function maybeGetBurnedRadicalsThen(callback) {
    displayRadicalLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedRadicals", "Radicals", fetchAndCacheBurnedRadicalsThen);
}

function maybeGetBurnedKanjiThen(callback) {
    displayKanjiLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedKanji", "Kanji", fetchAndCacheBurnedKanjiThen);
}

function maybeGetBurnedVocabThen(callback) {
    displayVocabLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedVocab", "Vocab", fetchAndCacheBurnedVocabThen);
}

function getBurnReviewDataThen(callback) {
    BRLog("Getting WaniKana data");

    maybeGetBurnedRadicalsThen(function() {
        maybeGetBurnedKanjiThen(function() {
            maybeGetBurnedVocabThen(function() {

                BRLog("Data items { RadicalData: " + BRData.Radicals.length +
                                 "; KanjiData: " + BRData.Kanji.length +
                                 "; VocabData: " + BRData.Vocab.length + "}");
                callback();
            });
        });
    });

}

function clearBurnedItemData() {
    localStorage.removeItem("burnedRadicals");
    localStorage.removeItem("burnedKanji");
    localStorage.removeItem("burnedVocab");
    BRData.Radicals = [];
    BRData.Kanji    = [];
    BRData.Vocab    = [];
}

function confirmResurrection() {
    $(".answer-exception-form").css({"display": "block", "opacity": "0", "-webkit-transform": "translateY(20px)", "-moz-transform": "translateY(20px)"}).removeClass("animated fadeInUp");
    $(".answer-exception-form").addClass("animated fadeInUp");

    var itemTypeForUrl = BRQuestion.DependingOnTypeUse("radicals/", "kanji/", "vocabulary/");
    var resurrectionUrl = "https://www.wanikani.com/retired/" + itemTypeForUrl + BRQuestion.Item.character + "?resurrect=true";
    var resurrectionLink = '<a href="' + resurrectionUrl + '" target="_blank" class="btn btn-mini resurrect-btn" data-method="put" rel="nofollow">';

    var resurrectEng = '<div class="br-en">Are you sure you want to ' + resurrectionLink + 'Resurrect</a> the ' +
                            BRQuestion.DependingOnTypeUse("radical", "kanji item", "vocabulary item") + ' "' + BRQuestion.Item.character + '"?</div>';

    var resurrectJp  = '<div class="br-jp">' + BRQuestion.DependingOnTypeUse("部首", "漢字", "単語") + "「" + BRQuestion.Item.character  + "」を" +
                            resurrectionLink + '復活</a>する<br />本当によろしいですか?</div>';

    $(".answer-exception-form span").html(resurrectEng + resurrectJp);
    setLanguage();

    document.getElementById("answer-exception").onclick = "return false";
    return false;
}

function initBurnReviews() {

    BRLog("Initialising the Burn Review widget");

    var loadStylesAndConstructWidget = function() {
        useCache = false;
        $("#loadingBR").remove();
        addBurnReviewStylesThen(constructBurnReviewWidget);
    };

    getBurnReviewDataThen(loadStylesAndConstructWidget);

}

function constructBurnReviewWidget() {

    BRLog("Getting new burn review item");
    newBRItem();

    BRLog("Adding burn review section");
    constructBurnReviewHtml();

    $("#answer-button").click(function() {
        submitBRAnswer();
    });
    updateBRItem(false);
    configureInputForEnglishOrJapanese();

    bindMouseClickEvents();

}

function configureInputForEnglishOrJapanese() {
    if (BRQuestion.IsAskingForMeaning()) {
        disableKanaInput();
        $("#user-response").removeAttr("lang").attr("placeholder","Your Response");
        $("#question-type").addClass("meaning");
        $("#question-type").removeClass("reading");
    } else {
        enableKanaInput();
        $("#user-response").attr({lang:"ja",placeholder:"答え"});
        $("#question-type").addClass("reading");
        $("#question-type").removeClass("meaning");
    }
}

function bindMouseClickEvents() {

    bindQuestionTypeToggleButtonClickEvents();

    bindLoadButtonClickEvent();

    bindStartButtonToggleButtonClickEvent();

    bindLanguageToggleButtonClickEvent();

    bindResizeButtonClickEvent();

    bindDimOverlayClickEvent();

    bindNewItemButtonClickEvent();
}

function bindNewItemButtonClickEvent() {
    document.getElementById("new-item").onclick = skipItem;
}

function bindLanguageToggleButtonClickEvent() {
    $('.toggle-language-button').click(function() {
        switchBRLang();
    });
}

function resizeWidget() {
    if (!resizeWidget.complete) return;

    resizeWidget.complete = false;
    $('.resize-button').toggleClass("on");
    $(".burn-reviews.kotoba-table-list.dashboard-sub-section").toggleClass("scale-up");
    $("#dim-overlay").fadeToggle({
        duration: 1000,
        complete: function() { resizeWidget.complete = true; }
    });
}
resizeWidget.complete = true;

function bindResizeButtonClickEvent() {
    $('.resize-button').click(function() {
        resizeWidget();
    });
}

function bindDimOverlayClickEvent() {
    $("#dim-overlay").click(function () {
        $('.resize-button').trigger("click");
    });
}


function bindLoadButtonClickEvent() {
    $(".load-button").click(function() {
        $("#dim-overlay").remove();
        $(".burn-reviews").parent().remove();
        BRQuestion.Reset();
        queueBRAnim = false;
        allowQueueBRAnim = true;
        $(wrapperHTML()).insertAfter($(".low-percentage.kotoba-table-list.dashboard-sub-section").parent().next());
        displayStartMessage();
        clearBurnedItemData();
        initBurnReviews();
    });
}

function bindStartButtonToggleButtonClickEvent() {
    $(".start-button-toggle").click(function() {
        $(this).toggleClass("on");
        if ($(this).hasClass("on")) {
            localStorage.setItem("BRStartButton", true);
        }
        else {
            localStorage.removeItem("BRStartButton");
        }
    });
}

function bindQuestionTypeToggleButtonClickEvents() {
    bindRadicalsToggleButtonClickEvent();
    bindKanjiToggleButtonClickEvent();
    bindVocabToggleButtonClickEvent();
}

function bindVocabToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.vocab-toggle', "BRVocabEnabled", "VocabEnabled", BRQuestion.IsVocab.bind(BRQuestion));
}

function bindKanjiToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.kanji-toggle', "BRKanjiEnabled", "KanjiEnabled", BRQuestion.IsKanji.bind(BRQuestion));
}

function bindRadicalsToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.radicals-toggle', "BRRadicalsEnabled", "RadicalsEnabled", BRQuestion.IsRadical.bind(BRQuestion));
}

function bindItemToggleButtonClickEvent(cssClass, storageKey, configKey, currentQuestionIsType) {
    $('.item-toggle-buttons ' + cssClass).click(function() {
        if ($(this).hasClass("on")) {
            // Don't let last button be disabled
            if ($('.item-toggle-buttons .on').length == 1) return;

            localStorage.setItem(storageKey, false);
            BRConfig[configKey] = false;
            if (currentQuestionIsType()) {
                skipItem();
            }
        }
        else {
            localStorage.removeItem(storageKey);
            BRConfig[configKey] = true;
        }
        $(this).toggleClass("on");
    });
}

function switchBRLang() {
    if (!BRLangJP) {
        localStorage.setItem("BRLangJP", true);
    }
    else {
        localStorage.removeItem("BRLangJP");
    }
    BRLangJP = !BRLangJP;

    $('.br-en,.br-jp').toggleClass('br-hide');
}

function checkBurnReviewAnswer() {
    BRLog("Checking answer");
    var response = $("#user-response").val().toLowerCase().trim();
    response = BRQuestion.IsAskingForReading() ? addTerminalN(response) : response;
    var answers = BRQuestion.GetAnswers();
    var answerIsCorrect = isAnswerCorrect(response, answers);

    $("#answer-form").focus(); // fix for FF to stop focus being trapped
    $("#user-response").attr("disabled", true);

    if (responseIsValid(response)) {
        if (isIncorrectReading(response, answerIsCorrect)) {
            shakeAnswerForm();
            displayIncorrectReadingMessage();
            $("#user-response").attr("disabled", false).focus();
        }
        else {
            if (answerIsCorrect) {
                onCorrectAnswer(response);
            } else {
                onIncorrectAnswer(response);
            }
            BRQuestion.SetAnswered(true);
    	}
    } else {
        shakeAnswerForm();
        $("#user-response").attr("disabled", false).focus();
    }
}

function addTerminalN(str = '') {
  return /n/i.test(str.slice(-1)) ? `${str.slice(0, -1)}ん` : str;
}

function isAnswerCorrect(response, answers) {
    for (var a = 0; a < answers.length; a++) {
        if (response == answers[a]) {
            return true;
        }
    }
    return false;
}

function shakeAnswerForm() {
    $('#answer-form').addClass('shake')
                     .delay(1000)
                     .queue(function(next) {
                        $(this).removeClass('shake');
                        next();
                     });
}

function setInputValue(text) {
    $("#user-response").val(text);
}

function onCorrectAnswer(answer) {
    $("#answer-form fieldset").removeClass("incorrect").addClass("correct");
    setInputValue(answer);
}

function onIncorrectAnswer(answer) {
    $("#answer-form fieldset").removeClass("correct").addClass("incorrect");
    setInputValue(answer);
    displayIncorrectAnswerMessage();
}

function responseIsValid(response) {
    return ((BRQuestion.IsAskingForMeaning() && isAsciiPresent(response)) ||
            (!isAsciiPresent(response) && BRQuestion.IsAskingForReading())) && response !== "";
}

function displayIncorrectAnswerMessage() {
    // concat in to string of comma-separated answers
    var answerList = BRQuestion.GetAnswers().join(", ");
    var resurrectButton = '<a href="#" class="btn btn-mini resurrect-btn">';

    var answerTextEng = '<div class="br-en">The answer was:<br />"' + answerList + '"<br />' + resurrectButton + 'Resurrect</a> this item?</div>';
    var answerTextJp  = '<div class="br-jp">解答は<br />「' + answerList + '」であった。<br />この項目を' + resurrectButton + '復活</a>したいか?</div>';
    $('.answer-exception-form span').html(answerTextEng + answerTextJp);
    setLanguage();

    $(".answer-exception-form").css({"display": "block"}).addClass("animated fadeInUp");
    $('.resurrect-btn').on('click', confirmResurrection);
}

function isIncorrectReading(response, answerIsCorrect) {
    return (!answerIsCorrect && BRQuestion.IsKanji() && BRQuestion.IsAskingForReading() && ((BRQuestion.Item.important_reading == "onyomi" &&
       		compareKunyomiReading(response, BRQuestion.Item.kunyomi)) || (BRQuestion.Item.important_reading == "kunyomi" && response == BRQuestion.Item.onyomi)));
}

function displayIncorrectReadingMessage() {
    var incorrectReadingText = '<div class="br-en">Oops! You entered the wrong reading.</div>' +
                               '<div class="br-jp">おっと、異なる読みを入力してしまった。</div>';
    $(".answer-exception-form span").html(incorrectReadingText);
    setLanguage();

    $(".answer-exception-form").css({"display": "block"}).addClass("animated fadeInUp").delay(5000).queue(function(){
        $(this).addClass("fadeOut").dequeue().delay(800).queue(function(){
            $(this).removeClass("fadeOut").css("display", "none").dequeue();
        });
    });
}

function compareKunyomiReading(input, reading) {
    return (input == reading || input == reading.toString().substring(0, reading.indexOf(".")) || input == reading.toString().replace("*", input.substring(reading.indexOf(".") + 1)).replace(".", ""));
}

function submitBRAnswer() {
    if (!BRQuestion.IsAnswered()) {
        checkBurnReviewAnswer();
    }
    else {
        nextQuestion();
    }
}

function isAsciiPresent(e){
    return (BRQuestion.IsAskingForMeaning()) ? !/[^a-z \-0-9]/i.test(e) : /[^ぁ-ー0-9 ]/.test(e);
}

function startWaniKaniBurnReviews() {
    if (!useCache) {
        clearBurnedItemData();
    }
    BRLog("Loading...");

    var checkReady = setInterval(function() {
        BRLog("Checking for wanakana...");
        if (wanakana !== undefined) {
            clearInterval(checkReady);
            initBurnReviews();
        }
    }, 250);
}

function pageIsDashboard() {
    return $('title').text().search("Dashboard") > 0;
}

function main() {

    if (!pageIsDashboard()) {
        BRLog("Script not running on dashboard, exiting...");
        return;
    }


    getApiKeyThen(function(key) {

        if (key === null) {
            BRLog("Couldn't fetch API key. It's all gone Pete Tong. Cannot continue ;__;", ERROR);
            return;
        }

        apiKey = key; //global
        BRLog("Running!");

        useCache            =  !(localStorage.getItem("burnedRadicals") === null || localStorage.getItem("burnedKanji") === null || localStorage.getItem("burnedVocab") === null);
        BRIsChrome          =  (navigator.userAgent.toLowerCase().indexOf('chrome') > -1);
        BRQuestion.Reset();
        queueBRAnim              =  false;
        allowQueueBRAnim         =  true;
        BRLangJP                 =  (localStorage.getItem("BRLangJP") == "true");
        BRConfig.RadicalsEnabled =  (localStorage.getItem("BRRadicalsEnabled") != "false");
        BRConfig.KanjiEnabled    =  (localStorage.getItem("BRKanjiEnabled") != "false");
        BRConfig.VocabEnabled    =  (localStorage.getItem("BRVocabEnabled") != "false");


        String.prototype.trim = function() {
            return(this.replace(/^ +/,'').replace(/ +$/,''));
        };

        appendPriorityCSS();
        injectWidgetHtmlWrapper();

        displayStartMessage();
        bindStartButtonClickEvent();

        function submitOnEnterPress(event) {
            const isEnterKey = event.keyCode == 13;
            const isNotWkSearchInput = !event.target.matches('#query.search-query');
            if(isNotWkSearchInput && isEnterKey) {
                BRLog("User pressed Enter");
                submitBRAnswer();
            }
         }

        $(document).unbind('keypress', submitOnEnterPress).bind('keypress', submitOnEnterPress);

        if (localStorage.getItem("BRStartButton") === null)
        {
            startWaniKaniBurnReviews();
        }
    });
}

if (document.readyState === 'complete')
    main();
else
    window.addEventListener("load", main, false);

// ==/UserScript==