您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds an option to add and review your own custom vocabulary
当前为
- // ==UserScript==
- // @name Wanikani Self-Study Plus
- // @namespace wkselfstudyplus
- // @description Adds an option to add and review your own custom vocabulary
- // @include /^https://www\.wanikani\.com/(dashboard|level|radicals|kanji|vocabulary|lattice|about|api|faq|community|chat)/
- // @include *.wanikani.com/community*
- // @version 0.0.7
- // @author shudouken and Ethan
- // @license Attribution-NonCommercial 3.0 Unported
- // @resource LICENSE http://creativecommons.org/licenses/by-nc/3.0/
- // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
- // require https://raw.github.com/WaniKani/WanaKana/master/lib/wanakana.min.js
- // @grant GM_addStyle
- // @grant unsafeWindow
- // ==/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/
- */
- //shut up JSHint
- /* jshint multistr: true , jquery: true, indent:2 */
- /* global unsafeWindow, wanakana, GM_addStyle, Storage, XDomainRequest */
- var APIkey = "YOUR_API_HERE";
- /*
- * Debugging
- */
- var debugging = true;
- var scriptLog = debugging ? function (msg) {
- if (typeof msg === 'string') {
- unsafeWindow.console.log("WKSS: " + msg);
- } else {
- unsafeWindow.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);
- /*
- * 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("''");
- }
- });
- });
- /*
- * Auto Scroll Popup-Dialogs
- */
- $(function () {
- var $sidebar = $(".WKSS"),
- $window = $(window),
- offset = $sidebar.offset(),
- topPadding = padding;
- $window.scroll(function () {
- if ($window.scrollTop() > offset.top) {
- $sidebar.stop().animate({
- marginTop: $window.scrollTop() - offset.top + topPadding
- });
- } else {
- $sidebar.stop().animate({
- marginTop: padding
- });
- }
- });
- });
- /*
- * populate reviews when menu button pressed
- */
- unsafeWindow.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.
- unsafeWindow.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\
- <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\
- <button id="AddCloseBtn" type="button">Close window</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();
- //--------------------------------------------------------------------------------------------------------
- // scriptLog("generating review list because: new item added, it might be up for review");
- // generateReviewList();
- if (item.manualLock === "yes"){
- $("#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++){
- var srsitem = srsList[s];
- delete srsitem.meaning;
- delete srsitem.reading;
- }
- }
- scriptLog("getSrsList: "+JSON.stringify(srsList));
- return srsList;
- }
- function setSrsList(srsitem){
- // find kanji in 'Vocab-List'
- var srsList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
- if(srsList){
- 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;
- }else {
- scriptLog("SRS Kanji not found in vocablist, fail");
- }
- }
- 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("item found at vocList["+v+"], replacing meaning and reading");
- //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++) {
- 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
- */
- unsafeWindow.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\"> \
- <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=\"EditCloseBtn\" type=\"button\">Close window</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]);
- }
- // scriptLog("generating review list because: levels were just reset, review needs to be reinitialised");
- // generateReviewList();
- }
- });
- $("#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 = "";
- // scriptLog("generating review list because: items were edited, rebuilding review");
- // generateReviewList();
- }
- $("#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 last accessed/saved
- 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 = hour - dif ;
- }
- else {
- //calulate minutes to next review
- var mins = (Math.floor((hour-dif)/(mstohour/60))+1);
- //review in more than 60min
- if (mins >=60) {
- //calculate hours to next review
- var hours = Math.floor(mins/60);
- //review in more than 24hrs
- if(hours >= 24) {
- //calculate days to next review
- var days = Math.floor(hours/24);
- //review is in more than 7 days
- if(days >= 7) {
- //calculate weeks to next review
- var weeks = Math.floor(days/7);
- if(weeks > 4) {
- //calculate months to next review
- var months = Math.floor(weeks/4);
- review = "in ~" + months + "mon";
- }
- else {
- review = "in ~" + weeks + "w";
- }
- }
- else {
- review = "in ~" + days + "d";
- }
- }
- else {
- review = "in ~" + hours + "h";
- }
- }
- else {
- review = "in ~" + mins + "min";
- }
- }//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);
- }
- }
- }
- /*
- * Export
- */
- unsafeWindow.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"> \
- <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>\
- <button id="ExportCloseBtn" type="button">Close window</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
- */
- unsafeWindow.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"> \
- <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> \
- <button id="ImportItemsBtn" type="button">Import Items</button>\
- <button id="ImportCloseBtn" type="button">Close window</button> \
- </form> \
- </div> '
- );
- $("#import").hide();
- $("#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++) {
- //var srsitem = {};
- // srsitem.kanji = add[i].kanji;
- // srsitem.level = 0;
- // srsitem.date = Date.now();
- //--------------------------------------------------------------------------------------------------------
- //srsitem.manualLock = "";
- //srsitem = setLocks(srsitem);
- //--------------------------------------------------------------------------------------------------------
- add[i].level = 0;
- add[i].date = Date.now();
- //--------------------------------------------------------------------------------------------------------
- add[i].manualLock = "";
- add[i] = setLocks(add[i]);
- //--------------------------------------------------------------------------------------------------------
- setVocList(add[i]);
- }
- // localStorage.setItem('User-Vocab', JSON.stringify(newlist));
- // localStorage.setItem('User-SRS', JSON.stringify(srslist));
- $("#importStatus").text("Import successful!");
- // scriptLog("generating review list because: Items imported, need to rebuild review");
- //generateReviewList() ;
- $("#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
- */
- unsafeWindow.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">\
- <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>\
- <button id="SelfstudyCloseBtn" type="button">Close window</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){
- return;
- }
- scriptLog("generateReviewList()");
- // function generateReviewList() builds a review session and updates the html menu to show number waiting.
- var numReviews = 0;
- 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 until next review
- for(var i = 0; i < vocabList.length; i++) {
- var dif = now - vocabList[i].date;
- // if (time passed is greater than required for level) and (level not burned) and (srs is unlocked)
- // (if item is up for review)
- if((srsintervals[vocabList[i].level] <= dif) && vocabList[i].level < 8 && (vocabList[i].manualLock !== "yes" )){
- // count vocab up for review
- numReviews++;
- // add item-meaning object to reviewList
- 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] !== "") {
- scriptLog("vocabList["+i+"].reading === '"+ vocabList[i].reading+"'");
- 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);
- }
- }//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, see?: "+JSON.stringify(reviewList));
- }
- var strReviews = numReviews.toString();
- //....sure, why not.
- if (numReviews > 42) {
- strReviews = "42+"; //hail the crabigator!
- }
- // return the number of reviews
- scriptLog(numReviews.toString() +" reviews created");
- 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;
- // scriptLog("generating review list because: Session is over creating the next one if it exists");
- // generateReviewList();
- }
- $("body").append(' \
- <div id="resultwindow" class="WKSS"> \
- <h1>Review Results</h1>\
- <h2>Reading</h2>\
- <div id="stats-r"></div>\
- <h2>Meaning</h2>\
- <div id="stats-m"></div>\
- <button id="ReviewresultsCloseBtn" type="button">Close window</button>\
- </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>";
- //generateReviewList();
- }
- }
- /*
- * 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 {\
- background: #FFF;\
- padding: 20px 30px 20px 30px;\
- 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: 0px 0px 10px 40px;\
- display: block;\
- border-bottom: 1px solid #DADADA;\
- margin: -10px -30px 30px -30px;\
- 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 {\
- 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: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 {\
- position:absolute;\
- float:right;\
- top:50%;\
- left:50%;\
- width:" + addWindowWidth + "px;\
- height:" + addWindowHeight + "px; \
- margin-left:-" + addWindowWidth/2 + "px; \
- margin-top:-" + addWindowHeight/2 + "px; \
- }\
- ");
- GM_addStyle("\
- #export, #import {\
- position:absolute;\
- float:right;\
- top:50%;\
- left:50%;\
- width:" + exportImportWindowWidth + "px;\
- height:" + exportImportWindowHeight + "px; \
- margin-left:-" + exportImportWindowWidth/2 + "px; \
- margin-top:-" + exportImportWindowHeight/2 + "px; \
- }\
- ");
- GM_addStyle("\
- #edit {\
- position:absolute;\
- float:right;\
- top:50%;\
- left:50%;\
- width:" + editWindowWidth + "px;\
- height:" + editWindowHeight + "px; \
- margin-left:-" + editWindowWidth/2 + "px; \
- margin-top:-" + editWindowHeight/2 + "px; \
- }\
- ");
- GM_addStyle("\
- #selfstudy {\
- position:absolute;\
- float:right;\
- top:50%;\
- left:50%;\
- width:" + studyWindowWidth + "px;\
- height:" + studyWindowHeight + "px; \
- margin-left:-" + studyWindowWidth/2 + "px; \
- margin-top:-" + studyWindowHeight/2 + "px; \
- }\
- ");
- GM_addStyle("\
- #resultwindow {\
- position:absolute;\
- float:right;\
- top:50%;\
- left:50%;\
- width:" + resultWindowWidth + "px;\
- height:" + resultWindowHeight + "px; \
- margin-left:-" + resultWindowWidth/2 + "px; \
- margin-top:-" + resultWindowHeight/2 + "px; \
- }"
- );
- GM_addStyle("\
- #SelfstudyCloseBtn {\
- margin-top: 35px;\
- left: 45%;\
- position: relative;\
- 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("\
- #ReviewresultsCloseBtn {\
- top: 0%;\
- left: 75%;\
- position: relative;\
- -webkit-margin-before: 50px;\
- }");
- 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();
- //provide warning to users trying to use the (incomplete) script.
- scriptLog("this script is still incomplete: \n\
- I recommend you install the original script by shudouken at \n\
- http://userscripts-mirror.org/scripts/show/381435");
- }
- 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(obj) {
- //eval can be harmful
- var text_val = eval(obj);
- 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){
- //functions:
- // refreshLocks()
- // generateReviewList()
- scriptLog("creating empty kanjiList");
- if (APIkey !== "YOUR_API_HERE"){
- var xhrk = createCORSRequest("get", "https://www.wanikani.com/api/user/" + APIkey + "/kanji/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() {
- var kanjiList = [];
- if (xhrk.readyState == 4){
- var resp = JSON.parse(xhrk.responseText);
- //test for error
- 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 = 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;
- }
- $(document).ready(function(){
- /*
- * Start the script
- */
- //update kanjiList on connection
- getServerResp(APIkey);
- scriptInit();
- });