Wanikani Self-Study Plus

Adds an option to add and review your own custom vocabulary

当前为 2014-10-28 提交的版本,查看 最新版本

// ==UserScript==
// @name        Wanikani Self-Study Plus
// @namespace   wkselfstudyplus
// @description Adds an option to add and review your own custom vocabulary
// @include     *.wanikani.com/*
// @exclude	*.wanikani.com
// @include     *.wanikani.com/dashboard*
// @include     *.wanikani.com/community*
// @version     0.1.1
// @author      shudouken and Ethan
// require     https://raw.github.com/WaniKani/WanaKana/master/lib/wanakana.min.js
// @grant       none
// ==/UserScript==

/*
 *  This script is licensed under the Creative Commons License
 *  "Attribution-NonCommercial 3.0 Unported"
 *  
 *  More information at:
 *  http://creativecommons.org/licenses/by-nc/3.0/
 */

/* Original script source:
http://userscripts-mirror.org/scripts/show/381435
*/

//shut up JSHint
/* jshint multistr: true , jquery: true, indent:2 */
/* global window, wanakana, Storage, XDomainRequest */

var APIkey = "YOUR_API_HERE";
var lockDB = true; //Set to false to unlock Kanji is not available on WaniKani (ie. not returned by API)
var reverse = true; //Include English to ひらがな reading reviews

/*
 *  Debugging
 */

var debugging = true;

var scriptLog = debugging ? function (msg) {
    if (typeof msg === 'string') {
        window.console.log("WKSS: " + msg);
    } else {
        window.console.log(msg);
    }
} : function () {
};

/*
 *  Settings and constants
 */

///###############################################
// Config for window sizes in pixels

// add Window, standard 300 x 300
var addWindowHeight = 300;
var addWindowWidth = 300;

// export and import Window, standard 275 x 390
var exportImportWindowHeight = 275;
var exportImportWindowWidth = 390;

// edit Window, standard 380 x 800
var editWindowHeight = 380;
var editWindowWidth = 800;

// study(review) Window, standard 380 x 600
var studyWindowHeight = 380;
var studyWindowWidth = 600;

// result Window, standard 500 x 700
var resultWindowHeight = 500;
var resultWindowWidth = 700;

// padding from top, standard -150
//var padding = 150;

///###############################################

var errorAllowance = 4; //every x letters, you can make one mistake when entering the meaning
var mstohour = 3600000;

//srs 4h, 8h, 24h, 3d (guru), 1w, 2w (master), 1m (enlightened), 4m (burned)
var srslevels = [];
srslevels.push("Apprentice");
srslevels.push("Apprentice");
srslevels.push("Apprentice");
srslevels.push("Apprentice");
srslevels.push("Guru");
srslevels.push("Guru");
srslevels.push("Master");
srslevels.push("Enlightened");
srslevels.push("Burned");

var srsintervals = [];
srsintervals.push(0);
srsintervals.push(14400000);
srsintervals.push(28800000);
srsintervals.push(86400000);
srsintervals.push(259200000);
srsintervals.push(604800000);
srsintervals.push(1209600000);
srsintervals.push(2628000000);
srsintervals.push(10512000000);

//GM_addStyle shim for compatibility
function GM_addStyle(CssString){
//get DOM head
  var head = document.getElementsByTagName('head')[0];
  if (head) {
//build style tag
    var style = document.createElement('style');
    style.setAttribute('type', 'text/css');
    style.textContent = CssString;
//insert DOM style into head
    head.appendChild(style);
  }
}

/*
 *  JQuery fixes
 */
$("[placeholder]").focus(function () {
    var input = $(this);
    if (input.val() == input.attr("placeholder")) {
        input.val("''");
        input.removeClass("'placeholder'");
    }
}).blur(function () {
        var input = $(this);
        if (input.val() == "''" || input.val() == input.attr("placeholder")) {
            input.addClass("placeholder");
            input.val(input.attr("placeholder"));
        }
    }).blur();

$("[placeholder]").parents("form").submit(function () {
    $(this).find("[placeholder]").each(function () {
        var input = $(this);
        if (input.val() == input.attr("placeholder")) {
            input.val("");
        }
    });
});

/*
* populate reviews when menu button pressed
*/

window.generateReviewList = function() {
  //if menu is invisible, it is about to be visible
  if ( $("#WKSS_dropdown").is(":hidden") ){
    //This is really the only time it needs to run
    //unless we want to start updating in realtime by keeping track of the soonest item
    generateReviewList();
  }
};

/*
 *  Add Item
 */
// event function to open "add window" and close any other window that might be open at the time. 
window.WKSS_add = function () {
  //show the add window
  $("#add").show();
  //hide other windows
  $("#export").hide();
  $("#import").hide();
  $("#edit").hide();
  $("#selfstudy").hide();
};

//'add window' html text
var addHtml = '\n\
<div id="add" class="WKSS">\n\
    <form id="addForm">\n\
        <button id="AddCloseBtn" class="wkss-close" type="reset"><i class="icon-remove"></i></button>\n\
    <h1>Add a new Item</h1>\n\
        <input type="text" id="addKanji" placeholder="Enter 漢字, ひらがな or カタカナ">\n\
        <input type="text" id="addReading" title="Leave empty to add vocabulary like する (to do)" placeholder="Enter reading">\n\
        <input type="text" id="addMeaning" placeholder="Enter meaning">\n\
        \n\
        <p id="addStatus">Ready to add..</p>\n\
        <button id="AddItemBtn" type="button">Add new Item</button>\n\
    </form>\n\
    </div>\n\
    ';

//add html to page source
$("body").append(addHtml);

//hide add window ("div add" code that was just appended)
$("#add").hide();

//function to fire on click event for "Add new Item"
$("#AddItemBtn").click(function () {
    var kanji = $("#addKanji").val().toLowerCase();
    var reading = $("#addReading").val().toLowerCase().split(/[,、]+\s*/); //split at , or 、followed by 0 or any number of spaces
    var meaning = $("#addMeaning").val().toLowerCase().split(/[,、]+\s*/);
    var success = false; //initalise values
    var meanlen = 0;
    for(var i = 0; i < meaning.length; i++) {
        meanlen += meaning[i].length;
    }
    
    //input is invalid: prompt user for valid input
    var item = {};
    if (kanji.length === 0 || meanlen === 0) {
        $("#addStatus").text("One or more required fields are empty!");
        if (kanji.length === 0) {
            $("#addKanji").addClass("error");
        }
        else {
            $("#addKanji").removeClass("error");
        }
        if (meanlen === 0) {
            $("#addMeaning").addClass("error");
        }
        else {
            $("#addMeaning").removeClass("error");
        }
    }
    else {
      scriptLog("building item: "+kanji);
        item.kanji = kanji;
        item.reading = reading; //optional
        item.meaning = meaning;
  
      //--preserve data integrity by combining item and srsitem
        item.level = 0;
        item.date = Date.now();
		    item.manualLock = "";
		    item = setLocks(item);
	
        success = true;
      scriptLog("item is valid");
    }
    
    //on successful creation of item
    if (success) {
        //clear error layout to required fields
        $("#addKanji").removeClass("error");
        $("#addMeaning").removeClass("error");

      
      /*
      commenting out code here, 'add' should just override existing kanji now
      */
      
      //if there are already user items, retrieve vocabList
       // var vocabList = [];
         
      
        //check stored user items for duplicates ****************** to do: option for editing duplicate item with new input
      //  if(checkForDuplicates(vocabList,item)) {
        //    $("#addStatus").text("Duplicate Item detected!");
          //  $("#addKanji").addClass("error");
            //return;
   //     }
        
      scriptLog("starting setVocList"+item);
setVocList(item);
      scriptLog("finished setVocList");
      scriptLog("clear form");
      $("#addForm")[0].reset();

      
      //--------------------------------------------------------------------------------------------------------
        if (item.manualLock === "yes" || item.manualLock === "DB" && lockDB){
            $("#addStatus").html("<i class=\"icon-lock\"></i> Added locked item");
        }else{
            $("#addStatus").html("<i class=\"icon-unlock\"></i>Added successfully");
        }
//--------------------------------------------------------------------------------------------------------
    }
});

$("#AddCloseBtn").click(function () {
    $("#add").hide();
    $("#addForm")[0].reset();
    $("#addStatus").text('Ready to add..');
    $("#addKanji").removeClass("error");
    $("#addMeaning").removeClass("error");
});



