Wanikani Self-Study Plus

Adds an option to add and review your own custom vocabulary

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

  1. // ==UserScript==
  2. // @name Wanikani Self-Study Plus
  3. // @namespace wkselfstudyplus
  4. // @description Adds an option to add and review your own custom vocabulary
  5. // @include /^https://www\.wanikani\.com/(dashboard|level|radicals|kanji|vocabulary|lattice|about|api|faq|community|chat)/
  6. // @include *.wanikani.com/community*
  7. // @version 0.0.7
  8. // @author shudouken and Ethan
  9. // @license Attribution-NonCommercial 3.0 Unported
  10. // @resource LICENSE http://creativecommons.org/licenses/by-nc/3.0/
  11. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
  12. // require https://raw.github.com/WaniKani/WanaKana/master/lib/wanakana.min.js
  13. // @grant GM_addStyle
  14. // @grant unsafeWindow
  15. // ==/UserScript==
  16.  
  17. /*
  18. * This script is licensed under the Creative Commons License
  19. * "Attribution-NonCommercial 3.0 Unported"
  20. *
  21. * More information at:
  22. * http://creativecommons.org/licenses/by-nc/3.0/
  23. */
  24.  
  25. //shut up JSHint
  26. /* jshint multistr: true , jquery: true, indent:2 */
  27. /* global unsafeWindow, wanakana, GM_addStyle, Storage, XDomainRequest */
  28.  
  29. var APIkey = "YOUR_API_HERE";
  30.  
  31. /*
  32. * Debugging
  33. */
  34.  
  35. var debugging = true;
  36.  
  37. var scriptLog = debugging ? function (msg) {
  38. if (typeof msg === 'string') {
  39. unsafeWindow.console.log("WKSS: " + msg);
  40. } else {
  41. unsafeWindow.console.log(msg);
  42. }
  43. } : function () {
  44. };
  45.  
  46. /*
  47. * Settings and constants
  48. */
  49.  
  50. ///###############################################
  51. // Config for window sizes in pixels
  52.  
  53. // add Window, standard 300 x 300
  54. var addWindowHeight = 300;
  55. var addWindowWidth = 300;
  56.  
  57. // export and import Window, standard 275 x 390
  58. var exportImportWindowHeight = 275;
  59. var exportImportWindowWidth = 390;
  60.  
  61. // edit Window, standard 380 x 800
  62. var editWindowHeight = 380;
  63. var editWindowWidth = 800;
  64.  
  65. // study(review) Window, standard 380 x 600
  66. var studyWindowHeight = 380;
  67. var studyWindowWidth = 600;
  68.  
  69. // result Window, standard 500 x 700
  70. var resultWindowHeight = 500;
  71. var resultWindowWidth = 700;
  72.  
  73. // padding from top, standard -150
  74. var padding = -150;
  75.  
  76. ///###############################################
  77.  
  78. var errorAllowance = 4; //every x letters, you can make one mistake when entering the meaning
  79. var mstohour = 3600000;
  80.  
  81. //srs 4h, 8h, 24h, 3d (guru), 1w, 2w (master), 1m (enlightened), 4m (burned)
  82. var srslevels = [];
  83. srslevels.push("Apprentice");
  84. srslevels.push("Apprentice");
  85. srslevels.push("Apprentice");
  86. srslevels.push("Apprentice");
  87. srslevels.push("Guru");
  88. srslevels.push("Guru");
  89. srslevels.push("Master");
  90. srslevels.push("Enlightened");
  91. srslevels.push("Burned");
  92.  
  93. var srsintervals = [];
  94. srsintervals.push(0);
  95. srsintervals.push(14400000);
  96. srsintervals.push(28800000);
  97. srsintervals.push(86400000);
  98. srsintervals.push(259200000);
  99. srsintervals.push(604800000);
  100. srsintervals.push(1209600000);
  101. srsintervals.push(2628000000);
  102. srsintervals.push(10512000000);
  103.  
  104. /*
  105. * JQuery fixes
  106. */
  107. $("'[placeholder]'").focus(function () {
  108. var input = $(this);
  109. if (input.val() == input.attr("'placeholder'")) {
  110. input.val("''");
  111. input.removeClass("'placeholder'");
  112. }
  113. }).blur(function () {
  114. var input = $(this);
  115. if (input.val() == "''" || input.val() == input.attr("'placeholder'")) {
  116. input.addClass("'placeholder'");
  117. input.val(input.attr("'placeholder'"));
  118. }
  119. }).blur();
  120.  
  121. $("'[placeholder]'").parents("'form'").submit(function () {
  122. $(this).find("'[placeholder]'").each(function () {
  123. var input = $(this);
  124. if (input.val() == input.attr("'placeholder'")) {
  125. input.val("''");
  126. }
  127. });
  128. });
  129.  
  130. /*
  131. * Auto Scroll Popup-Dialogs
  132. */
  133. $(function () {
  134.  
  135. var $sidebar = $(".WKSS"),
  136. $window = $(window),
  137. offset = $sidebar.offset(),
  138. topPadding = padding;
  139.  
  140. $window.scroll(function () {
  141. if ($window.scrollTop() > offset.top) {
  142. $sidebar.stop().animate({
  143. marginTop: $window.scrollTop() - offset.top + topPadding
  144. });
  145. } else {
  146. $sidebar.stop().animate({
  147. marginTop: padding
  148. });
  149. }
  150. });
  151.  
  152. });
  153.  
  154. /*
  155. * populate reviews when menu button pressed
  156. */
  157.  
  158. unsafeWindow.generateReviewList = function() {
  159. //if menu is invisible, it is about to be visible
  160. if ( $("#WKSS_dropdown").is(":hidden") ){
  161. //This is really the only time it needs to run
  162. //unless we want to start updating in realtime by keeping track of the soonest item
  163. generateReviewList();
  164. }
  165. };
  166.  
  167. /*
  168. * Add Item
  169. */
  170. // event function to open "add window" and close any other window that might be open at the time.
  171. unsafeWindow.WKSS_add = function () {
  172. //show the add window
  173. $("#add").show();
  174. //hide other windows
  175. $("#export").hide();
  176. $("#import").hide();
  177. $("#edit").hide();
  178. $("#selfstudy").hide();
  179. };
  180.  
  181. //'add window' html text
  182. var addHtml = '\n\
  183. <div id="add" class="WKSS">\n\
  184. <form id="addForm">\n\
  185. <h1>Add a new Item</h1>\n\
  186. <input type="text" id="addKanji" placeholder="Enter 漢字, ひらがな or カタカナ">\n\
  187. <input type="text" id="addReading" title="Leave empty to add vocabulary like する (to do)" placeholder="Enter reading">\n\
  188. <input type="text" id="addMeaning" placeholder="Enter meaning">\n\
  189. \n\
  190. <p id="addStatus">Ready to add..</p>\n\
  191. <button id="AddItemBtn" type="button">Add new Item</button>\n\
  192. <button id="AddCloseBtn" type="button">Close window</button>\n\
  193. </form>\n\
  194. </div>\n\
  195. ';
  196.  
  197. //add html to page source
  198. $("body").append(addHtml);
  199.  
  200. //hide add window ("div add" code that was just appended)
  201. $("#add").hide();
  202.  
  203. //function to fire on click event for "Add new Item"
  204. $("#AddItemBtn").click(function () {
  205. var kanji = $("#addKanji").val().toLowerCase();
  206. var reading = $("#addReading").val().toLowerCase().split(/[,、]+\s*/); //split at , or 、followed by 0 or any number of spaces
  207. var meaning = $("#addMeaning").val().toLowerCase().split(/[,、]+\s*/);
  208. var success = false; //initalise values
  209. var meanlen = 0;
  210. for(var i = 0; i < meaning.length; i++) {
  211. meanlen += meaning[i].length;
  212. }
  213. //input is invalid: prompt user for valid input
  214. var item = {};
  215. if (kanji.length === 0 || meanlen === 0) {
  216. $("#addStatus").text("One or more required fields are empty!");
  217. if (kanji.length === 0) {
  218. $("#addKanji").addClass("error");
  219. }
  220. else {
  221. $("#addKanji").removeClass("error");
  222. }
  223. if (meanlen === 0) {
  224. $("#addMeaning").addClass("error");
  225. }
  226. else {
  227. $("#addMeaning").removeClass("error");
  228. }
  229. }
  230. else {
  231. scriptLog("building item: "+kanji);
  232. item.kanji = kanji;
  233. item.reading = reading; //optional
  234. item.meaning = meaning;
  235. //--preserve data integrity by combining item and srsitem
  236. item.level = 0;
  237. item.date = Date.now();
  238. item.manualLock = "";
  239. item = setLocks(item);
  240. success = true;
  241. scriptLog("item is valid");
  242. }
  243. //on successful creation of item
  244. if (success) {
  245. //clear error layout to required fields
  246. $("#addKanji").removeClass("error");
  247. $("#addMeaning").removeClass("error");
  248.  
  249. /*
  250. commenting out code here, 'add' should just override existing kanji now
  251. */
  252. //if there are already user items, retrieve vocabList
  253. // var vocabList = [];
  254. //check stored user items for duplicates ****************** to do: option for editing duplicate item with new input
  255. // if(checkForDuplicates(vocabList,item)) {
  256. // $("#addStatus").text("Duplicate Item detected!");
  257. // $("#addKanji").addClass("error");
  258. //return;
  259. // }
  260. scriptLog("starting setVocList"+item);
  261. setVocList(item);
  262. scriptLog("finished setVocList");
  263. scriptLog("clear form");
  264. $("#addForm")[0].reset();
  265.  
  266. //--------------------------------------------------------------------------------------------------------
  267. // scriptLog("generating review list because: new item added, it might be up for review");
  268.  
  269. // generateReviewList();
  270.  
  271. if (item.manualLock === "yes"){
  272. $("#addStatus").html("<i class=\"icon-lock\"></i> Added locked item");
  273. }else{
  274. $("#addStatus").html("<i class=\"icon-unlock\"></i>Added successfully");
  275. }
  276. //--------------------------------------------------------------------------------------------------------
  277. }
  278. });
  279.  
  280. $("#AddCloseBtn").click(function () {
  281. $("#add").hide();
  282. $("#addForm")[0].reset();
  283. $("#addStatus").text('Ready to add..');
  284. $("#addKanji").removeClass("error");
  285. $("#addMeaning").removeClass("error");
  286. });
  287.  
  288.  
  289.  
  290. //---Function wrappers to facilitate use of one localstorage array
  291. //---Maintains data integrity between previously two (vocab and srs)
  292. function getSrsList(){
  293. var srsList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  294. if(srsList){
  295. for(var s=0;s<srsList.length;s++){
  296. var srsitem = srsList[s];
  297. delete srsitem.meaning;
  298. delete srsitem.reading;
  299. }
  300. }
  301. scriptLog("getSrsList: "+JSON.stringify(srsList));
  302. return srsList;
  303. }
  304.  
  305. function setSrsList(srsitem){
  306. // find kanji in 'Vocab-List'
  307. var srsList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  308. if(srsList){
  309. for(var s=0;s<srsList.length;s++){
  310. scriptLog("comparing "+ srsList[s].kanji +" to "+ srsitem.kanji);
  311. if (srsList[s].kanji === srsitem.kanji){
  312. scriptLog("found match");
  313. //add date, level, locked and manualLock to item
  314. srsList[s].date = srsitem.date;
  315. srsList[s].level = srsitem.level;
  316. srsList[s].locked = srsitem.locked;
  317. srsList[s].manualLock = srsitem.manualLock;
  318. }else {
  319. scriptLog("SRS Kanji not found in vocablist, fail");
  320. }
  321. }
  322. scriptLog("item: "+JSON.stringify(srsitem));
  323. localStorage.setItem('User-Vocab', JSON.stringify(srsList));
  324. }
  325. }
  326.  
  327. function getVocList(){
  328. var vocList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  329. if (vocList){
  330. for(var v=0;v<vocList.length;v++){
  331. delete vocList[v].date;
  332. delete vocList[v].level;
  333. delete vocList[v].locked;
  334. delete vocList[v].manualLock;
  335. }
  336. }else{
  337. //return empty if null
  338. vocList = [];
  339. }
  340. scriptLog("getVocList: "+JSON.stringify(vocList));
  341. return vocList;
  342. }
  343.  
  344. function setVocList(item){
  345. // find kanji in 'Vocab-List'
  346. var found = false;
  347. var vocList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  348. if (vocList){
  349. for(var v=0;v<vocList.length;v++){
  350. if (vocList[v].kanji === item.kanji){
  351. found = true;
  352. scriptLog("item found at vocList["+v+"], replacing meaning and reading");
  353. //add meaning and reading to existing item
  354. vocList[v].meaning = item.meaning;
  355. vocList[v].reading = item.reading;
  356. }
  357. }
  358. if (!found) {
  359. scriptLog("Kanji not found in vocablist, adding now");
  360. vocList.push(item);
  361. }
  362. }
  363. else{
  364. scriptLog("vocablist not found, creating now");
  365. vocList = [];
  366. vocList.push(item);
  367. }
  368. scriptLog("setVocList: "+JSON.stringify(vocList));
  369. scriptLog("putting vocList in storage");
  370. localStorage.setItem('User-Vocab', JSON.stringify(vocList));
  371. }
  372.  
  373. function getFullList(){
  374. var fullList = jQuery.parseJSON(localStorage.getItem('User-Vocab'));
  375. if(!fullList){
  376. fullList=[];
  377. }
  378. return fullList;
  379. }
  380.  
  381.  
  382.  
  383. //checks if an item is present in a list
  384. function checkForDuplicates(list, item) {
  385. scriptLog("Check for dupes with:" + item.kanji);
  386.  
  387. for(var i = 0; i < list.length; i++) {
  388. if(list[i].kanji == item.kanji)
  389. return true;
  390. }
  391. return false;
  392. }
  393.  
  394. //manages .locked property of srsitem
  395. /*This function manages the .locked and manualLock properties of srsitem
  396. .locked is a real time evaluation of the item (is any of the kanji in the word locked?)
  397. .manualLock will return 'no' if .locked has ever returned 'no'.
  398. This is to stop items being locked again after they have been unlocked if any
  399. of the kanji used falls below the unlock threshold
  400. (eg. if the 勉 in 勉強 falls back to apprentice, we do not want to lock up 勉強 again.)
  401. */
  402. function setLocks(srsitem){
  403. //functions:
  404. // isKanjiLocked(srsitem)
  405. var kanjiList = jQuery.parseJSON(localStorage.getItem('User-KanjiList'));
  406. srsitem.locked = isKanjiLocked(srsitem, kanjiList);
  407. //seems to be returning "DB" all the time
  408. //once manualLock is "no" it stays "no"
  409. //this is to stop an item from locking up again if
  410. //any of the component kanji fall below 'guru'
  411. if (srsitem.manualLock !== "no"){
  412. srsitem.manualLock = srsitem.locked;
  413. }
  414.  
  415. scriptLog("setting locks for "+ srsitem.kanji +": locked: "+srsitem.locked+", manualLock: "+ srsitem.manualLock);
  416.  
  417. return srsitem;
  418. }
  419.  
  420. function isKanjiLocked(srsitem, kanjiList){
  421. //scriptLog("isKanjiLocked(srsitem, kanjiList)");
  422. //functions:
  423. // getCompKanji(srsitem.kanji, kanjiList)
  424. //item unlocked by default
  425. //may have no kanji, only unlocked kanji will get through the code unflagged
  426. var locked = "no";
  427. scriptLog("initialise 'locked': "+ locked);
  428. //get the kanji characters in the word.
  429. var componentList = getCompKanji(srsitem.kanji, kanjiList);
  430. // eg: componentList = getCompKanji("折り紙", kanjiList);
  431. // componentList = [{"kanji": "折", "srs": "guru"}, {"kanji": "紙", "srs": "apprentice"}]
  432. scriptLog("isKanjiLocked -> kanjiList = "+JSON.stringify(kanjiList));
  433. scriptLog("components: "+JSON.stringify(componentList));
  434.  
  435. for (var i=0; i < componentList.length; i++){
  436.  
  437. //look for locked kanji in list
  438. if (componentList[i].srs == "apprentice" || componentList[i].srs == "noServerResp"){
  439.  
  440. //----could be apprentice etc.
  441. //Simple: lock is 'yes'
  442. locked = "yes";
  443. // "yes": item will be locked while there is no database connection.
  444. // if the server response indicates that it has been unlocked, only then will it be available for review
  445.  
  446. scriptLog("test srs for apprentice etc. 'locked': "+ locked);
  447. scriptLog(componentList[i].kanji +": "+componentList[i].srs +" -> "+ locked);
  448.  
  449. break; // as soon as one kanji is locked, the whole item is locked
  450. }
  451.  
  452. //DB locks get special state
  453. if (componentList[i].srs == "noMatchWK" || componentList[i].srs == "noMatchGuppy"){
  454.  
  455. locked = "DB";
  456. //"DB" : database limitations, one of two things
  457. //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
  458. //b. the kanji isn't in the database and the user is a turtle --could change if more kanji added.
  459.  
  460. scriptLog("test srs for unmatched kanji. 'locked': "+ locked);
  461. scriptLog(componentList[i].kanji +": "+componentList[i].srs +" -> "+ locked);
  462. scriptLog(kanjiList);
  463. }
  464.  
  465. } //for char in componentList
  466. scriptLog("out of character loop");
  467. //locked will be either "yes","no", or "DB"
  468. return locked;
  469. }
  470. //--------
  471.  
  472. /*
  473. * Edit Items
  474. */
  475. unsafeWindow.WKSS_edit = function () {
  476. generateEditOptions();
  477. $("#edit").show();
  478. //hide other windows
  479. $("#export").hide();
  480. $("#import").hide();
  481. $("#add").hide();
  482. $("#selfstudy").hide();
  483. };
  484.  
  485. $("body").append(" \
  486. <div id=\"edit\" class=\"WKSS\"> \
  487. <form id=\"editForm\"> \
  488. <h1>Edit your Vocab</h1> \
  489. <select id=\"editWindow\" size=\"8\"></select>\
  490. <input type=\"text\" id=\"editItem\" name=\"\" size=\"40\" placeholder=\"Select vocab, click edit, change and save!\">\
  491. \
  492. <p id=\"editStatus\">Ready to edit..</p>\
  493. <button id=\"EditEditBtn\" type=\"button\">Edit</button>\
  494. <button id=\"EditSaveBtn\" type=\"button\">Save</button> \
  495. <button id=\"EditDeleteBtn\" type=\"button\" title=\"Delete selected item\">Delete</button> \
  496. <button id=\"EditDeleteAllBtn\" type=\"button\" title=\"本当にやるの?\">Delete All</button> \
  497. <button id=\"EditCloseBtn\" type=\"button\">Close window</button> \
  498. <button id=\"ResetLevelsBtn\" type=\"button\">Reset levels</button> \
  499. </form> \
  500. </div>"
  501. );
  502. $("#edit").hide();
  503.  
  504. $("#ResetLevelsBtn").click(function () {
  505. var srslist = getSrsList();
  506. if (srslist) {
  507. for (var i = 0; i < srslist.length; i++){
  508. srslist[i].level = 0;
  509. setSrsList(srslist[i]);
  510. }
  511. // scriptLog("generating review list because: levels were just reset, review needs to be reinitialised");
  512.  
  513. // generateReviewList();
  514. }
  515. });
  516.  
  517.  
  518. $("#EditEditBtn").click(function () {
  519. //get handle for 'select' area
  520. var select = document.getElementById("editWindow");
  521. //get the index for the currently selected item
  522. var index = select.options[select.selectedIndex].value;
  523. var vocabList = getVocList();
  524.  
  525. document.getElementById("editItem").value = JSON.stringify(vocabList[index]);
  526. document.getElementById("editItem").name = index; //using name to save the index
  527. $("#editStatus").text('Loaded item to edit');
  528. });
  529.  
  530. $("#EditSaveBtn").click(function () {
  531. if ($("#editItem").val().length !== 0) {
  532. try {
  533. var index = document.getElementById("editItem").name;
  534. var item = JSON.parse(document.getElementById("editItem").value.toLowerCase());
  535.  
  536. var fullList = getFullList();
  537. scriptLog(JSON.stringify(fullList));
  538. if ((!checkItem(item)) || (checkForDuplicates(fullList,item) && fullList[index].kanji !== item.kanji)) {
  539. $("#editStatus").text('Invalid item or duplicate!');
  540. return;
  541. }
  542. var srslist = getSrsList();
  543.  
  544. fullList[index] = item;
  545.  
  546. fullList[index].date = srslist[index].date;
  547. fullList[index].level = srslist[index].level;
  548. fullList[index].locked = srslist[index].locked;
  549. fullList[index].manualLock = srslist[index].manualLock;
  550.  
  551. localStorage.setItem('User-Vocab', JSON.stringify(fullList));
  552.  
  553. generateEditOptions();
  554. $("#editStatus").text('Saved changes!');
  555. document.getElementById("editItem").value = "";
  556. document.getElementById("editItem").name = "";
  557. }
  558. catch (e) {
  559. $("#editStatus").text(e);
  560. }
  561. }
  562. });
  563.  
  564. $("#EditDeleteBtn").click(function () {
  565. //select options element window
  566. var select = document.getElementById("editWindow");
  567.  
  568. //index of selected item
  569. var item = select.options[select.selectedIndex].value;
  570.  
  571. //fetch JSON strings from storage and convert them into Javascript literals
  572. var vocabList = getFullList();
  573. //starting at selected index, remove 1 entry (the selected index).
  574. if (item > -1) {
  575. if (vocabList !== null){
  576. vocabList.splice(item, 1);
  577. }
  578. }
  579.  
  580. //yuck
  581. if (vocabList.length !== 0) {
  582. localStorage.setItem('User-Vocab', JSON.stringify(vocabList));
  583. }
  584. else {
  585. localStorage.removeItem('User-Vocab');
  586. }
  587.  
  588. updateEditGUI();
  589.  
  590. $("#editStatus").text('Item deleted!');
  591. });
  592.  
  593. function updateEditGUI(){
  594.  
  595. generateEditOptions();
  596. document.getElementById("editItem").value = "";
  597. document.getElementById("editItem").name = "";
  598.  
  599. // scriptLog("generating review list because: items were edited, rebuilding review");
  600.  
  601. // generateReviewList();
  602. }
  603.  
  604. $("#EditDeleteAllBtn").click(function () {
  605. var deleteAll = confirm("Are you sure you want to delete all entries?");
  606. if (deleteAll) {
  607. //drop local storage
  608. localStorage.removeItem('User-Vocab');
  609. updateEditGUI();
  610.  
  611. $("#editStatus").text('All items deleted!');
  612. }
  613. });
  614.  
  615.  
  616. $("#EditCloseBtn").click(function () {
  617. $("#edit").hide();
  618. $("#editForm")[0].reset();
  619. $("#editStatus").text('Ready to edit..');
  620. });
  621.  
  622. //retrieve values from storage to populate 'select' menu
  623. function generateEditOptions() {
  624. var select = document.getElementById('editWindow');
  625. //clear the menu (blank slate)
  626. while (select.firstChild) {
  627. select.removeChild(select.firstChild);
  628. }
  629.  
  630. //check for items to add
  631. if (localStorage.getItem('User-Vocab')) {
  632.  
  633. //retrieve from local storage
  634. var vocabList = getVocList();
  635. var srslist = getSrsList();
  636.  
  637. //build option string
  638. for (var i = 0; i < vocabList.length; i++) {
  639. //form element to save string
  640. var opt = document.createElement('option');
  641. //dynamic components of string
  642. //how long since the item was last accessed/saved
  643. var dif = Date.now() - srslist[i].date;
  644. //how much time required for this level
  645. var hour = srsintervals[srslist[i].level];
  646. var review = "";
  647. //no future reviews if burned
  648. if(srslist[i].level == 8) {
  649. review = "never";
  650. }
  651. //calculate next relative review time
  652. //more time has elapsed than required for the level
  653. else if(hour <= dif) {
  654. review = hour - dif ;
  655. }
  656. else {
  657.  
  658. //calulate minutes to next review
  659. var mins = (Math.floor((hour-dif)/(mstohour/60))+1);
  660.  
  661. //review in more than 60min
  662. if (mins >=60) {
  663. //calculate hours to next review
  664. var hours = Math.floor(mins/60);
  665. //review in more than 24hrs
  666. if(hours >= 24) {
  667. //calculate days to next review
  668. var days = Math.floor(hours/24);
  669. //review is in more than 7 days
  670. if(days >= 7) {
  671. //calculate weeks to next review
  672. var weeks = Math.floor(days/7);
  673. if(weeks > 4) {
  674. //calculate months to next review
  675. var months = Math.floor(weeks/4);
  676. review = "in ~" + months + "mon";
  677. }
  678. else {
  679. review = "in ~" + weeks + "w";
  680. }
  681. }
  682. else {
  683. review = "in ~" + days + "d";
  684. }
  685. }
  686. else {
  687. review = "in ~" + hours + "h";
  688. }
  689. }
  690. else {
  691. review = "in ~" + mins + "min";
  692. }
  693. }//end if review is not 'never' or 'now'
  694. var text = vocabList[i].kanji + " & " + vocabList[i].reading + " & " + vocabList[i].meaning + " (" + srslevels[srslist[i].level] + " - Review: " + review + ") Locked: " + srslist[i].manualLock;
  695. opt.value = i;
  696. opt.innerHTML = text;
  697. select.appendChild(opt);
  698. }
  699. }
  700. }
  701.  
  702. /*
  703. * Export
  704. */
  705. unsafeWindow.WKSS_export = function () {
  706. $("#export").show();
  707. //hide other windows
  708. $("#add").hide();
  709. $("#import").hide();
  710. $("#edit").hide();
  711. $("#selfstudy").hide();
  712. };
  713.  
  714. $("body").append(' \
  715. <div id="export" class="WKSS"> \
  716. <form id="exportForm"> \
  717. <h1>Export Items</h1> \
  718. <textarea cols="50" rows="18" id="exportArea" placeholder="Export your stuff! Sharing is caring ;)"></textarea> \
  719. \
  720. <p id="exportStatus">Ready to export..</p> \
  721. <button id="ExportItemsBtn" type="button">Export Items</button>\
  722. <button id="ExportSelectAllBtn" type="button">Select All</button>\
  723. <button id="ExportCloseBtn" type="button">Close window</button> \
  724. </form> \
  725. </div>'
  726. );
  727. $("#export").hide();
  728.  
  729.  
  730. $("#ExportItemsBtn").click(function () {
  731.  
  732. if (localStorage.getItem('User-Vocab')) {
  733. $("#exportForm")[0].reset();
  734. var vocabList = getVocList();
  735. $("#exportArea").text(JSON.stringify(vocabList));
  736. $("#exportStatus").text("Copy this text and share it with others!");
  737. }
  738. else {
  739. $("#exportStatus").text("Nothing to export yet :(");
  740. }
  741. });
  742.  
  743. $("#ExportSelectAllBtn").click(function () {
  744. if ($("#exportArea").val().length !== 0) {
  745. select_all($("#exportArea"));
  746. $("#exportStatus").text("Don't forget to CTRL + C!");
  747. }
  748. });
  749.  
  750. $("#ExportCloseBtn").click(function () {
  751. $("#export").hide();
  752. $("#exportForm")[0].reset();
  753. $("#exportArea").text("");
  754. $("#exportStatus").text('Ready to export..');
  755. });
  756.  
  757. /*
  758. * Import
  759. */
  760. unsafeWindow.WKSS_import = function () {
  761. $("#import").show();
  762. //hide other windows
  763. $("#add").hide();
  764. $("#export").hide();
  765. $("#edit").hide();
  766. $("#selfstudy").hide();
  767. };
  768.  
  769. $("body").append(' \
  770. <div id="import" class="WKSS"> \
  771. <form id="importForm"> \
  772. <h1>Import Items</h1> \
  773. <textarea cols="50" rows="18" id="importArea" placeholder="Paste your stuff and hit the import button! Use with caution!"></textarea> \
  774. \
  775. <p id="importStatus">Ready to import..</p> \
  776. <button id="ImportItemsBtn" type="button">Import Items</button>\
  777. <button id="ImportCloseBtn" type="button">Close window</button> \
  778. </form> \
  779. </div> '
  780. );
  781. $("#import").hide();
  782.  
  783.  
  784. $("#ImportItemsBtn").click(function () {
  785.  
  786. if ($("#importArea").val().length !== 0) {
  787. try {
  788. var add = JSON.parse($("#importArea").val().toLowerCase());
  789.  
  790. if (!checkAdd(add)) {
  791. $("#importStatus").text("No valid input (duplicates?)!");
  792. return;
  793. }
  794.  
  795. var newlist;
  796. var srslist = [];
  797. if (localStorage.getItem('User-Vocab')) {
  798. var vocabList = getVocList();
  799. srslist = getSrsList();
  800. newlist = vocabList.concat(add);
  801. }
  802. else {
  803. newlist = add;
  804. }
  805. for(var i = 0; i < add.length; i++) {
  806. //var srsitem = {};
  807. // srsitem.kanji = add[i].kanji;
  808. // srsitem.level = 0;
  809. // srsitem.date = Date.now();
  810. //--------------------------------------------------------------------------------------------------------
  811. //srsitem.manualLock = "";
  812. //srsitem = setLocks(srsitem);
  813. //--------------------------------------------------------------------------------------------------------
  814. add[i].level = 0;
  815. add[i].date = Date.now();
  816. //--------------------------------------------------------------------------------------------------------
  817. add[i].manualLock = "";
  818. add[i] = setLocks(add[i]);
  819. //--------------------------------------------------------------------------------------------------------
  820. setVocList(add[i]);
  821. }
  822. // localStorage.setItem('User-Vocab', JSON.stringify(newlist));
  823. // localStorage.setItem('User-SRS', JSON.stringify(srslist));
  824.  
  825.  
  826.  
  827.  
  828. $("#importStatus").text("Import successful!");
  829.  
  830. // scriptLog("generating review list because: Items imported, need to rebuild review");
  831.  
  832. //generateReviewList() ;
  833. $("#importForm")[0].reset();
  834. $("#importArea").text("");
  835.  
  836. }
  837. catch (e) {
  838. $("#importStatus").text("Parsing Error!");
  839. scriptLog(e);
  840. }
  841.  
  842. }
  843. else {
  844. $("#importStatus").text("Nothing to import :( Please paste your stuff first");
  845. }
  846. });
  847.  
  848. $("#ImportCloseBtn").click(function () {
  849. $("#import").hide();
  850. $("#importForm")[0].reset();
  851. $("#importArea").text("");
  852. $("#importStatus").text('Ready to import..');
  853. });
  854.  
  855. /*
  856. * Review Items
  857. */
  858. unsafeWindow.WKSS_review = function () {
  859. //is there a session waiting in storage?
  860. if(sessionStorage.getItem('User-Review')) {
  861. scriptLog("There is a session ready "+JSON.stringify(sessionStorage.getItem('User-Review')));
  862. //show the selfstudy window
  863. $("#selfstudy").show();
  864.  
  865. //hide other windows
  866. $("#add").hide();
  867. $("#export").hide();
  868. $("#edit").hide();
  869. $("#import").hide();
  870.  
  871. startReview();
  872. }
  873. };
  874.  
  875. $("body").append(' \
  876. <div id="selfstudy" class="WKSS">\
  877. <h1>Review</h1>\
  878. <div id="wkss-kanji">\
  879. <span id="rev-kanji"></span>\
  880. </div><div id="wkss-type">\
  881. <span id="rev-type"></span><br />\
  882. </div><div id="wkss-solution">\
  883. <span id="rev-solution"></span>\
  884. </div><div id="wkss-input">\
  885. <input type="text" id="rev-input" size="40" placeholder="">\
  886. </div><span id="rev-index" style="display: none;"></span>\
  887. \
  888. <form id="audio-form">\
  889. <button id="AudioButton" type="button">Play audio</button>\
  890. <button id="SelfstudyCloseBtn" type="button">Close window</button>\
  891. </form>\
  892. <div id="rev-audio" style="display:none;"></div>\
  893. </div> '
  894. );
  895. $("#selfstudy").hide();
  896.  
  897. $("#SelfstudyCloseBtn").click(function () {
  898. $("#selfstudy").hide();
  899. $("#rev-input").val("");
  900. reviewActive = false;
  901. });
  902.  
  903. $("#AudioButton").click(function () {
  904. OpenInNewTab(document.getElementById('rev-audio').innerHTML);
  905. });
  906.  
  907. function OpenInNewTab(url )
  908. {
  909. var win=window.open(url, '_blank');
  910. win.focus();
  911. }
  912.  
  913. function playAudio() {
  914. var kanji = document.getElementById('rev-kanji').innerHTML;
  915. var kana = (document.getElementById('rev-solution').innerHTML.split(/[,、]+\s*/))[0];
  916. document.getElementById('rev-audio').innerHTML = "";
  917. document.getElementById('audio-form').action = "";
  918. //document.getElementById('AudioButton').disabled = true;
  919. if(! kanji.match(/[a-zA-Z]+/i) && ! kana.match(/[a-zA-Z]+/i)) {
  920. kanji = encodeURIComponent(kanji);
  921. kana = encodeURIComponent(kana);
  922. var i;
  923. var newkanji = "";
  924. for(i = 1; i < kanji.length; i = i+3) {
  925. newkanji = newkanji.concat(kanji[i-1]);
  926. newkanji = newkanji.concat('2');
  927. newkanji = newkanji.concat('5');
  928. newkanji = newkanji.concat(kanji[i]);
  929. newkanji = newkanji.concat(kanji[i+1]);
  930. }
  931. var newkana = "";
  932. for(i = 1; i < kana.length; i = i+3) {
  933. newkana = newkana.concat(kana[i-1]);
  934. newkana = newkana.concat('2');
  935. newkana = newkana.concat('5');
  936. newkana = newkana.concat(kana[i]);
  937. newkana = newkana.concat(kana[i+1]);
  938. }
  939. var url = "http://www.csse.monash.edu.au/~jwb/audiock.swf?u=kana=" + newkana + "%26kanji=" + newkanji;
  940. scriptLog("Audio URL: " + url);
  941. document.getElementById('AudioButton').disabled = false;
  942. document.getElementById('rev-audio').innerHTML = url;
  943. }
  944. }
  945.  
  946.  
  947. function generateReviewList() {
  948. //don't interfere with an active session
  949. if (reviewActive){
  950. return;
  951. }
  952. scriptLog("generateReviewList()");
  953. // function generateReviewList() builds a review session and updates the html menu to show number waiting.
  954. var numReviews = 0;
  955. var reviewList = [];
  956. //check to see if there is vocab already in offline storage
  957. if (localStorage.getItem('User-Vocab')) {
  958. var vocabList = getFullList();
  959. scriptLog("var vocabList = localStorage.getItem('User-Vocab') (" + JSON.stringify(vocabList)+")");
  960. var srsList = getSrsList();
  961. scriptLog("var srsList = localStorage.getItem('User-Vocab') (" + JSON.stringify(srsList)+")");
  962. var now = Date.now();
  963. //for each vocab in storage, get the amount of time until next review
  964. for(var i = 0; i < vocabList.length; i++) {
  965. var dif = now - vocabList[i].date;
  966. // if (time passed is greater than required for level) and (level not burned) and (srs is unlocked)
  967. // (if item is up for review)
  968. if((srsintervals[vocabList[i].level] <= dif) && vocabList[i].level < 8 && (vocabList[i].manualLock !== "yes" )){
  969. // count vocab up for review
  970. numReviews++;
  971.  
  972. // add item-meaning object to reviewList
  973. var revItem = {};
  974. revItem.kanji = vocabList[i].kanji;
  975. revItem.type = "Meaning";
  976. revItem.solution = vocabList[i].meaning;
  977. revItem.index = i;
  978. reviewList.push(revItem);
  979. // reading is optional, if there is a reading for the vocab, add its object.
  980. if (vocabList[i].reading[0] !== "") {
  981. scriptLog("vocabList["+i+"].reading === '"+ vocabList[i].reading+"'");
  982. var revItem2 = {};
  983. revItem2.kanji = vocabList[i].kanji;
  984. revItem2.type = "Reading";
  985. revItem2.solution = vocabList[i].reading;
  986. // item and item2 are matched by mutual index
  987. revItem2.index = i;
  988. reviewList.push(revItem2);
  989. }
  990. }//end if item is up for review
  991. }// end iterate through vocablist
  992. }// end if localStorage
  993. if (reviewList.length !== 0){
  994. //shuffle the deck
  995. reviewList = shuffle(reviewList);
  996. //store reviewList in current session
  997. sessionStorage.setItem('User-Review', JSON.stringify(reviewList));
  998. scriptLog("sessionStorage.setItem('User-Review, "+JSON.stringify(reviewList)+")");
  999. }else{
  1000. scriptLog("reviewList is empty, see?: "+JSON.stringify(reviewList));
  1001. }
  1002. var strReviews = numReviews.toString();
  1003. //....sure, why not.
  1004. if (numReviews > 42) {
  1005. strReviews = "42+"; //hail the crabigator!
  1006. }
  1007. // return the number of reviews
  1008. scriptLog(numReviews.toString() +" reviews created");
  1009. document.getElementById('user-review').innerHTML = "Review (" + strReviews + ")";
  1010.  
  1011. }
  1012.  
  1013. //global to keep track of when a review is in session.
  1014. var reviewActive = false;
  1015.  
  1016. function startReview() {
  1017. scriptLog("startReview()");
  1018. reviewActive = true;
  1019. //get the review 'list' from session storage, line up the first item in queue
  1020. var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
  1021. nextReview(reviewList);
  1022. }
  1023.  
  1024. function nextReview(reviewList) {
  1025. scriptLog("reviewList = "+sessionStorage.getItem('User-Review'));
  1026. //uses functions:
  1027. // wanakana.bind/unbind
  1028.  
  1029. var item = reviewList[0];
  1030. document.getElementById('rev-kanji').innerHTML = item.kanji;
  1031. document.getElementById('rev-type').innerHTML = item.type;
  1032. document.getElementById('rev-solution').innerHTML = item.solution;
  1033. document.getElementById('rev-index').innerHTML = item.index;
  1034.  
  1035. //initialise the input field
  1036. $("#rev-input").focus();
  1037. $("#rev-input").removeClass("error");
  1038. $("#rev-input").removeClass("correct");
  1039. $("#rev-input").val("");
  1040.  
  1041. //check for alphabet letters and decide to bind or unbind wanakana
  1042. if (item.solution[0].match(/[a-zA-Z]+/i)) {
  1043. wanakana.unbind(document.getElementById('rev-input'));
  1044. $('#rev-input').attr('placeholder','Your response');
  1045. document.getElementById('rev-type').innerHTML = "Meaning";
  1046. }
  1047. else {
  1048. wanakana.bind(document.getElementById('rev-input'));
  1049. $('#rev-input').attr('placeholder','答え');
  1050. document.getElementById('rev-type').innerHTML = "Reading";
  1051. }
  1052. playAudio();
  1053. }
  1054.  
  1055. function storeSession(correct) {
  1056. //functions:
  1057. // updateSRS(var correct)
  1058. //initialize statsList variable
  1059. var statsList = [];
  1060. //is there a statsList stored in the session
  1061. if (sessionStorage.getItem('User-Stats')) {
  1062. //assign it to variable
  1063. statsList = JSON.parse(sessionStorage.getItem('User-Stats'));
  1064. //iterate through statsList
  1065. for (var i = 0; i < statsList.length; i++) {
  1066. //filter statsList for wrong answers
  1067. if (statsList[i].correct === false) {
  1068. // if the statslist member's kanji is the one currently in the prompt window
  1069. if ((statsList[i].kanji.trim() === document.getElementById('rev-kanji').innerHTML.trim()) &&
  1070. // AND is it asking for the same thing (meaning vs reading)
  1071. (statsList[i].type.trim() === document.getElementById('rev-type').innerHTML.trim())) {
  1072. //-the item currently being tested is present in 'stats' so it has already been asked this session
  1073. //-it was answered incorrectly
  1074. //-it was this exact question matching both the kanji and whether it is meaning or reading
  1075. //do not store this answer in stats, it was already wrong and can't get wronger
  1076. //we do not want to overwrite it as 'correct'
  1077. return;
  1078. }//if this is a question previously asked
  1079. }//filter for wrong answers
  1080. }//loop through all items already answered in session
  1081. }//check if any questions answered yet
  1082. //there may or may not be User-Stats in storage
  1083. //if there are none, statsList is empty
  1084. //if there are, statslist is populated with items that are not the current answer
  1085. //so 'stats' can be safely pushed onto the session statsList
  1086. var stats = {};
  1087. stats.correct = correct;
  1088. stats.kanji = document.getElementById('rev-kanji').innerHTML;
  1089. stats.type = document.getElementById('rev-type').innerHTML;
  1090. updateSRS(correct);
  1091.  
  1092. statsList.push(stats);
  1093. sessionStorage.setItem('User-Stats', JSON.stringify(statsList));
  1094.  
  1095. }
  1096.  
  1097. function showResults() {
  1098. scriptLog("showResults()");
  1099. if (sessionStorage.getItem('User-Stats')) {
  1100. var statsList = JSON.parse(sessionStorage.getItem('User-Stats'));
  1101. for (var i = 0; i < statsList.length; i++) {
  1102.  
  1103. if (statsList[i].correct === true) {
  1104. if (statsList[i].type.trim() === "Meaning".trim()){
  1105. document.getElementById("stats-m").innerHTML += "<span class=\"rev-correct\">" + statsList[i].kanji + "</span>";
  1106. }else{
  1107. document.getElementById("stats-r").innerHTML += "<span class=\"rev-correct\">" + statsList[i].kanji + "</span>";
  1108. }
  1109. }
  1110. else {
  1111. if (statsList[i].type.trim() === "Meaning".trim()){
  1112. document.getElementById("stats-m").innerHTML += "<span class=\"rev-error\">" + statsList[i].kanji + "</span>";
  1113. }else{
  1114. document.getElementById("stats-r").innerHTML += "<span class=\"rev-error\">" + statsList[i].kanji + "</span>";
  1115. }
  1116. }
  1117. }
  1118. }
  1119. //clear session
  1120. sessionStorage.clear();
  1121. reviewActive = false;
  1122. // scriptLog("generating review list because: Session is over creating the next one if it exists");
  1123. // generateReviewList();
  1124. }
  1125.  
  1126. $("body").append(' \
  1127. <div id="resultwindow" class="WKSS"> \
  1128. <h1>Review Results</h1>\
  1129. <h2>Reading</h2>\
  1130. <div id="stats-r"></div>\
  1131. <h2>Meaning</h2>\
  1132. <div id="stats-m"></div>\
  1133. <button id="ReviewresultsCloseBtn" type="button">Close window</button>\
  1134. </div> '
  1135. );
  1136.  
  1137. $("#resultwindow").hide();
  1138.  
  1139. $("#ReviewresultsCloseBtn").click(function () {
  1140. $("#resultwindow").hide();
  1141. document.getElementById("stats-r").innerHTML = "";
  1142. document.getElementById("stats-m").innerHTML = "";
  1143. });
  1144.  
  1145.  
  1146. //declare global values for keyup event
  1147. //is an answer being submitted?
  1148. var submit = true;
  1149.  
  1150. //jquery keyup event
  1151. $("#rev-input").keyup(function (e) {
  1152. //functions:
  1153. // inputCorrect()
  1154. //check if key press was 'enter' (keyCode 13) on the way up
  1155. //and keystate true (answer being submitted)
  1156. //and cursor is focused in reviewfield
  1157. if (e.keyCode == 13 && submit === true) {
  1158.  
  1159. //check for input, do nothing if none
  1160. if($("#rev-input").val().length === 0){
  1161. return;
  1162. }
  1163. //disable input after submission
  1164. //document.getElementById('rev-input').disabled = true;
  1165. //was the input correct?
  1166. var correct = inputCorrect();
  1167.  
  1168. if (correct) {
  1169. //highlight in (default) green
  1170. $("#rev-input").addClass("correct");
  1171. //show answer
  1172. $("#rev-solution").addClass("info");
  1173. }
  1174. else {
  1175. //highight in red
  1176. $("#rev-input").addClass("error");
  1177. //show answer
  1178. $("#rev-solution").addClass("info");
  1179. }
  1180. //remove from sessionList if correct
  1181. //( maybe this should be done in inputCorrect() )
  1182. if (correct) {
  1183. scriptLog("correct answer");
  1184. var sessionList = JSON.parse(sessionStorage.getItem('User-Review'));
  1185. if (sessionList !== null){
  1186. var oldlen = sessionList.length;
  1187. sessionList.splice(0, 1);
  1188. scriptLog("sessionList.length: "+ oldlen +" -> "+sessionList.length);
  1189. //replace shorter (by one) sessionList to session
  1190. if (sessionList.length !== 0) {
  1191. scriptLog("sessionList.length: "+ sessionList.length);
  1192. sessionStorage.setItem('User-Review', JSON.stringify(sessionList));
  1193. }
  1194. else {
  1195. //reveiw over, delete sessionlist from session
  1196. scriptLog("sessionStorage.removeItem('User-Review')");
  1197. sessionStorage.removeItem('User-Review');
  1198. }
  1199. }else{
  1200. scriptLog("Error: no session found");
  1201. }
  1202. }else{
  1203. scriptLog("wrong answer");
  1204. }
  1205. scriptLog("store session");
  1206. storeSession(correct);
  1207. //playAudio();
  1208. scriptLog("store session complete");
  1209.  
  1210. //answer submitted, next 'enter' proceeds with script
  1211. submit = false;
  1212. }
  1213. else if (e.keyCode == 13 && submit === false) {
  1214. scriptLog("keystat = " + submit);
  1215.  
  1216. //there are still more reviews in session?
  1217. if (sessionStorage.getItem('User-Review')) {
  1218. scriptLog("found a 'User-Review': " + sessionStorage.getItem('User-Review') );
  1219.  
  1220. setTimeout(function () {
  1221. scriptLog("refreshing reviewList from storage");
  1222. var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
  1223. //cue up first remaining review
  1224. nextReview(reviewList);
  1225. scriptLog("checking for empty reviewList");
  1226. if (reviewList.length !== 0){
  1227. // sessionStorage.setItem('User-Review', JSON.stringify(reviewList));
  1228. }else{
  1229. scriptLog("session over. reviewList: "+JSON.stringify(reviewList));
  1230. sessionStorage.removeItem("User-Review");
  1231. }
  1232. // document.getElementById('rev-input').disabled = true;
  1233. $("#rev-solution").removeClass("info");
  1234. $("#selfstudy").hide().fadeIn('fast');
  1235.  
  1236. }, 1);
  1237. }
  1238. else {
  1239. // no review stored in session, review is over
  1240. setTimeout(function () {
  1241. $("#selfstudy").hide();
  1242. //document.getElementById('rev-input').disabled = false;
  1243. $("#rev-solution").removeClass("info");
  1244. scriptLog("showResults");
  1245. showResults();
  1246. $("#resultwindow").show();
  1247. scriptLog("showResults completed");
  1248. }, 1);
  1249. }
  1250. submit = true;
  1251. scriptLog("submit = " + submit);
  1252.  
  1253. }
  1254. });
  1255.  
  1256.  
  1257. function updateSRS(correct) {
  1258. var srslist = getSrsList();
  1259. var reviewList = JSON.parse(sessionStorage.getItem('User-Review'));
  1260. var index = document.getElementById('rev-index').innerHTML;
  1261. if(reviewList && checkForDuplicates(reviewList,srslist[index])) {
  1262. scriptLog("Other item found, save status");
  1263. if(sessionStorage.getItem(srslist[index].kanji)) {
  1264. correct = correct && JSON.parse(sessionStorage.getItem(srslist[index].kanji));
  1265. }
  1266. sessionStorage.setItem(srslist[index].kanji, JSON.stringify(correct));
  1267. return;
  1268. }
  1269. else if(sessionStorage.getItem(srslist[index].kanji)) {
  1270. correct = correct && JSON.parse(sessionStorage.getItem(srslist[index].kanji));
  1271. sessionStorage.removeItem(srslist[index].kanji);
  1272. }
  1273. var now = Date.now();
  1274. if(correct === true) {
  1275. srslist[index].level++;
  1276. }
  1277. else
  1278. {
  1279. if(srslist[index].level !== 0)
  1280. srslist[index].level--;
  1281. }
  1282. srslist[index].date = now;
  1283. scriptLog("updateSRS - " + srslist[index].kanji + " - new level: " + srslist[index].level + " date: " + now);
  1284. setSrsList(srslist[index]);
  1285.  
  1286. }
  1287.  
  1288. function inputCorrect() {
  1289. var input = $("#rev-input").val().toLowerCase();
  1290. var solution = document.getElementById('rev-solution').innerHTML.split(/[,、]+\s*/);
  1291. var correct = 0;
  1292. var returnvalue = false;
  1293. scriptLog("Input: " + input);
  1294. // also allow entering of both solutions at once, if available
  1295. if(solution.length == 2) {
  1296. solution[2] = solution[0] + ", " + solution[1];
  1297. solution[3] = solution[1] + ", " + solution[0];
  1298. }
  1299. for(var i = 0; i < solution.length; i++) {
  1300. solution[i] = solution[i].toLowerCase();
  1301. correct = 0;
  1302. var threshold = Math.floor(solution[i].length / errorAllowance);
  1303. if(document.getElementById('rev-type').innerHTML == "Reading") {
  1304. threshold = 0;
  1305. }
  1306. scriptLog("Checking " + solution[i] + " with threshold: " + threshold);
  1307. var j;
  1308. if (input.length <= solution[i].length) {
  1309.  
  1310. if(input.length < (solution[i].length - threshold)) {
  1311. returnvalue = returnvalue || false;
  1312. scriptLog("false at if branch " + input.length + " < " + (solution[i].length - threshold));
  1313. }
  1314. else {
  1315. j = input.length;
  1316. while (j--) {
  1317. if (input[j] == solution[i][j]) {
  1318. correct++;
  1319. }
  1320. }
  1321.  
  1322. if ((solution[i].length - threshold) <= correct) {
  1323. returnvalue = returnvalue || true;
  1324. scriptLog("true at if branch " + (solution[i].length - threshold) + " <= " + correct);
  1325. }
  1326. }
  1327. }
  1328. else {
  1329. if(input.length > (solution[i].length + threshold)) {
  1330. returnvalue = returnvalue || false;
  1331. scriptLog("false at else branch " + input.length + " > " + (solution[i].length + threshold));
  1332. }
  1333. else {
  1334. j = solution[i].length;
  1335. while (j--) {
  1336. if (input[j] == solution[i][j]) {
  1337. correct++;
  1338. }
  1339. }
  1340. if ((solution[i].length - threshold) <= correct) {
  1341. returnvalue = returnvalue || true;
  1342. scriptLog("true at else branch " + (solution[i].length - threshold) + " <= " + correct);
  1343. }
  1344. }
  1345. }
  1346. }
  1347.  
  1348. scriptLog("Returning " + returnvalue);
  1349. return returnvalue;
  1350. }
  1351.  
  1352. /*
  1353. * Adds the Button
  1354. */
  1355. function addUserVocabButton() {
  1356. scriptLog("addUserVocabButton()");
  1357. //Functions (indirect)
  1358. // WKSS_add()
  1359. // WKSS_edit()
  1360. // WKSS_export()
  1361. // WKSS_import()
  1362. // WKSS_lock()
  1363. // WKSS_review()
  1364. var nav = document.getElementsByClassName('nav');
  1365. scriptLog("generating review list because: initialising script and populating reviews");
  1366.  
  1367. if (nav) {
  1368. nav[2].innerHTML = nav[2].innerHTML + "\n\
  1369. <li class=\"dropdown custom\">\n\
  1370. <a class=\"dropdown-toggle custom\" data-toggle=\"dropdown\" href=\"#\" onclick=\"generateReviewList();\">\n\
  1371. <span lang=\"ja\">自習</span>\n\
  1372. Self-Study <i class=\"icon-chevron-down\"></i>\n\
  1373. </a>\n\
  1374. <ul class=\"dropdown-menu\" id=\"WKSS_dropdown\">\n\
  1375. <li class=\"nav-header\">Customize</li>\n\
  1376. <li><a id=\"click\" href=\"#\" onclick=\"WKSS_add();\">Add</a></li>\n\
  1377. <li><a href=\"#\" onclick=\"WKSS_edit();\">Edit</a></li>\n\
  1378. <li><a href=\"#\" onclick=\"WKSS_export();\">Export</a></li>\n\
  1379. <li><a href=\"#\" onclick=\"WKSS_import();\">Import</a></li>\n\
  1380. <!--// <li><a href=\"#\" onclick=\"WKSS_lock();\">Server Settings</a></li>//-->\n\
  1381. <li class=\"nav-header\">Learn</li>\n\
  1382. <li><a id=\"user-review\" href=\"#\" onclick=\"WKSS_review();\">Please wait...</a></li>\n\
  1383. </ul>\n\
  1384. </li>";
  1385. //generateReviewList();
  1386. }
  1387. }
  1388.  
  1389.  
  1390.  
  1391. /*
  1392. * Prepares the script
  1393. */
  1394. function scriptInit() {
  1395. scriptLog("scriptInit()");
  1396. //functions:
  1397. // addUserVocabButton()
  1398. // logError(err)
  1399. scriptLog("Initializing Wanikani UserVocab Script!");
  1400.  
  1401. GM_addStyle(".custom .dropdown-menu {background-color: #DBA901 !important;}");
  1402. GM_addStyle(".custom .dropdown-menu:after {border-bottom-color: #DBA901 !important;");
  1403. GM_addStyle(".custom .dropdown-menu:before {border-bottom-color: #DBA901 !important;");
  1404. GM_addStyle(".open .dropdown-toggle.custom {background-color: #FFC400 !important;}");
  1405. GM_addStyle(".custom .dropdown-menu a:hover {background-color: #A67F00 !important;}");
  1406. GM_addStyle(".custom:hover {color: #FFC400 !important;}");
  1407. GM_addStyle(".custom:hover span {border-color: #FFC400 !important;}");
  1408. GM_addStyle(".custom:focus {color: #FFC400 !important;}");
  1409. GM_addStyle(".custom:focus span {border-color: #FFC400 !important;}");
  1410. GM_addStyle(".open .custom span {border-color: #FFFFFF !important;}");
  1411. GM_addStyle(".open .custom {color: #FFFFFF !important}");
  1412.  
  1413. GM_addStyle(" \
  1414. .WKSS {\
  1415. background: #FFF;\
  1416. padding: 20px 30px 20px 30px;\
  1417. font: 12px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
  1418. color: #888;\
  1419. text-shadow: 1px 1px 1px #FFF;\
  1420. border:1px solid #DDD;\
  1421. border-radius: 5px;\
  1422. -webkit-border-radius: 5px;\
  1423. -moz-border-radius: 5px;\
  1424. box-shadow: 10px 10px 5px #888888;\
  1425. }\
  1426. .WKSS h1 {\
  1427. font: 25px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
  1428. padding: 0px 0px 10px 40px;\
  1429. display: block;\
  1430. border-bottom: 1px solid #DADADA;\
  1431. margin: -10px -30px 30px -30px;\
  1432. color: #888;\
  1433. }\
  1434. .WKSS h1>span {\
  1435. display: block;\
  1436. font-size: 11px;\
  1437. }\
  1438. .WKSS label {\
  1439. display: block;\
  1440. margin: 0px 0px 5px;\
  1441. }\
  1442. .WKSS label>span {\
  1443. float: left;\
  1444. width: 80px;\
  1445. text-align: right;\
  1446. padding-right: 10px;\
  1447. margin-top: 10px;\
  1448. color: #333;\
  1449. font-family: \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
  1450. font-weight: bold;\
  1451. }\
  1452. .WKSS input[type=\"text\"], .WKSS input[type=\"email\"], .WKSS textarea{\
  1453. border: 1px solid #CCC;\
  1454. color: #888;\
  1455. height: 20px;\
  1456. margin-bottom: 16px;\
  1457. margin-right: 6px;\
  1458. margin-top: 2px;\
  1459. outline: 0 none;\
  1460. padding: 6px 12px;\
  1461. width: 80%;\
  1462. border-radius: 4px;\
  1463. line-height: normal !important;\
  1464. -webkit-border-radius: 4px;\
  1465. -moz-border-radius: 4px;\
  1466. font: normal 14px/14px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
  1467. -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1468. box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1469. -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1470. }\
  1471. .WKSS select {\
  1472. border: 1px solid #CCC;\
  1473. color: #888;\
  1474. outline: 0 none;\
  1475. padding: 6px 12px;\
  1476. height: 160px !important;\
  1477. width: 95%;\
  1478. border-radius: 4px;\
  1479. -webkit-border-radius: 4px;\
  1480. -moz-border-radius: 4px;\
  1481. font: normal 14px/14px \"ヒラギノ角ゴ Pro W3\", \"Hiragino Kaku Gothic Pro\",Osaka, \"メイリオ\", Meiryo, \"MS Pゴシック\", \"MS PGothic\", sans-serif;\
  1482. -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1483. box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1484. -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\
  1485. #background: #FFF url('down-arrow.png') no-repeat right;\
  1486. #background: #FFF url('down-arrow.png') no-repeat right);\
  1487. appearance:none;\
  1488. -webkit-appearance:none;\
  1489. -moz-appearance: none;\
  1490. text-indent: 0.01px;\
  1491. text-overflow: '';\
  1492. }\
  1493. .WKSS textarea{\
  1494. height:100px;\
  1495. }\
  1496. .WKSS button {\
  1497. background: #FFF;\
  1498. border: 1px solid #CCC;\
  1499. padding: 10px 25px 10px 25px;\
  1500. color: #333;\
  1501. border-radius: 4px;\
  1502. }\
  1503. .WKSS button:disabled {\
  1504. background: #EBEBEB;\
  1505. border: 1px solid #CCC;\
  1506. padding: 10px 25px 10px 25px;\
  1507. color: #333;\
  1508. border-radius: 4px;\
  1509. }\
  1510. .WKSS button:hover:enabled {\
  1511. color: #333;\
  1512. background-color: #EBEBEB;\
  1513. border-color: #ADADAD;\
  1514. } \
  1515. .WKSS button:hover:disabled {\
  1516. cursor: default\
  1517. } \
  1518. .error {border-color:#F00 !important; color: #F00 !important;}\
  1519. .correct {border-color:#0F0 !important; color: #0F0 !important;}\
  1520. .info {border-color:#696969 !important; color: #696969 !important;}\
  1521. .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;}\
  1522. .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;}"
  1523. );
  1524. GM_addStyle("\
  1525. #add {\
  1526. position:absolute;\
  1527. float:right;\
  1528. top:50%;\
  1529. left:50%;\
  1530. width:" + addWindowWidth + "px;\
  1531. height:" + addWindowHeight + "px; \
  1532. margin-left:-" + addWindowWidth/2 + "px; \
  1533. margin-top:-" + addWindowHeight/2 + "px; \
  1534. }\
  1535. ");
  1536. GM_addStyle("\
  1537. #export, #import {\
  1538. position:absolute;\
  1539. float:right;\
  1540. top:50%;\
  1541. left:50%;\
  1542. width:" + exportImportWindowWidth + "px;\
  1543. height:" + exportImportWindowHeight + "px; \
  1544. margin-left:-" + exportImportWindowWidth/2 + "px; \
  1545. margin-top:-" + exportImportWindowHeight/2 + "px; \
  1546. }\
  1547. ");
  1548. GM_addStyle("\
  1549. #edit {\
  1550. position:absolute;\
  1551. float:right;\
  1552. top:50%;\
  1553. left:50%;\
  1554. width:" + editWindowWidth + "px;\
  1555. height:" + editWindowHeight + "px; \
  1556. margin-left:-" + editWindowWidth/2 + "px; \
  1557. margin-top:-" + editWindowHeight/2 + "px; \
  1558. }\
  1559. ");
  1560. GM_addStyle("\
  1561. #selfstudy {\
  1562. position:absolute;\
  1563. float:right;\
  1564. top:50%;\
  1565. left:50%;\
  1566. width:" + studyWindowWidth + "px;\
  1567. height:" + studyWindowHeight + "px; \
  1568. margin-left:-" + studyWindowWidth/2 + "px; \
  1569. margin-top:-" + studyWindowHeight/2 + "px; \
  1570. }\
  1571. ");
  1572. GM_addStyle("\
  1573. #resultwindow {\
  1574. position:absolute;\
  1575. float:right;\
  1576. top:50%;\
  1577. left:50%;\
  1578. width:" + resultWindowWidth + "px;\
  1579. height:" + resultWindowHeight + "px; \
  1580. margin-left:-" + resultWindowWidth/2 + "px; \
  1581. margin-top:-" + resultWindowHeight/2 + "px; \
  1582. }"
  1583. );
  1584. GM_addStyle("\
  1585. #SelfstudyCloseBtn {\
  1586. margin-top: 35px;\
  1587. left: 45%;\
  1588. position: relative;\
  1589. display: inline !important;\
  1590. -webkit-margin-before: 50px;\
  1591. }");
  1592. GM_addStyle("\
  1593. #AudioButton {\
  1594. margin-top: 35px;\
  1595. left: 10%;\
  1596. position: relative;\
  1597. display: inline !important;\
  1598. -webkit-margin-before: 50px;\
  1599. }");
  1600. GM_addStyle("\
  1601. #ReviewresultsCloseBtn {\
  1602. top: 0%;\
  1603. left: 75%;\
  1604. position: relative;\
  1605. -webkit-margin-before: 50px;\
  1606. }");
  1607. GM_addStyle("\
  1608. #wkss-kanji, #rev-kanji {\
  1609. text-align:center !important;\
  1610. font-size:50px !important;\
  1611. background-color: #9400D3 !important;\
  1612. color: #FFFFFF !important;\
  1613. border-radius: 10px 10px 0px 0px;\
  1614. }");
  1615. GM_addStyle("\
  1616. #wkss-solution, #rev-solution {\
  1617. text-align: center !important;\
  1618. font-size:30px !important;\
  1619. color: #FFFFFF;\
  1620. padding: 2px;\
  1621. }");
  1622. GM_addStyle("\
  1623. #wkss-type, #rev-type {\
  1624. text-align:center !important;\
  1625. font-size:24px !important;\
  1626. background-color: #696969 !important;\
  1627. color: #FFFFFF !important;\
  1628. border-radius: 0px 0px 10px 10px;\
  1629. }");
  1630. GM_addStyle("\
  1631. #wkss-input, #rev-input {\
  1632. text-align:center !important;\
  1633. font-size:40px !important;\
  1634. height: 60px !important;\
  1635. line-height: normal !important;\
  1636. }");
  1637. // Set up buttons
  1638. try {
  1639. if (typeof(Storage) !== "undefined") {
  1640. addUserVocabButton();
  1641. //provide warning to users trying to use the (incomplete) script.
  1642. scriptLog("this script is still incomplete: \n\
  1643. I recommend you install the original script by shudouken at \n\
  1644. http://userscripts-mirror.org/scripts/show/381435");
  1645. }
  1646. else {
  1647. scriptLog("Wanikani Self-Study: Your browser does not support localStorage.. Sorry :(");
  1648. }
  1649. }
  1650. catch (err) {
  1651. logError(err);
  1652. }
  1653. }
  1654.  
  1655. /*
  1656. * Helper Functions/Variables
  1657. */
  1658.  
  1659. function isEmpty(value) {
  1660. return (typeof value === "undefined" || value === null);
  1661. }
  1662.  
  1663. function select_all(obj) {
  1664. //eval can be harmful
  1665. var text_val = eval(obj);
  1666. scriptLog(text_val);
  1667. text_val.focus();
  1668. text_val.select();
  1669. }
  1670.  
  1671. function checkAdd(add) {
  1672. var i;
  1673. if(localStorage.getItem('User-Vocab')) {
  1674. var vocabList = getVocList();
  1675. for (i = 0; i < add.length; i++) {
  1676. if (!checkItem(add[i]) || checkForDuplicates(vocabList,add[i]))
  1677. return false;
  1678. }
  1679. }
  1680. else {
  1681. for (i = 0; i < add.length; i++) {
  1682. if (!checkItem(add[i]))
  1683. return false;
  1684. }
  1685. }
  1686. return true;
  1687. }
  1688.  
  1689. function checkItem(add) {
  1690. if (isEmpty(add.kanji) || isEmpty(add.meaning) || isEmpty(add.reading))
  1691. return false;
  1692. if((Object.prototype.toString.call(add.meaning) !== '[object Array]')||
  1693. (Object.prototype.toString.call(add.reading) !== '[object Array]'))
  1694. return false;
  1695.  
  1696. return true;
  1697. }
  1698.  
  1699. function shuffle(array) {
  1700. var currentIndex = array.length;
  1701. var temporaryValue;
  1702. var randomIndex;
  1703.  
  1704. // While there remain elements to shuffle...
  1705. while (0 !== currentIndex) {
  1706.  
  1707. // Pick a remaining element...
  1708. randomIndex = Math.floor(Math.random() * currentIndex);
  1709. currentIndex -= 1;
  1710.  
  1711. // And swap it with the current element.
  1712. temporaryValue = array[currentIndex];
  1713. array[currentIndex] = array[randomIndex];
  1714. array[randomIndex] = temporaryValue;
  1715. }
  1716.  
  1717. return array;
  1718. }
  1719.  
  1720. /*
  1721. * Error handling
  1722. * Can use 'error.stack', not cross-browser (though it should work on Firefox and Chrome)
  1723. */
  1724. function logError(error) {
  1725. scriptLog("logError(error)");
  1726. var stackMessage = "";
  1727. if ("stack" in error)
  1728. stackMessage = "\n\tStack: " + error.stack;
  1729.  
  1730. scriptLog("WKSS: Error: " + error.name + "\n\tMessage: " + error.message + stackMessage);
  1731. console.error("WKSS: Error: " + error.name + "\n\tMessage: " + error.message + stackMessage);
  1732. }
  1733.  
  1734.  
  1735. //*****Ethan's Functions*****
  1736.  
  1737. function getServerResp(APIkey){
  1738. //functions:
  1739. // refreshLocks()
  1740. // generateReviewList()
  1741. scriptLog("creating empty kanjiList");
  1742. if (APIkey !== "YOUR_API_HERE"){
  1743. 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");
  1744.  
  1745. if (xhrk){
  1746. xhrk.onreadystatechange = function() {
  1747. var kanjiList = [];
  1748. if (xhrk.readyState == 4){
  1749. var resp = JSON.parse(xhrk.responseText);
  1750. //test for error
  1751. for(var i=0;i<resp.requested_information.length;i++){
  1752. //push response onto kanjilist variable
  1753. if (resp.requested_information[i].user_specific !== null){
  1754. kanjiList.push({"character": resp.requested_information[i].character, "srs": resp.requested_information[i].user_specific.srs});
  1755. }
  1756. }
  1757. //store kanjiList for offline and event use
  1758. //Overwrite previous kanjiList
  1759. scriptLog("Server responded with new kanjiList: \n"+JSON.stringify(kanjiList));
  1760. localStorage.setItem('User-KanjiList', JSON.stringify(kanjiList));
  1761.  
  1762. //update locks in localStorage
  1763.  
  1764. refreshLocks();
  1765.  
  1766.  
  1767. }
  1768. };
  1769.  
  1770. xhrk.send();
  1771. }
  1772. }
  1773. else {
  1774. //dummy server response for testing.
  1775.  
  1776. setTimeout(function () {
  1777. var kanjiList = [];
  1778. scriptLog("creating dummy response");
  1779. kanjiList.push({"character": "猫", "srs": "noServerResp"});
  1780. var SRS = prompt("enter SRS for 子", "guru");
  1781. kanjiList.push({"character": "子", "srs": SRS});
  1782. kanjiList.push({"character": "品", "srs": "guru"});
  1783. kanjiList.push({"character": "供", "srs": "guru"});
  1784. kanjiList.push({"character": "本", "srs": "guru"});
  1785. kanjiList.push({"character": "聞", "srs": "apprentice"});
  1786. kanjiList.push({"character": "人", "srs": "enlightened"});
  1787. kanjiList.push({"character": "楽", "srs": "burned"});
  1788. kanjiList.push({"character": "相", "srs": "guru"});
  1789. kanjiList.push({"character": "卒", "srs": "noMatchWK"});
  1790. kanjiList.push({"character": "無", "srs": "noMatchGuppy"});
  1791.  
  1792. scriptLog("Server responded with dummy kanjiList: \n"+JSON.stringify(kanjiList));
  1793. localStorage.setItem('User-KanjiList', JSON.stringify(kanjiList));
  1794. //update locks in localStorage
  1795. refreshLocks();
  1796.  
  1797.  
  1798. }, 10000);
  1799. }
  1800. }
  1801.  
  1802. function getComponents(kanji){
  1803. scriptLog("getComponents(kanji)");
  1804. //functions:
  1805. // none
  1806. //takes in a string and returns an array containing only the kanji characters in the string.
  1807. var components = [];
  1808. for (var c = 0; c < kanji.length; c++){
  1809. if(/^[\u4e00-\u9faf]+$/.test(kanji[c])) {
  1810. components.push(kanji[c]);
  1811. }
  1812. }
  1813. return components;
  1814. }
  1815.  
  1816. function refreshLocks(){
  1817. //functions:
  1818. // setLocks(srsitem)
  1819.  
  1820. //scriptLog("refreshLocks()");
  1821. if (localStorage.getItem('User-Vocab')) {
  1822. scriptLog("srsList found in local storage");
  1823.  
  1824. var srsList = getSrsList();
  1825. scriptLog("Error?");
  1826. for (var i=0;i<srsList.length;i++){
  1827. scriptLog("No");
  1828. srsList[i] = setLocks(srsList[i]);
  1829. setSrsList(srsList[i]);
  1830. }
  1831. // scriptLog("Setting new locks: "+JSON.stringify(srsList));
  1832. }else{
  1833. scriptLog("no srs storage found");
  1834. }
  1835. }
  1836.  
  1837. function getCompKanji(vocab, kanjiList){
  1838. scriptLog("getCompKanji(vocab, kanjiList)");
  1839. //functions:
  1840. // getComponents(vocab)
  1841.  
  1842. var compSRS = [];
  1843. var kanjiReady = false; //indicates if the kanjiList has been populated
  1844. var userGuppy = false; //indicated if kanjiList has less than 100 items
  1845. //has the server responded yet
  1846. if (kanjiList.length > 0){
  1847. scriptLog("kanjiList is > 0");
  1848. kanjiReady = true;
  1849. //is there less than 100 kanji in the response
  1850. if (kanjiList.length < 100){
  1851. scriptLog("kanjiList is < 100");
  1852. userGuppy = true;
  1853. }
  1854. }
  1855. //break the item down into its component kanji, discards katakana, hiragana etc
  1856. var components = getComponents(vocab);
  1857. scriptLog(vocab+": "+JSON.stringify(components));
  1858. //for each kanji character component
  1859. // this is the outer loop since there will be far less of them than kanjiList
  1860. for(var i = 0; i < components.length; i++){
  1861.  
  1862. var matched = false;
  1863. //for each kanji returned by the server
  1864. for(var j=0; j<kanjiList.length; j++){
  1865. //if the kanji returned by the server matches the character in the item
  1866. if (kanjiList[j].character == components[i]){
  1867. compSRS[i] = {"kanji": components[i], "srs": kanjiList[j].srs};
  1868. matched = true;
  1869. break; //kanji found: 'i' is its position in item components; 'j' is its postion in the 'kanjiList' server response
  1870. }
  1871. }
  1872. if (matched === false){ // character got all the way through kanjiList without a match.
  1873. if (kanjiReady){ //was there a server response?
  1874. if (userGuppy){ //is the user a guppy (kanji probably matches a turtles response)
  1875. scriptLog("matched=false, kanjiList.length: "+kanjiList.length);
  1876. compSRS[i] = {"kanji": components[i], "srs": "noMatchGuppy"};
  1877. }else{ //user is a turtle, kanji must not have been added to WK (yet)
  1878. scriptLog("matched=false, kanjiList.length: "+kanjiList.length);
  1879. compSRS[i] = {"kanji": components[i], "srs": "noMatchWK"};
  1880. }
  1881. }else{
  1882. scriptLog("matched=false, kanjiReady=false, noServerResp");
  1883. compSRS[i] = {"kanji": components[i], "srs": "noServerResp"};
  1884. }
  1885. }
  1886. }
  1887. return compSRS; // compSRS is an array of the kanji with SRS values for each kanji component.
  1888. // eg. 折り紙:
  1889. // compSRS = [{"kanji": "折", "srs": "guru"}, {"kanji": "紙", "srs": "apprentice"}]
  1890. }
  1891.  
  1892. function createCORSRequest(method, url){
  1893. var xhr = new XMLHttpRequest();
  1894. if ("withCredentials" in xhr){
  1895. xhr.open(method, url, true);
  1896. } else if (typeof XDomainRequest !== "undefined"){
  1897. xhr = new XDomainRequest();
  1898. xhr.open(method, url);
  1899. } else {
  1900. xhr = null;
  1901. }
  1902. return xhr;
  1903. }
  1904.  
  1905. $(document).ready(function(){
  1906. /*
  1907. * Start the script
  1908. */
  1909.  
  1910. //update kanjiList on connection
  1911. getServerResp(APIkey);
  1912. scriptInit();
  1913. });