//---Function wrappers to facilitate use of one localstorage array
//---Maintains data integrity between previously two (vocab and srs)
function getSrsList(){
  var srsList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  if(srsList){
    for(var s=0;s<srsList.length;s++){
      
      srsList[s].i = s;
      delete srsList[s].meaning;
      delete srsList[s].reading;
    }
  }
  scriptLog("getSrsList: "+JSON.stringify(srsList));
  return srsList;
}

function setSrsList(srsitem){
  //looks for the kanji in the vocablist, pulls it out, replaces some properties and puts it back in

// this will need some major optimisation/sorting, perhaps version 0.1.0?
  
  // find kanji in 'Vocab-List'
  var srsList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  if(srsList){
    /*let's try something---
    for(var s=0;s<srsList.length;s++){
      scriptLog("comparing "+ srsList[s].kanji +" to "+ srsitem.kanji);
      if (srsList[s].kanji === srsitem.kanji){
      scriptLog("found match");
        //add date, level, locked and manualLock to item
        srsList[s].date = srsitem.date;
        srsList[s].level = srsitem.level;
        srsList[s].locked = srsitem.locked;
        srsList[s].manualLock = srsitem.manualLock;
        break;//item found lets get out of here.
      }else {
      }*/
    
    if(srsList[srsitem.i].kanji===srsitem.kanji){
      scriptLog("success: "+srsitem.kanji+" found at index "+ srsitem.i);
      srsList[srsitem.i].date = srsitem.date;
      srsList[srsitem.i].level = srsitem.level;
      srsList[srsitem.i].locked = srsitem.locked;
      srsList[srsitem.i].manualLock = srsitem.manualLock;
    }else{
      scriptLog("SRS Kanji not found in vocablist, needs work");
      
    }
    scriptLog("item: "+JSON.stringify(srsitem));
    localStorage.setItem('User-Vocab', JSON.stringify(srsList));
  }
}

function getVocList(){
  var vocList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  if (vocList){
    for(var v=0;v<vocList.length;v++){
      delete vocList[v].date;
      delete vocList[v].level;
      delete vocList[v].locked;
      delete vocList[v].manualLock;
    }
  }else{
    //return empty if null
    vocList = [];
  }
  scriptLog("getVocList: "+JSON.stringify(vocList));
  return vocList;
}

function setVocList(item){
  // find kanji in 'Vocab-List'
  var found = false;
  var vocList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  if (vocList){
    for(var v=0;v<vocList.length;v++){
      if (vocList[v].kanji === item.kanji){
        found = true;
        scriptLog("duplicate found, skipping item (give options in future)");

        //add meaning and reading to existing item
//        vocList[v].meaning = item.meaning;
  //      vocList[v].reading = item.reading;
      }
    }
    if (!found) {
      scriptLog("Kanji not found in vocablist, adding now");
      vocList.push(item);
    }
  }
  else{
    scriptLog("vocablist not found, creating now");
    vocList = [];
    vocList.push(item);
  }
  scriptLog("setVocList: "+JSON.stringify(vocList));
  scriptLog("putting vocList in storage");
  localStorage.setItem('User-Vocab', JSON.stringify(vocList));
}

function getFullList(){
  var fullList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  if(!fullList){
    fullList=[];
  }
  return fullList;
}



//checks if an item is present in a list
function checkForDuplicates(list, item) {
    scriptLog("Check for dupes with:" + item.kanji);

    for(var i = 0; i < list.length; i++) {
list[i].i = i; //set index property for quick lookup
      if(list[i].kanji == item.kanji)
          
            return true;
    }
    return false;
}

//manages .locked property of srsitem
/*This function manages the .locked and manualLock properties of srsitem
.locked is a real time evaluation of the item (is any of the kanji in the word locked?)
.manualLock will return 'no' if .locked has ever returned 'no'.
This is to stop items being locked again after they have been unlocked if any
of the kanji used falls below the unlock threshold
(eg. if the 勉 in 勉強 falls back to apprentice, we do not want to lock up 勉強 again.)
*/
function setLocks(srsitem){
  //functions:
  //    isKanjiLocked(srsitem)
  
  var kanjiList = jQuery.parseJSON(localStorage.getItem('User-KanjiList'));
  
  srsitem.locked = isKanjiLocked(srsitem, kanjiList);
  //seems to be returning "DB" all the time
  
  
  //once manualLock is "no" it stays "no"
  //this is to stop an item from locking up again if
  //any of the component kanji fall below 'guru'
  if (srsitem.manualLock !== "no"){
    srsitem.manualLock = srsitem.locked;
  }

  scriptLog("setting locks for "+ srsitem.kanji +": locked: "+srsitem.locked+", manualLock: "+ srsitem.manualLock);

  return srsitem;
}

function isKanjiLocked(srsitem, kanjiList){
  //scriptLog("isKanjiLocked(srsitem, kanjiList)");
  //functions:
  //    getCompKanji(srsitem.kanji, kanjiList)
  
  //item unlocked by default
  //may have no kanji, only unlocked kanji will get through the code unflagged
  
  var locked = "no"; 
  scriptLog("initialise 'locked': "+ locked);
  
	//get the kanji characters in the word.
  var componentList = getCompKanji(srsitem.kanji, kanjiList);
	// eg: componentList = getCompKanji("折り紙", kanjiList);
	// componentList = [{"kanji": "折", "srs": "guru"}, {"kanji": "紙", "srs": "apprentice"}]
  //scriptLog("isKanjiLocked -> kanjiList = "+JSON.stringify(kanjiList));
  scriptLog("components: "+JSON.stringify(componentList));

    for (var i=0; i < componentList.length; i++){

      //look for locked kanji in list
      if (componentList[i].srs == "apprentice" || componentList[i].srs == "noServerResp"){

        //----could be apprentice etc. 
        //Simple: lock is 'yes'
        locked = "yes";
        // "yes":	item will be locked while there is no database connection.
        //			if the server response indicates that it has been unlocked, only then will it be available for review

        scriptLog("test srs for apprentice etc. 'locked': "+ locked);
  
        scriptLog(componentList[i].kanji +": "+componentList[i].srs +" -> "+ locked);

        break; // as soon as one kanji is locked, the whole item is locked
      }

      //DB locks get special state
      if (componentList[i].srs == "noMatchWK" || componentList[i].srs == "noMatchGuppy"){

        locked = "DB";
        //"DB"	: database limitations, one of two things
        //a. the kanji isn't in the database and the user is a guppy --could change if user subscribes or first two levels change/expand
        //b. the kanji isn't in the database and the user is a turtle --could change if more kanji added.

        scriptLog("test srs for unmatched kanji. 'locked': "+ locked);
  
        scriptLog(componentList[i].kanji +": "+componentList[i].srs +" -> "+ locked);
  
        scriptLog(kanjiList);
		
      }

  } //for char in componentList
scriptLog("out of character loop");
  //locked will be either "yes","no", or "DB"
  return locked;
}
//--------

/*
 *  Edit Items
 */
window.WKSS_edit = function () {
    generateEditOptions();
    $("#edit").show();
    //hide other windows
    $("#export").hide();
    $("#import").hide();
    $("#add").hide();
    $("#selfstudy").hide();
};

$("body").append("                                                          \
    <div id=\"edit\" class=\"WKSS\">                                               \
    <form id=\"editForm\">                                                                    \
        <button id=\"EditCloseBtn\" class=\"wkss-close\" type=\"button\"><i class=\"icon-remove\"></i></button>\
    <h1>Edit your Vocab</h1>                                                \
        <select id=\"editWindow\" size=\"8\"></select>\
        <input type=\"text\" id=\"editItem\" name=\"\" size=\"40\" placeholder=\"Select vocab, click edit, change and save!\">\
         \
        <p id=\"editStatus\">Ready to edit..</p>\
        <button id=\"EditEditBtn\" type=\"button\">Edit</button>\
        <button id=\"EditSaveBtn\" type=\"button\">Save</button>         \
        <button id=\"EditDeleteBtn\" type=\"button\" title=\"Delete selected item\">Delete</button>         \
        <button id=\"EditDeleteAllBtn\" type=\"button\" title=\"本当にやるの?\">Delete All</button>   \
        <button id=\"ResetLevelsBtn\" type=\"button\">Reset levels</button>         \
    </form>                                                                   \
    </div>"
);
$("#edit").hide();

$("#ResetLevelsBtn").click(function () {
  var srslist = getSrsList();
  if (srslist) {
    for (var i = 0; i < srslist.length; i++){
      srslist[i].level = 0;
    
            setSrsList(srslist[i]);
    }
  }
});


$("#EditEditBtn").click(function () {
  //get handle for 'select' area
  var select = document.getElementById("editWindow");
  //get the index for the currently selected item
  var index = select.options[select.selectedIndex].value;
  var vocabList = getVocList();

    document.getElementById("editItem").value = JSON.stringify(vocabList[index]);
    document.getElementById("editItem").name = index; //using name to save the index
    $("#editStatus").text('Loaded item to edit');
});

$("#EditSaveBtn").click(function () {
    if ($("#editItem").val().length !== 0) {
        try {
            var index = document.getElementById("editItem").name;
            var item = JSON.parse(document.getElementById("editItem").value.toLowerCase());

            var fullList = getFullList();
scriptLog(JSON.stringify(fullList));
          
            if ((!checkItem(item)) || (checkForDuplicates(fullList,item) && fullList[index].kanji !== item.kanji)) {
                $("#editStatus").text('Invalid item or duplicate!');
                return;
            }
            
            var srslist = getSrsList();

            fullList[index] = item;

          
            fullList[index].date = srslist[index].date;
            fullList[index].level = srslist[index].level;
            fullList[index].locked = srslist[index].locked;
            fullList[index].manualLock = srslist[index].manualLock;
          

       
            localStorage.setItem('User-Vocab', JSON.stringify(fullList));

            generateEditOptions();
            $("#editStatus").text('Saved changes!');
            document.getElementById("editItem").value = "";
            document.getElementById("editItem").name = "";
        }
        catch (e) {
            $("#editStatus").text(e);
        }
    }
});

$("#EditDeleteBtn").click(function () {
    //select options element window
    var select = document.getElementById("editWindow");

    //index of selected item
    var item = select.options[select.selectedIndex].value;

    //fetch JSON strings from storage and convert them into Javascript literals
    var vocabList = getFullList();
    
    //starting at selected index, remove 1 entry (the selected index).
    if (item > -1) {
      if (vocabList !== null){
        vocabList.splice(item, 1);
      }
    }

  //yuck
    if (vocabList.length !== 0) {
        localStorage.setItem('User-Vocab', JSON.stringify(vocabList));
    }
    else {
        localStorage.removeItem('User-Vocab');
    }

updateEditGUI();

    $("#editStatus").text('Item deleted!');
});

function updateEditGUI(){

    generateEditOptions();
    document.getElementById("editItem").value = "";
    document.getElementById("editItem").name = "";

}

$("#EditDeleteAllBtn").click(function () {
    var deleteAll = confirm("Are you sure you want to delete all entries?");
    if (deleteAll) {
    
        //drop local storage
    	localStorage.removeItem('User-Vocab');
    
        
		updateEditGUI();

	    $("#editStatus").text('All items deleted!');
    }
});


$("#EditCloseBtn").click(function () {
    $("#edit").hide();
    $("#editForm")[0].reset();
    $("#editStatus").text('Ready to edit..');
});

//retrieve values from storage to populate 'select' menu
function generateEditOptions() {
    var select = document.getElementById('editWindow');
    
	//clear the menu (blank slate)
    while (select.firstChild) {
        select.removeChild(select.firstChild);
    }

    //check for items to add
    if (localStorage.getItem('User-Vocab')) {

        //retrieve from local storage
        var vocabList = getVocList();
        var srslist =  getSrsList();

        //build option string
        for (var i = 0; i < vocabList.length; i++) {
            //form element to save string
            var opt = document.createElement('option');
            //dynamic components of string
            	//how long since the item was created
            var dif = Date.now() - srslist[i].date;
                //how much time required for this level
            var hour = srsintervals[srslist[i].level];
            var review = "";
            
            //no future reviews if burned
            if(srslist[i].level == 8) {
                review = "Never";
            }
            
            //calculate next relative review time
            		//more time has elapsed than required for the level
            else if(hour <= dif) {
                
                review = "Now" ;
            } else {
              review = ms2str(hour-dif);            
            }//end if review is not 'never' or 'now'
            
            var text = vocabList[i].kanji + " & " + vocabList[i].reading + " & " + vocabList[i].meaning + " (" + srslevels[srslist[i].level] + " - Review: " + review + ") Locked: " + srslist[i].manualLock;
            opt.value = i;
            opt.innerHTML = text;
            select.appendChild(opt);
        }
    }
}

function ms2str(milliseconds){
  var num; //number of months weeks hours etc
  //more time has elapsed than required for the level
  if(milliseconds <= 0) {
    return "Now" ;
  }
  if(milliseconds > 2628000000) {//About a month
    num = Math.floor(milliseconds/2628000000).toString()+" month";
    if (num !== "1 month"){
      return num+"s";
    }else{
      return num;
    }
  }
  if(milliseconds > 604800000) {//A week
    num = Math.floor(milliseconds/604800000).toString()+" week";
    if (num !== "1 week"){
      return num+"s";
    }else{
      return num;
    }
  }
  if(milliseconds > 86400000) {//A day
    num = Math.floor(milliseconds/604800000).toString()+" day";
    if (num !== "1 day"){
      return num+"s";
    }else{
      return num;
    }
  }
  if(milliseconds > 3600000) {//An hour
    num = Math.floor(milliseconds/3600000).toString()+" hour";
    if (num !== "1 hour"){
      return num+"s";
    }else{
      return num;
    }
  }
  if(milliseconds > 60000) {//A minute
    num = Math.floor(milliseconds/60000).toString()+" minute";
    if (num !== "1 minute"){
      return num+"s";
    }else{
      return num;
    }
  }
  if(milliseconds > 1000) {//A second
    num = Math.floor(milliseconds/1000).toString()+" second";
    if (num !== "1 second"){
      return num+"s";
    }else{
      return num;
    }
  }
}

/*
 *  Export
 */
window.WKSS_export = function () {
    $("#export").show();
    //hide other windows
    $("#add").hide();
    $("#import").hide();
    $("#edit").hide();
    $("#selfstudy").hide();
};

$("body").append('                                                          \
    <div id="export" class="WKSS">                                               \
    <form id="exportForm">                                                                    \
        <button id="ExportCloseBtn" class="wkss-close" type="button"><i class="icon-remove"></i></button>\
    <h1>Export Items</h1>                                                \
        <textarea cols="50" rows="18" id="exportArea" placeholder="Export your stuff! Sharing is caring ;)"></textarea>                           \
         \
        <p id="exportStatus">Ready to export..</p>                                        \
        <button id="ExportItemsBtn" type="button">Export Items</button>\
        <button id="ExportSelectAllBtn" type="button">Select All</button>\
    </form>                                                                   \
    </div>'
);
$("#export").hide();


$("#ExportItemsBtn").click(function () {

    if (localStorage.getItem('User-Vocab')) {
        $("#exportForm")[0].reset();
        var vocabList = getVocList();
        $("#exportArea").text(JSON.stringify(vocabList));
        $("#exportStatus").text("Copy this text and share it with others!");
    }
    else {
        $("#exportStatus").text("Nothing to export yet :(");
    }
});

$("#ExportSelectAllBtn").click(function () {
    if ($("#exportArea").val().length !== 0) {
        select_all("exportArea");
        $("#exportStatus").text("Don't forget to CTRL + C!");
    }
});

$("#ExportCloseBtn").click(function () {
    $("#export").hide();
    $("#exportForm")[0].reset();
    $("#exportArea").text("");
    $("#exportStatus").text('Ready to export..');
});

/*
 *  Import
 */
window.WKSS_import = function () {
    $("#import").show();
    //hide other windows
    $("#add").hide();
    $("#export").hide();
    $("#edit").hide();
    $("#selfstudy").hide();
};

$("body").append('                                                          \
    <div id="import" class="WKSS">                                               \
    <form id="importForm">                                                                    \
<button id="ImportCloseBtn" class="wkss-close" type="reset"><i class="icon-remove"></i></button>\
<h1>Import Items</h1>\
        <textarea cols="50" rows="18" id="importArea" placeholder="Paste your stuff and hit the import button! Use with caution!"></textarea>                     \
         \
        <p id="importStatus">Ready to import..</p>                                        \
        <label class="button" id="ImportItemsBtn" style="display:inline;">Import Items</label>\
\
        <label id="ImportCsvBtn" class="button" style="display:inline;cursor: pointer;">Import CSV         \
\
https://www.wanikani.com/api/user/fbe9263761d278b4bedd28ddf3696041/vocabulary/1,2,3,4,5,6\
\
                 <input type="file" id="upload" accept=".csv,.tsv" style="height:0px;width:0px;background:red;opacity:0;filter:opacity(1);" />\
		</label>\
    </form>                                                                   \
    </div>                                                                    '
);
$("#import").hide();
        
function fileUpload (ev){
  var csvHeader = true;        //first row contains stuff like "Kanji/Vocab, Reading, Meaning" etc
  var tsvfile;          //tabs separate fields, commas seperate values? or false for vice versa
  var CSVs = ev.target.files;
  var name =CSVs[0].name;
  var colsplit, vsplit;
  if (name.substr(name.lastIndexOf("."),4)===".csv"){
    tsvfile = false;
    colsplit = ",";
    vsplit = "\t";
  }else{
    tsvfile = true;
    colsplit = "\t";
    vsplit = ",";
  }
    
  scriptLog("tsvfile: "+tsvfile.toString());
  scriptLog("file uploaded: "+CSVs[0].name);
  var reader = new FileReader();
  reader.readAsText(CSVs[0]);
  reader.onload = function(ev){
    var csvString = ev.target.result;
    var csvRow = csvString.split("\n");
    //default column rows
    var k = 0;
    var r = 1;
    var m = 2;
    
    var i = csvRow.length;
    //process header, changing k,r,m if necessary       
    var JSONimport = [];
    while(i--){
      var row = csvRow[i];
      if ((csvHeader === true && i === 0)||  //  Skip header
          (row === "") // Skip empty rows
         ){
			scriptLog("Skipping row #"+i);
        
      }else{
        scriptLog(row);
        
        
        var elem = row.split(colsplit);
        var item = {};
        var c;
        
        if (elem[k]){
          item.kanji = elem[k].trim();
          
          if (elem[r]){
          
            if (elem[r].indexOf(vsplit)>-1){
            // eg 'reading 1[tab]reading 2[tab]reading 3'
          
              item.reading = elem[r].split(vsplit);
            }else{ //no tabs in string, single value
              item.reading=[elem[r]];
            }
            
          }else{
            item.reading=[""];
          }
        
          if (elem[m]){
          
            if (elem[m].indexOf(vsplit)>-1){
            // eg 'meaning 1[tab]meaning 2[tab]meaning 3'
          
              item.meaning = elem[m].split("\t");
            }else{ //no tabs in string, single value
              item.meaning=[elem[m]];
            }

            c = item.meaning.length;
          
            while(c--){
              scriptLog("item.meaning["+c+"]: "+item.meaning[c]);
            }
          }else{//todo: provide overwrite option on forced meaning
            item.meaning=[""];
          }
        
          JSONimport.push(item);
        }else{ // corrupt row ('kanji' is mandatory (can be kana-only word), is not present on row, skip
        }  
      }
    }
    var JSONstring = JSON.stringify(JSONimport);
    scriptLog(JSONstring);
    
    if (JSONstring.length !== 0) {
        try {
            var add = JSON.parse(JSONstring.toLowerCase());
/*//----------------------
            if (!checkAdd(add)) {
                $("#importStatus").text("No valid input (duplicates?)!");
                return;
            }
//----------------------*/
  

            for(i = 0; i < add.length; i++) { 
              add[i].level = 0;
              add[i].date = Date.now();
              add[i].manualLock = "";
              add[i] = setLocks(add[i]);
              setVocList(add[i]);
            }
          
            $("#importStatus").text("Import successful!");

            $("#importForm")[0].reset();
            $("#importArea").text("");

        }
        catch (e) {
            $("#importStatus").text("Parsing Error!");
            scriptLog(e);
        }

    }
    else {
        $("#importStatus").text("Nothing to import :( Please paste your stuff first");
    }
    
  };
}
document.getElementById("upload").addEventListener('change', fileUpload, false);


$("#ImportCsvBtn").click(function () {
});

$("#ImportItemsBtn").click(function () {

    if ($("#importArea").val().length !== 0) {
        try {
            var add = JSON.parse($("#importArea").val().toLowerCase());

            if (!checkAdd(add)) {
                $("#importStatus").text("No valid input (duplicates?)!");
                return;
            }

            var newlist;
            var srslist = [];
            if (localStorage.getItem('User-Vocab')) {
                var vocabList = getVocList();
                srslist = getSrsList();
                newlist = vocabList.concat(add);
            }
            else {
                newlist = add;
                
                
            }
            for(var i = 0; i < add.length; i++) {
//--------------------------------------------------------------------------------------------------------
              add[i].level = 0;
              add[i].date = Date.now();
//--------------------------------------------------------------------------------------------------------
              add[i].manualLock = "";
              add[i] = setLocks(add[i]);
//--------------------------------------------------------------------------------------------------------
              setVocList(add[i]);
            }
          
            $("#importStatus").text("Import successful!");

            $("#importForm")[0].reset();
            $("#importArea").text("");

        }
        catch (e) {
            $("#importStatus").text("Parsing Error!");
            scriptLog(e);
        }

    }
    else {
        $("#importStatus").text("Nothing to import :( Please paste your stuff first");
    }
});

$("#ImportCloseBtn").click(function () {
    $("#import").hide();
    $("#importForm")[0].reset();
    $("#importArea").text("");
    $("#importStatus").text('Ready to import..');
});

/*
 *  Review Items
 */
window.WKSS_review = function () {
  
  //is there a session waiting in storage?  
  if(sessionStorage.getItem('User-Review')) {
scriptLog("There is a session ready "+JSON.stringify(sessionStorage.getItem('User-Review')));
    
      //show the selfstudy window
      $("#selfstudy").show();

      //hide other windows
      $("#add").hide();
      $("#export").hide();
      $("#edit").hide();
      $("#import").hide();

      startReview();
    }
};

$("body").append('                                                          \
    <div id="selfstudy" class="WKSS">\
          <button id="SelfstudyCloseBtn" class="wkss-close" type="button"><i class="icon-remove"></i></button>\
       <h1>Review</h1>\
       <div id="wkss-kanji">\
          <span id="rev-kanji"></span>\
       </div><div id="wkss-type">\
          <span id="rev-type"></span><br />\
       </div><div id="wkss-solution">\
          <span id="rev-solution"></span>\
       </div><div id="wkss-input">\
          <input type="text" id="rev-input" size="40" placeholder="">\
       </div><span id="rev-index" style="display: none;"></span>\
       \
       <form id="audio-form">\
          <button id="AudioButton" type="button">Play audio</button>\
       </form>\
       <div id="rev-audio" style="display:none;"></div>\
    </div>                                                                    '
);
$("#selfstudy").hide();

$("#SelfstudyCloseBtn").click(function () {
    $("#selfstudy").hide();
    $("#rev-input").val("");
  reviewActive = false;
});

$("#AudioButton").click(function () {
    OpenInNewTab(document.getElementById('rev-audio').innerHTML);
});

function OpenInNewTab(url )
{
  var win=window.open(url, '_blank');
  win.focus();
}

function playAudio() {
    
    var kanji = document.getElementById('rev-kanji').innerHTML;
    var kana = (document.getElementById('rev-solution').innerHTML.split(/[,、]+\s*/))[0];
    
    document.getElementById('rev-audio').innerHTML = "";
    document.getElementById('audio-form').action = "";
    //document.getElementById('AudioButton').disabled = true;
    
    if(! kanji.match(/[a-zA-Z]+/i) && ! kana.match(/[a-zA-Z]+/i)) {
        
        kanji = encodeURIComponent(kanji);
        kana = encodeURIComponent(kana);
        var i;
      
        var newkanji = "";
        for(i = 1; i < kanji.length; i = i+3) {
            newkanji = newkanji.concat(kanji[i-1]);
            newkanji = newkanji.concat('2');
            newkanji = newkanji.concat('5');
            newkanji = newkanji.concat(kanji[i]);
            newkanji = newkanji.concat(kanji[i+1]);
        }
        
        var newkana = "";
        for(i = 1; i < kana.length; i = i+3) {
            newkana = newkana.concat(kana[i-1]);
            newkana = newkana.concat('2');
            newkana = newkana.concat('5');
            newkana = newkana.concat(kana[i]);
            newkana = newkana.concat(kana[i+1]);
        }
        
        var url = "http://www.csse.monash.edu.au/~jwb/audiock.swf?u=kana=" + newkana + "%26kanji=" + newkanji;
            
        scriptLog("Audio URL: " + url);
        
        document.getElementById('AudioButton').disabled = false;
        
        document.getElementById('rev-audio').innerHTML = url;
        
    }
   
}


function generateReviewList() {
  //don't interfere with an active session
  if (reviewActive){

    document.getElementById('user-review').innerHTML = "Review in Progress";

    return;
  }
  scriptLog("generateReviewList()");
  // function generateReviewList() builds a review session and updates the html menu to show number waiting.
	var numReviews = 0;
  var soonest;
  var next;
  
  var reviewList = [];
	//check to see if there is vocab already in offline storage
  if (localStorage.getItem('User-Vocab')) {
    var vocabList = getFullList();
    scriptLog("var vocabList = localStorage.getItem('User-Vocab') (" + JSON.stringify(vocabList)+")");
    var srsList = getSrsList();
    scriptLog("var srsList = localStorage.getItem('User-Vocab') (" + JSON.stringify(srsList)+")");
    var now = Date.now();
    
    //for each vocab in storage, get the amount of time vocab has lived       
    for(var i = 0; i < vocabList.length; i++) {
      var dif = now - vocabList[i].date;
      
      // if tem is unlocked and unburned
      if (vocabList[i].level < 8 &&
          (vocabList[i].manualLock === "no" ||
           vocabList[i].manualLock ==="DB" && !lockDB )){
        // if it is past review time
        if(srsintervals[vocabList[i].level] <= dif) {
          // count vocab up for review
          numReviews++;

          // add item-meaning object to reviewList
          // have made this optional for surname lists etc.
          if (vocabList[i].meaning[0] !== "") {
            var revItem = {};
            revItem.kanji = vocabList[i].kanji;
            revItem.type = "Meaning";
            revItem.solution = vocabList[i].meaning;
            revItem.index = i;
            reviewList.push(revItem);
          }
          
          // reading is optional, if there is a reading for the vocab, add its object.
          if (vocabList[i].reading[0] !== "") {
            var revItem2 = {};
            revItem2.kanji = vocabList[i].kanji;
            revItem2.type = "Reading";
            revItem2.solution = vocabList[i].reading;
            // item and item2 are matched by mutual index
            revItem2.index = i;
            reviewList.push(revItem2);
          }
          
          //if there is a meaning and reading, and reverse flag is true, test reading from english
          if (vocabList[i].reading[0] !== "" && vocabList[i].meaning[0] !== "" && reverse){
            var revItem3 = {};
            revItem3.kanji = vocabList[i].meaning.toString();
            revItem3.type = "Reverse";
            revItem3.solution = vocabList[i].reading;
            // item and item2 are matched by mutual index
            revItem3.index = i;
            reviewList.push(revItem3);
          }
          
        }else{//unlocked/unburned but not time to review yet
          scriptLog("setting soonest");
          next = srsintervals[vocabList[i].level] - dif;
          if(soonest){
            soonest = Math.min(soonest, next);
          }else{
            soonest = next;
          }
            
        }
      }//end if item is up for review
    }// end iterate through vocablist 
  }// end if localStorage
  
  if (reviewList.length !== 0){
        
    //shuffle the deck
    reviewList = shuffle(reviewList);
        
    //store reviewList in current session
    sessionStorage.setItem('User-Review', JSON.stringify(reviewList));
    scriptLog("sessionStorage.setItem('User-Review, "+JSON.stringify(reviewList)+")");
    
  }else{
    scriptLog("reviewList is empty: "+JSON.stringify(reviewList));
    document.getElementById('user-review').innerHTML = "Next Review in "+ms2str(soonest);
  }
    
  var strReviews = numReviews.toString();
  
  //....sure, why not.
  /* No! I want to see!
  if (numReviews > 42) {
    strReviews = "42+"; //hail the crabigator!
  }
  //*/
  
  // return the number of reviews
  scriptLog(numReviews.toString() +" reviews created");
  if (numReviews > 0){
    document.getElementById('user-review').innerHTML = "Review (" + strReviews + ")";
  }
}

//global to keep track of when a review is in session.
var reviewActive = false;

function startReview() {
  scriptLog("startReview()");
  reviewActive = true;
  //get the review 'list' from session storage, line up the first item in queue
  var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
    nextReview(reviewList);
}

function nextReview(reviewList) {
scriptLog("reviewList = "+sessionStorage.getItem('User-Review'));
  //uses functions:
  //    wanakana.bind/unbind

  var item = reviewList[0];
  
  document.getElementById('rev-kanji').innerHTML = item.kanji;
  document.getElementById('rev-type').innerHTML = item.type;
  document.getElementById('rev-solution').innerHTML = item.solution;
  document.getElementById('rev-index').innerHTML = item.index;

  //initialise the input field
  $("#rev-input").focus();
  $("#rev-input").removeClass("error");
  $("#rev-input").removeClass("correct");
  $("#rev-input").val("");

    //check for alphabet letters and decide to bind or unbind wanakana
    if (item.solution[0].match(/[a-zA-Z]+/i)) {
        wanakana.unbind(document.getElementById('rev-input'));
        $('#rev-input').attr('placeholder','Your response');
        document.getElementById('rev-type').innerHTML = "Meaning";
    }
    else {
        wanakana.bind(document.getElementById('rev-input'));
        $('#rev-input').attr('placeholder','答え');
        document.getElementById('rev-type').innerHTML = "Reading";
    }
    
   playAudio();
}

function storeSession(correct) {
  //functions:
  //  updateSRS(var correct)
  
  //initialize statsList variable
  var statsList = [];
  
  //is there a statsList stored in the session
  if (sessionStorage.getItem('User-Stats')) {
    //assign it to variable
    statsList = JSON.parse(sessionStorage.getItem('User-Stats'));
    
    //iterate through statsList
    for (var i = 0; i < statsList.length; i++) {
      
      //filter statsList for wrong answers
      if (statsList[i].correct === false) {
        
        // if the statslist member's kanji is the one currently in the prompt window
        if ((statsList[i].kanji.trim() === document.getElementById('rev-kanji').innerHTML.trim()) &&
        // AND is it asking for the same thing (meaning vs reading)
            (statsList[i].type.trim() === document.getElementById('rev-type').innerHTML.trim())) {
            
          //-the item currently being tested is present in 'stats' so it has already been asked this session
          //-it was answered incorrectly
          //-it was this exact question matching both the kanji and whether it is meaning or reading
          
          //do not store this answer in stats, it was already wrong and can't get wronger
          //we do not want to overwrite it as 'correct'
          return;
          
        }//if this is a question previously asked
      }//filter for wrong answers
    }//loop through all items already answered in session
  }//check if any questions answered yet
  
  
  //there may or may not be User-Stats in storage
  //if there are none, statsList is empty
  //if there are, statslist is populated with items that are not the current answer
  
  //so 'stats' can be safely pushed onto the session statsList
  
  var stats = {};
  stats.correct = correct;
  stats.kanji = document.getElementById('rev-kanji').innerHTML;
  stats.type = document.getElementById('rev-type').innerHTML;
  updateSRS(correct);

  statsList.push(stats);
  sessionStorage.setItem('User-Stats', JSON.stringify(statsList));  

}

function showResults() {
  scriptLog("showResults()");
  if (sessionStorage.getItem('User-Stats')) {
    var statsList = JSON.parse(sessionStorage.getItem('User-Stats'));
    for (var i = 0; i < statsList.length; i++) {

      if (statsList[i].correct === true) {
        if (statsList[i].type.trim() === "Meaning".trim()){
           document.getElementById("stats-m").innerHTML += "<span class=\"rev-correct\">" + statsList[i].kanji + "</span>";
        }else{
          document.getElementById("stats-r").innerHTML += "<span class=\"rev-correct\">" + statsList[i].kanji + "</span>";     
        }
      }
      else {
        if (statsList[i].type.trim() === "Meaning".trim()){
          document.getElementById("stats-m").innerHTML += "<span class=\"rev-error\">" + statsList[i].kanji + "</span>";
        }else{
          document.getElementById("stats-r").innerHTML += "<span class=\"rev-error\">" + statsList[i].kanji + "</span>";          
        }    
      }
    }
  }
  //clear session
  sessionStorage.clear();
  reviewActive = false;
}

$("body").append('                                                          \
    <div id="resultwindow" class="WKSS">                                    \
       <button id="ReviewresultsCloseBtn" class="wkss-close" type="button"><i class="icon-remove"></i></button>\
       <h1>Review Results</h1>\
       <h2>Reading</h2>\
       <div id="stats-r"></div>\
       <h2>Meaning</h2>\
       <div id="stats-m"></div>\
    </div>                                                                   '
);

$("#resultwindow").hide();

$("#ReviewresultsCloseBtn").click(function () {
    $("#resultwindow").hide();
    document.getElementById("stats-r").innerHTML = "";
    document.getElementById("stats-m").innerHTML = "";
});


//declare global values for keyup event
//is an answer being submitted?
var submit = true;

//jquery keyup event
$("#rev-input").keyup(function (e) {
  //functions:
  //  inputCorrect()
  
    
  //check if key press was 'enter' (keyCode 13) on the way up
  //and keystate true (answer being submitted) 
  //and cursor is focused in reviewfield
  if (e.keyCode == 13 && submit === true) {

		//check for input, do nothing if none
    if($("#rev-input").val().length === 0){
      return;
    }  
      
    //disable input after submission
    //document.getElementById('rev-input').disabled = true;
    
    
    //was the input correct?
    var correct = inputCorrect();

    if (correct) {
        //highlight in (default) green
        $("#rev-input").addClass("correct");
        //show answer
        $("#rev-solution").addClass("info");
      }
      else {
        //highight in red
        $("#rev-input").addClass("error");
        //show answer
        $("#rev-solution").addClass("info");
      }
    
      //remove from sessionList if correct
    //( maybe this should be done in inputCorrect() )
      if (correct) {
        scriptLog("correct answer");
        var sessionList = JSON.parse(sessionStorage.getItem('User-Review'));
        if (sessionList !== null){
          var oldlen = sessionList.length;
          sessionList.splice(0, 1);
          scriptLog("sessionList.length: "+ oldlen +" -> "+sessionList.length);
          
          //replace shorter (by one) sessionList to session 
          if (sessionList.length !== 0) {
            scriptLog("sessionList.length: "+ sessionList.length);
            sessionStorage.setItem('User-Review', JSON.stringify(sessionList));
            
          }
          else {
            //reveiw over, delete sessionlist from session
            scriptLog("sessionStorage.removeItem('User-Review')");
            sessionStorage.removeItem('User-Review');
          }
        }else{
          scriptLog("Error: no session found");
        }
      }else{
        scriptLog("wrong answer");
        
      }
scriptLog("store session");
      storeSession(correct);
        //playAudio();
scriptLog("store session complete");

        //answer submitted, next 'enter' proceeds with script
        submit = false;
    }
    else if (e.keyCode == 13 && submit === false) {
        scriptLog("keystat = " + submit);

        //there are still more reviews in session?
        if (sessionStorage.getItem('User-Review')) {
          scriptLog("found a 'User-Review': " + sessionStorage.getItem('User-Review') );

          setTimeout(function () {
            scriptLog("refreshing reviewList from storage");
            var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
            
            //cue up first remaining review
            nextReview(reviewList);
            scriptLog("checking for empty reviewList");
            if (reviewList.length !== 0){
            
//              sessionStorage.setItem('User-Review', JSON.stringify(reviewList));
              
            }else{
                scriptLog("session over. reviewList: "+JSON.stringify(reviewList));
                sessionStorage.removeItem("User-Review");
              }
              
                     //         document.getElementById('rev-input').disabled = true;
                $("#rev-solution").removeClass("info");
                $("#selfstudy").hide().fadeIn('fast');

            }, 1);
        }
        else {
        // no review stored in session, review is over    
            setTimeout(function () {
                
                $("#selfstudy").hide();
                //document.getElementById('rev-input').disabled = false;
                $("#rev-solution").removeClass("info");
              scriptLog("showResults");  
              showResults();
                $("#resultwindow").show();
              scriptLog("showResults completed");
              
            }, 1);
        }
        submit = true;
        scriptLog("submit = " + submit);

    }
});


function updateSRS(correct) {
    var srslist = getSrsList();
    var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
    var index = document.getElementById('rev-index').innerHTML;
    
    if(reviewList && checkForDuplicates(reviewList,srslist[index])) {
        scriptLog("Other item found, save status");
        
        if(sessionStorage.getItem(srslist[index].kanji)) {
            correct = correct && JSON.parse(sessionStorage.getItem(srslist[index].kanji));
        }
        
        sessionStorage.setItem(srslist[index].kanji, JSON.stringify(correct));
        return;
    }
    else if(sessionStorage.getItem(srslist[index].kanji)) {
        correct = correct && JSON.parse(sessionStorage.getItem(srslist[index].kanji));
        sessionStorage.removeItem(srslist[index].kanji);
    }
        
    
    var now = Date.now();
    
    if(correct === true) {
        srslist[index].level++;
    }
    else
    {
        if(srslist[index].level !== 0)
            srslist[index].level--;
        
    }
    
    srslist[index].date = now;
    
    scriptLog("updateSRS - " + srslist[index].kanji + " - new level: " + srslist[index].level + " date: " + now);
    setSrsList(srslist[index]);

}

function inputCorrect() {
  
    var input = $("#rev-input").val().toLowerCase();
    var solution = document.getElementById('rev-solution').innerHTML.split(/[,、]+\s*/);
    var correct = 0;
    var returnvalue = false;
    
    scriptLog("Input: " + input);
    
    // also allow entering of both solutions at once, if available
    if(solution.length == 2) {
        solution[2] = solution[0] + ", " + solution[1];
        solution[3] = solution[1] + ", " + solution[0];
    }
    
    for(var i = 0; i < solution.length; i++) {
        solution[i] = solution[i].toLowerCase();
        
        correct = 0;
        var threshold = Math.floor(solution[i].length / errorAllowance);
    
        if(document.getElementById('rev-type').innerHTML == "Reading") {
            threshold = 0;
        }
    
        scriptLog("Checking " + solution[i] + " with threshold: " + threshold);
        
        var j;
        if (input.length <= solution[i].length) {

            if(input.length < (solution[i].length - threshold)) {
                returnvalue = returnvalue || false;
                scriptLog("false at if branch " + input.length + " < " + (solution[i].length - threshold));
            }
            else {
            
                j = input.length;
                while (j--) {
                    if (input[j] == solution[i][j]) {
                        correct++;
                    }
                }

                if ((solution[i].length - threshold) <= correct) {
                    returnvalue = returnvalue || true;
                    scriptLog("true at if branch " + (solution[i].length - threshold) + " <= " + correct);
                }
            }
        }
        else {
        
            if(input.length > (solution[i].length + threshold)) {
                returnvalue = returnvalue || false;
                scriptLog("false at else branch " + input.length + " > " + (solution[i].length + threshold));
            }
            else {
            
                j = solution[i].length;
                while (j--) {
                    if (input[j] == solution[i][j]) {   
                        correct++;
                    }
                }
            
                if ((solution[i].length - threshold) <= correct) {
                    returnvalue = returnvalue || true;
                    scriptLog("true at else branch " + (solution[i].length - threshold) + " <= " + correct);
                }
            }
        }
    }

    scriptLog("Returning " + returnvalue);
    return returnvalue;
}

/*
 *  Adds the Button
 */
function addUserVocabButton() {
  scriptLog("addUserVocabButton()");
  //Functions (indirect)
  //    WKSS_add()
  //    WKSS_edit()
  //    WKSS_export()
  //    WKSS_import()
  //    WKSS_lock()
  //    WKSS_review()
  
    var nav = document.getElementsByClassName('nav');
  scriptLog("generating review list because: initialising script and populating reviews");

  
    if (nav) {
        nav[2].innerHTML = nav[2].innerHTML + "\n\
<li class=\"dropdown custom\">\n\
  <a class=\"dropdown-toggle custom\" data-toggle=\"dropdown\" href=\"#\" onclick=\"generateReviewList();\">\n\
    <span lang=\"ja\">自習</span>\n\
    Self-Study <i class=\"icon-chevron-down\"></i>\n\
  </a>\n\
  <ul class=\"dropdown-menu\" id=\"WKSS_dropdown\">\n\
    <li class=\"nav-header\">Customize</li>\n\
    <li><a id=\"click\" href=\"#\" onclick=\"WKSS_add();\">Add</a></li>\n\
    <li><a href=\"#\" onclick=\"WKSS_edit();\">Edit</a></li>\n\
    <li><a href=\"#\" onclick=\"WKSS_export();\">Export</a></li>\n\
    <li><a href=\"#\" onclick=\"WKSS_import();\">Import</a></li>\n\
 <!--//   <li><a href=\"#\" onclick=\"WKSS_lock();\">Server Settings</a></li>//-->\n\
    <li class=\"nav-header\">Learn</li>\n\
    <li><a id=\"user-review\" href=\"#\" onclick=\"WKSS_review();\">Please wait...</a></li>\n\
  </ul>\n\
</li>";
  
    
    }
}



/*
 *  Prepares the script
 */
function scriptInit() {
  scriptLog("scriptInit()");
  //functions:
  //    addUserVocabButton()
  //    logError(err)
  
    scriptLog("Initializing Wanikani UserVocab Script!");

    GM_addStyle(".custom .dropdown-menu {background-color: #DBA901 !important;}");
    GM_addStyle(".custom .dropdown-menu:after {border-bottom-color: #DBA901 !important;");
    GM_addStyle(".custom .dropdown-menu:before {border-bottom-color: #DBA901 !important;");
    GM_addStyle(".open .dropdown-toggle.custom {background-color: #FFC400 !important;}");
    GM_addStyle(".custom .dropdown-menu a:hover {background-color: #A67F00 !important;}");
    GM_addStyle(".custom:hover {color: #FFC400 !important;}");
    GM_addStyle(".custom:hover span {border-color: #FFC400 !important;}");
    GM_addStyle(".custom:focus {color: #FFC400 !important;}");
    GM_addStyle(".custom:focus span {border-color: #FFC400 !important;}");
    GM_addStyle(".open .custom span {border-color: #FFFFFF !important;}");
    GM_addStyle(".open .custom {color: #FFFFFF !important}");

    GM_addStyle("   \
.WKSS {\
    position:fixed;\
top:125px;\
left:50%;\
margin:0px;\
background: #FFF;\
    padding: 5px;\
    font: 12px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
    color: #888;\
    text-shadow: 1px 1px 1px #FFF;\
    border:1px solid #DDD;\
    border-radius: 5px;\
    -webkit-border-radius: 5px;\
    -moz-border-radius: 5px;\
    box-shadow: 10px 10px 5px #888888;\
}\
.WKSS h1 {\
    font: 25px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
    padding-left: 5px;\
    display: block;\
    border-bottom: 1px solid #DADADA;\
    margin: 0px;\
    color: #888;\
}\
.WKSS h1>span {\
    display: block;\
    font-size: 11px;\
}\
.WKSS label {\
    display: block;\
    margin: 0px 0px 5px;\
}\
\
\
.WKSS label>span {\
    float: left;\
    width: 80px;\
    text-align: right;\
    padding-right: 10px;\
    margin-top: 10px;\
    color: #333;\
    font-family: \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
    font-weight: bold;\
}\
.WKSS input[type=\"text\"], .WKSS input[type=\"email\"], .WKSS textarea{\
    border: 1px solid #CCC;\
    color: #888;\
    height: 20px;\
    margin-bottom: 16px;\
    margin-right: 6px;\
    margin-top: 2px;\
    outline: 0 none;\
    padding: 6px 12px;\
    width: 80%;\
    border-radius: 4px;\
    line-height: normal !important;\
    -webkit-border-radius: 4px;\
    -moz-border-radius: 4px;\
    font: normal 14px/14px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
}\
.WKSS select {\
    border: 1px solid #CCC;\
    color: #888;\
    outline: 0 none;\
    padding: 6px 12px;\
    height: 160px !important;\
    width: 95%;\
    border-radius: 4px;\
    -webkit-border-radius: 4px;\
    -moz-border-radius: 4px;\
    font: normal 14px/14px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
    #background: #FFF url('down-arrow.png') no-repeat right;\
    #background: #FFF url('down-arrow.png') no-repeat right);\
    appearance:none;\
    -webkit-appearance:none;\
    -moz-appearance: none;\
    text-indent: 0.01px;\
    text-overflow: '';\
}\
.WKSS textarea{\
    height:100px;\
}\
.WKSS button, .button {\
    background: #FFF;\
    border: 1px solid #CCC;\
    padding: 10px 25px 10px 25px;\
    color: #333;\
    border-radius: 4px;\
}\
.WKSS button:disabled {\
    background: #EBEBEB;\
    border: 1px solid #CCC;\
    padding: 10px 25px 10px 25px;\
    color: #333;\
    border-radius: 4px;\
}\
.WKSS .button:hover, button:hover:enabled {\
    color: #333;\
    background-color: #EBEBEB;\
    border-color: #ADADAD;\
}                                                          \
.WKSS button:hover:disabled {\
cursor: default\
}                                                          \
.error {border-color:#F00 !important; color: #F00 !important;}\
.correct {border-color:#0F0 !important; color: #0F0 !important;}\
.info {border-color:#696969 !important; color: #696969 !important;}\
.rev-error {text-shadow:none; border: 1px solid #F00 !important;border-radius: 10px; background-color: #F00; padding:4px; margin:4px; color: #FFFFFF; font: normal 18px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;}\
.rev-correct {text-shadow:none; border: 1px solid #088A08 !important;border-radius: 10px; background-color: #088A08; padding:4px; margin:4px; color: #FFFFFF; font: normal 18px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;}"
);
    GM_addStyle("\
#add {\
    width:" + addWindowWidth + "px;\
    height:" + addWindowHeight + "px; \
    margin-left:-" + addWindowWidth/2 + "px; \
}\
                ");
    GM_addStyle("\
#export, #import {\
background:#fff;\
    width:" + exportImportWindowWidth + "px;\
    height:" + addWindowHeight + "px; \
    margin-left:-" + exportImportWindowWidth/2 + "px; \
}\
                ");
    GM_addStyle("\
#edit {\
    width:" + editWindowWidth + "px;\
    height:" + editWindowHeight + "px; \
    margin-left:-" + editWindowWidth/2 + "px; \
}\
                ");
    GM_addStyle("\
#selfstudy {\
left:50%;\
    width:" + studyWindowWidth + "px;\
    height:" + studyWindowHeight + "px; \
    margin-left:-" + studyWindowWidth/2 + "px; \
}\
                ");
    GM_addStyle("\
#resultwindow {\
    left:50%;\
    width:" + resultWindowWidth + "px;\
    height:" + resultWindowHeight + "px; \
    margin-left:-" + resultWindowWidth/2 + "px; \
}"
);
/*    GM_addStyle("\
#SelfstudyCloseBtn {\
margin-top: 35px;\
left: 45%;\

display: inline !important;\
-webkit-margin-before: 50px;\
}");*/
        GM_addStyle("\
#AudioButton {\
margin-top: 35px;\
left: 10%;\
position: relative;\
display: inline !important;\
-webkit-margin-before: 50px;\
}");
  GM_addStyle("\
button.wkss-close {\
float:right;\
background-color:#ff4040;\
color:#fff;\
padding:0px;\
height:27px;\
width:27px\
}\
\
#wkss-close {\
float:right;\
background-color:#ff4040;\
color:#fff;\
padding:0px;\
height:27px;\
width:27px\
}");
    GM_addStyle("\
#wkss-kanji, #rev-kanji {\
text-align:center !important;\
font-size:50px !important;\
background-color: #9400D3 !important;\
color: #FFFFFF !important;\
border-radius: 10px     10px      0px           0px;\
}");
    GM_addStyle("\
#wkss-solution, #rev-solution {\
text-align: center !important;\
font-size:30px !important;\
color: #FFFFFF;\
padding: 2px;\
}");
    GM_addStyle("\
#wkss-type, #rev-type {\
text-align:center !important;\
font-size:24px !important;\
background-color: #696969 !important;\
color: #FFFFFF !important;\
border-radius: 0px     0px      10px           10px;\
}");
    GM_addStyle("\
#wkss-input, #rev-input {\
text-align:center !important;\
font-size:40px !important;\
height: 60px !important;\
line-height: normal !important;\
}");
    // Set up buttons
    try {
        if (typeof(Storage) !== "undefined") {
            addUserVocabButton();
            
        }
        else {
            scriptLog("Wanikani Self-Study: Your browser does not support localStorage.. Sorry :(");
        }
    }
    catch (err) {
        logError(err);
    }
}

/*
 * Helper Functions/Variables
 */

function isEmpty(value) {
    return (typeof value === "undefined" || value === null);
}

function select_all(str) {
  //eval can be harmful
    var text_val = document.getElementById(str);
  scriptLog(text_val);
    text_val.focus();
    text_val.select();
}

function checkAdd(add) {
    var i;
    if(localStorage.getItem('User-Vocab')) {    
        var vocabList = getVocList();
        for (i = 0; i < add.length; i++) {
        if (!checkItem(add[i]) || checkForDuplicates(vocabList,add[i]))
            return false;
        }
    }
    else {
        for (i = 0; i < add.length; i++) {
        if (!checkItem(add[i]))
            return false;
        }
    }
    
    return true;
}

function checkItem(add) {
    if (isEmpty(add.kanji) || isEmpty(add.meaning) || isEmpty(add.reading))
        return false;
    
    if((Object.prototype.toString.call(add.meaning) !== '[object Array]')||
      (Object.prototype.toString.call(add.reading) !== '[object Array]'))
        return false;

    return true;
}

function shuffle(array) {
    var currentIndex = array.length;
    var temporaryValue;
    var randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element.
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }

    return array;
}

/*
 * Error handling
 * Can use 'error.stack', not cross-browser (though it should work on Firefox and Chrome)
 */
function logError(error) {
  scriptLog("logError(error)");
  var stackMessage = "";
  if ("stack" in error)
    stackMessage = "\n\tStack: " + error.stack;

  scriptLog("WKSS: Error: " + error.name + "\n\tMessage: " + error.message + stackMessage);
  console.error("WKSS: Error: " + error.name + "\n\tMessage: " + error.message + stackMessage);
}


//*****Ethan's Functions*****

function getServerResp(APIkey, requestedItem){
  //functions:
  //    refreshLocks()
  //    generateReviewList()
  scriptLog("creating empty kanjiList");
    if (APIkey !== "YOUR_API_HERE"){

      var xhrk = createCORSRequest("get", "https://www.wanikani.com/api/user/" + APIkey + "/" + requestedItem + "/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50");

      if (xhrk){
          xhrk.onreadystatechange = function(e) {
            
            var kanjiList = [];
            if (xhrk.readyState == 4){
      
              scriptLog("xhrk e.source");
              scriptLog(e);
      
              var resp = JSON.parse(xhrk.responseText);
					scriptLog("about to loop through requested information"); 
            
					    for(var i=0;i<resp.requested_information.length;i++){
                
                //push response onto kanjilist variable
                if (resp.requested_information[i].user_specific !== null){
                  kanjiList.push({"character": resp.requested_information[i].character, "srs": resp.requested_information[i].user_specific.srs});
                }
              }
              
              //store kanjiList for offline and event use
              //Overwrite previous kanjiList
              scriptLog("Server responded with new kanjiList: \n"+JSON.stringify(kanjiList));
              
              localStorage.setItem('User-KanjiList', JSON.stringify(kanjiList));

//update locks in localStorage

              refreshLocks();


              
            }
          };

          xhrk.send();
        }
    }
    else {
                        //dummy server response for testing.

      setTimeout(function () {
        var kanjiList = [];
scriptLog("creating dummy response");
kanjiList.push({"character": "猫", "srs": "noServerResp"});
var SRS = "apprentice"; //prompt("enter SRS for 子", "guru");
kanjiList.push({"character": "子", "srs": SRS});
kanjiList.push({"character": "品", "srs": "guru"});
kanjiList.push({"character": "供", "srs": "guru"});
kanjiList.push({"character": "本", "srs": "guru"});
kanjiList.push({"character": "聞", "srs": "apprentice"});
kanjiList.push({"character": "人", "srs": "enlightened"});
kanjiList.push({"character": "楽", "srs": "burned"});
kanjiList.push({"character": "相", "srs": "guru"});
kanjiList.push({"character": "卒", "srs": "noMatchWK"});
kanjiList.push({"character": "無", "srs": "noMatchGuppy"});

              scriptLog("Server responded with dummy kanjiList: \n"+JSON.stringify(kanjiList));
              
              localStorage.setItem('User-KanjiList', JSON.stringify(kanjiList));
        
//update locks in localStorage
                    refreshLocks();


      }, 10000);
    }   
}

function getComponents(kanji){
  scriptLog("getComponents(kanji)");
  //functions:
  //    none
  
    //takes in a string and returns an array containing only the kanji characters in the string.
    var components = [];
    for (var c = 0; c < kanji.length; c++){
        if(/^[\u4e00-\u9faf]+$/.test(kanji[c])) {
            components.push(kanji[c]);
        }
    }
    return components; 
}

function refreshLocks(){
  //functions:
  //    setLocks(srsitem)

  //scriptLog("refreshLocks()");
    if (localStorage.getItem('User-Vocab')) {
scriptLog("srsList found in local storage");

      var srsList = getSrsList();
      scriptLog("Error?");
      for (var i=0;i<srsList.length;i++){
      scriptLog("No");
        srsList[i] = setLocks(srsList[i]);  
        setSrsList(srsList[i]);  
      }
//      scriptLog("Setting new locks: "+JSON.stringify(srsList));
    }else{
      scriptLog("no srs storage found");
    }
}

function getCompKanji(vocab, kanjiList){
  scriptLog("getCompKanji(vocab, kanjiList)");
  //functions:
  //    getComponents(vocab)

  var compSRS = [];
  var kanjiReady = false; //indicates if the kanjiList has been populated
  var userGuppy = false; //indicated if kanjiList has less than 100 items
    
  //has the server responded yet
  if (kanjiList.length > 0){
    scriptLog("kanjiList is > 0");
    kanjiReady = true;
    
    //is there less than 100 kanji in the response
    if (kanjiList.length < 100){
      scriptLog("kanjiList is < 100");
      userGuppy = true;
    }
  }    
     
	//break the item down into its component kanji, discards katakana, hiragana etc
  var components = getComponents(vocab);
  scriptLog(vocab+": "+JSON.stringify(components));
  //for each kanji character component
  //    this is the outer loop since there will be far less of them than kanjiList
  for(var i = 0; i < components.length; i++){

    var matched = false;
		//for each kanji returned by the server
    for(var j=0; j<kanjiList.length; j++){
            
      //if the kanji returned by the server matches the character in the item
      if (kanjiList[j].character == components[i]){
        compSRS[i] = {"kanji": components[i], "srs": kanjiList[j].srs};
        matched = true;
        
        break; //kanji found: 'i' is its position in item components; 'j' is its postion in the 'kanjiList' server response
      }
    }
    
    if (matched === false){ // character got all the way through kanjiList without a match.
      if (kanjiReady){ //was there a server response?
        if (userGuppy){ //is the user a guppy (kanji probably matches a turtles response)
          scriptLog("matched=false, kanjiList.length: "+kanjiList.length);
          compSRS[i] = {"kanji": components[i], "srs": "noMatchGuppy"};
        }else{ //user is a turtle, kanji must not have been added to WK (yet)
          scriptLog("matched=false, kanjiList.length: "+kanjiList.length);
          compSRS[i] = {"kanji": components[i], "srs": "noMatchWK"};
        }
      }else{
        scriptLog("matched=false, kanjiReady=false, noServerResp");
        compSRS[i] = {"kanji": components[i], "srs": "noServerResp"};
      }
    }
  }
  return compSRS; // compSRS is an array of the kanji with SRS values for each kanji component.
    // eg. 折り紙:
    // compSRS = [{"kanji": "折", "srs": "guru"}, {"kanji": "紙", "srs": "apprentice"}]
}

function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr){
        xhr.open(method, url, true);
    } else if (typeof XDomainRequest !== "undefined"){
        xhr = new XDomainRequest();
        xhr.open(method, url);
    } else {
        xhr = null;
    }
    return xhr;
}

function createCSV(JSONstring){
  var JSONobject = (typeof JSONstring === 'string') ? jQuery.parseJSON(JSONstring) : JSONstring;
  var header = [];
  
  for (var c=0;c<JSONstring.length; c++){
  }
  window.open('data:text/csv;charset=utf-8,' + encodeURI(""));
}


$(document).ready(function(){


  // Check for file API support.
if (window.File && window.FileReader && window.FileList && window.Blob) {
  


} else {
  alert('The File APIs are not fully supported in this browser.');
}


  
/*
 * Start the script
 */

  
  //update kanjiList on connection
  getServerResp(APIkey, "kanji");
  
	scriptInit();
  
});