// ==UserScript==
// @name WaniKani Quick Info KT
// @namespace https://www.wanikani.com
// @description Shows available information while waiting for the server response. Originally by Ethan, Modified.
// @version 0.1.4
// @include http*://www.wanikani.com/review/session*
// @run-at document-end
// @grant none
// ==/UserScript==
/*global $, console, additionalContent, Notes, UserSynonyms, setTimeout*/
/*jslint plusplus: true */
(function () {
'use strict';
additionalContent.itemInfo = function () { return; }; // replace with empty function. will prevent execute on firefox, do nothing on chrome.
function newItemInfo() {
// takes in a string and returns an array containing only the kanji characters in the string.
function getComponents(vocab) {
var c, components = [];
for (c = 0; c < vocab.length; c++) {
if (/^[\u4e00-\u9faf]+$/.test(vocab[c])) {
components.push(vocab[c]);
}
}
return components;
}
function regex(key) {
switch (key) {
case "radical":
return (/\[(?:radical)\]/gi);
case "kanji":
return (/\[(?:kanji)\]/gi);
case "vocabulary":
return (/\[(?:vocabulary)\]/gi);
case "meaning":
return (/\[(?:meaning)\]/gi);
case "reading":
return (/\[(?:reading)\]/gi);
case "ja":
return (/\[(?:ja)\]/gi);
case "closeTagSpan":
return (/\[\/(?:radical|kanji|vocabulary|meaning|reading|ja)\]/gi);
}
}
function htmlify(str) {
var i, keys;
str = str.replace("\r\n", "<br><br>");
keys = [ "radical", "kanji", "vocabulary", "meaning", "reading", "ja", "closeTagSpan" ];
function filter(str, key) {
switch (key) {
case "ja":
return str.replace(regex(key), '<span lang="ja">');
case "closeTagSpan":
return str.replace(regex(key), "</span>");
default:
return str.replace(regex(key), '<span class="highlight-' + key + '">');
}
}
for (i = 0; i < keys.length; i++) {
str = filter(str, keys[i]);
}
return str;
}
function getRkvId(item) {
var rkvType = item.rad ? "r" : item.kan ? "k" : item.voc ? "v" : undefined;
return (rkvType + item.id);
}
function checkCurItem(readMeanType1, rkvId1) {
var readMeanType2, rkvId2;
readMeanType2 = $.jStorage.get("questionType");
rkvId2 = getRkvId($.jStorage.get("currentItem"));
return (readMeanType1 === readMeanType2 && rkvId1 === rkvId2);
}
// normal meaning/name always start with a Capital letter, but injected synonyms are always lowercase only
// answerChecker injects cur.syn into cur.en when validating a 'meaning' question
function meaningSynFilterFmt(meaningList) {
return $.grep(meaningList, function (str) {
return str.toLowerCase() !== str;
}).join(", ");
}
$("#option-item-info").off("click"); // delete all click handlers for this element
$("#option-item-info").click(function () {
// replace the main button toggler code
if ($("#option-item-info").hasClass("active")) { // if info button .active
$("#additional-content li").removeClass("active"); // deactivate all buttons
$("#information").hide().children().hide(); // hide "#information" and child "#item-info"
$("html, body").animate({ scrollTop: 0 }, 200); // scroll up
} else if ($("#user-response").is(":disabled")) { // info button NOT .active and not waiting for answer
$("#information").show().children().hide(); // show "#information" and hide any visible children
$("html, body").animate({ scrollTop: $("#user-response").offset().top - 10 }, 200); // scroll down
$("#additional-content li").removeClass("active"); // deactivate all buttons
$("#option-item-info").addClass("active"); // make info button active
$("#item-info").fadeIn(300); // show #item-info
}
// this replaces another click handler that we deleted
if ($("#user-response").is(":disabled")) {
$("#answer-exception").remove();
}
var rkvId, itemInfoCol1, itemInfoCol2, cur, readMeanType, itemInfo, loading;
itemInfo = $("#item-info");
cur = $.jStorage.get("currentItem");
readMeanType = $.jStorage.get("questionType");
rkvId = getRkvId(cur);
if (itemInfo.is(":visible") && (itemInfo.data("question-type") !== readMeanType || itemInfo.data("id") !== rkvId)) {
itemInfoCol1 = $("#item-info-col1");
itemInfoCol2 = $("#item-info-col2");
itemInfoCol1.empty();
itemInfoCol2.empty();
loading = '<img height="40px" src="https://cdn-staging.wanikani.com/assets/v03/loading-100x100-3f623bf48901ac45b1f40168644dbc901efd41b633b9f6d95a5d3ecda3f2111d.gif">'; //put crabigator here
if (cur.rad) {
itemInfoCol1.html('<section id="item-info-name"><h2>Name</h2>' + meaningSynFilterFmt(cur.en) + '</section><section class="user-synonyms"><h2>User Synonyms</h2></section>');
itemInfoCol2.html('<section id="item-info-mnemonic"><h2>Mnemonics</h2>' + loading + '</section><section id="note-meaning"></section>');
UserSynonyms.load("radical", cur.syn, cur.id, true);
$("#all-info").hide();
$.getJSON("/json/radical/" + cur.id, function (radJSON) {
if (!checkCurItem(readMeanType, rkvId)) {
return; // item has since changed, ignore this data.
}
radJSON.mnemonic = htmlify(radJSON.mnemonic);
itemInfoCol1.find("section#item-info-name").html("<h2>Name</h2>" + radJSON.en);
itemInfoCol2.find("section#item-info-mnemonic").html("<h2>Mnemonics</h2>" + radJSON.mnemonic);
itemInfoCol2.find("section#note-meaning").html("<h2>Name Note</h2>");
Notes.add("radical", "meaning", cur.id, radJSON.meaning_note, itemInfoCol2.find("section#note-meaning"));
itemInfo.data("id", rkvId);
itemInfo.data("question-type", readMeanType);
}).fail(function () {
$("#information-offline").show();
});
} else if (cur.kan) {
(function () {
var emphReading;
emphReading = cur.emph === "onyomi" ? cur.on : cur.kun;
itemInfoCol1.html('<section id="item-info-meaning"><h2>Meanings</h2>' + meaningSynFilterFmt(cur.en) + '</section><section class="user-synonyms"><h2>User Synonyms</h2></section><section id="item-info-reading"><h2>Important Readings (' + cur.emph + ")</h2>" + emphReading + '</section><section id="related-items"><h2>Radical Combination</h2>' + loading + "</section>");
itemInfoCol2.html('<section id="item-info-meaning-mnemonic"><h2>Meaning Mnemonic</h2>' + loading + '</section><section id="note-meaning"></section><section id="item-info-reading-mnemonic"><h2>Reading Mnemonic</h2>' + loading + '</section><section id="note-reading"></section>');
UserSynonyms.load("kanji", cur.syn, cur.id, true);
if (readMeanType === "meaning") {
$("#item-info-reading, #item-info-reading-mnemonic, #note-reading").hide();
} else {
$("#item-info-meaning, #item-info-meaning-mnemonic, #note-meaning, .user-synonyms").hide();
}
$("#all-info").show();
}());
$.getJSON("/json/kanji/" + cur.id, function (kanJSON) {
if (!checkCurItem(readMeanType, rkvId)) {
return; // item has since changed, ignore this data.
}
var relatedRadChar, relatedRad, i, relatedRadStr, relatedRadList;
kanJSON.meaning_mnemonic = htmlify(kanJSON.meaning_mnemonic);
kanJSON.reading_mnemonic = htmlify(kanJSON.reading_mnemonic);
kanJSON.meaning_hint = htmlify(kanJSON.meaning_hint);
kanJSON.reading_hint = htmlify(kanJSON.reading_hint);
relatedRadStr = "";
relatedRadList = kanJSON.related;
for (i = 0; i < relatedRadList.length; i++) {
relatedRad = relatedRadList[i];
if (relatedRad.custom_font_name) {
relatedRadChar = '<i class="radical-' + relatedRad.custom_font_name + '"></i>';
} else if (/\.png/i.test(relatedRad.rad)) {
relatedRadChar = '<img src="https://s3.amazonaws.com/s3.wanikani.com/images/radicals/' + relatedRad.rad + '">';
} else {
relatedRadChar = relatedRad.rad;
}
relatedRadStr += '<li><a title="View radical information page" target="_blank" href="/radicals/' + relatedRad.slug + '"><span class="radical" lang="ja">' + relatedRadChar + "</span> " + relatedRad.en.split(",")[0] + "</li>";
}
itemInfoCol1.find("section#item-info-meaning").html("<h2>Meanings</h2>" + kanJSON.en);
itemInfoCol1.find("section#related-items").html('<h2>Radical Combination</h2><ul class="radical">' + relatedRadStr + "</ul>");
itemInfoCol2.find("section#item-info-meaning-mnemonic").html('<h2>Meaning Mnemonic</h2>' + kanJSON.meaning_mnemonic + '<blockquote><h3><i class="icon-question-sign"></i> HINT</h3>' + kanJSON.meaning_hint + '</blockquote>');
itemInfoCol2.find("section#item-info-reading-mnemonic").html('<h2>Reading Mnemonic</h2>' + kanJSON.reading_mnemonic + '<blockquote><h3><i class="icon-question-sign"></i> HINT</h3>' + kanJSON.reading_hint + '</blockquote>');
itemInfoCol2.find("section#note-meaning").html("<h2>Meaning Note</h2>");
Notes.add("kanji", "meaning", cur.id, kanJSON.meaning_note, itemInfoCol2.find("section#note-meaning"));
itemInfoCol2.find("section#note-reading").html("<h2>Reading Note</h2>");
Notes.add("kanji", "reading", cur.id, kanJSON.reading_note, itemInfoCol2.find("section#note-reading"));
itemInfo.data("id", rkvId);
itemInfo.data("question-type", readMeanType);
}).fail(function () {
$("#information-offline").show();
});
} else if (cur.voc) {
(function () {
var i, relatedKanStr, relatedKanList, relatedKan;
relatedKanStr = "";
relatedKanList = getComponents(cur.voc);
for (i = 0; i < relatedKanList.length; i++) {
relatedKan = relatedKanList[i];
relatedKanStr += '<li><a title="View kanji information page" target="_blank" href="/kanji/' + relatedKan + '"><span class="kanji" lang="ja">' + relatedKan + "</span></a></li>";
}
itemInfoCol1.html('<section id="item-info-meaning"><h2>Meanings</h2>' + meaningSynFilterFmt(cur.en) + '</section><section class="user-synonyms"><h2>User Synonyms</h2></section><section id="item-info-reading"><h2>Reading</h2>' + cur.kana.join(", ") + '</section><section id="part-of-speech"><h2>Part of Speech</h2>' + loading + '</section><section id="related-items"><h2>Related Kanji</h2><ul class="kanji">' + relatedKanStr + "</ul></section>");
itemInfoCol2.html('<section id="item-info-meaning-mnemonic"><h2>Meaning Explanation</h2>' + loading + '</section><section id="note-meaning"></section><section id="item-info-reading-mnemonic"><h2>Reading Explanation</h2>' + loading + '</section><section id="note-reading"></section><section id="item-info-context-sentences"><h2>Context Sentence</h2>' + loading + "</section>");
UserSynonyms.load("vocabulary", cur.syn, cur.id, true);
if (readMeanType === "meaning") {
$("#item-info-reading, #item-info-reading-mnemonic, #note-reading").hide();
} else {
$("#item-info-meaning, #item-info-meaning-mnemonic, #note-meaning, .user-synonyms").hide();
}
$("#all-info").show();
}());
$.getJSON("/json/vocabulary/" + cur.id, function (vocJSON) {
if (!checkCurItem(readMeanType, rkvId)) {
return; // item has since changed, ignore this data.
}
var sentenceStr, relatedKan, i, relatedKanStr, relatedKanList;
vocJSON.meaning_explanation = htmlify(vocJSON.meaning_explanation);
vocJSON.reading_explanation = htmlify(vocJSON.reading_explanation);
relatedKanStr = "";
relatedKanList = vocJSON.related;
for (i = 0; i < relatedKanList.length; i++) {
relatedKan = relatedKanList[i];
relatedKanStr += '<li><a title="View kanji information page" target="_blank" href="/kanji/' + relatedKan.slug + '"><span class="kanji" lang="ja">' + relatedKan.kan + "</span> " + relatedKan.en + "</a></li>";
}
if (vocJSON.sentences.length === 0) {
sentenceStr = "<p>N/A</p>";
} else {
sentenceStr = "<p>" + vocJSON.sentences[0][0] + "</p><p>" + vocJSON.sentences[0][1] + "</p>";
}
itemInfoCol1.find("section#item-info-meaning").html("<h2>Meanings</h2>" + vocJSON.en);
itemInfoCol1.find("section#item-info-reading").html("<h2>Reading</h2>" + vocJSON.kana);
itemInfoCol1.find("section#part-of-speech").html("<h2>Part of Speech</h2>" + vocJSON.part_of_speech);
itemInfoCol1.find("section#related-items").html('<h2>Related Kanji</h2><ul class="kanji">' + relatedKanStr + "</ul>");
itemInfoCol2.find("section#item-info-meaning-mnemonic").html('<h2>Meaning Explanation</h2>' + vocJSON.meaning_explanation);
itemInfoCol2.find("section#item-info-reading-mnemonic").html('<h2>Reading Explanation</h2>' + vocJSON.reading_explanation);
itemInfoCol2.find("section#note-meaning").html("<h2>Meaning Note</h2>");
Notes.add("vocabulary", "meaning", cur.id, vocJSON.meaning_note, itemInfoCol2.find("section#note-meaning"));
itemInfoCol2.find("section#note-reading").html("<h2>Reading Note</h2>");
Notes.add("vocabulary", "reading", cur.id, vocJSON.reading_note, itemInfoCol2.find("section#note-reading"));
itemInfoCol2.find("section#item-info-context-sentences").html("<h2>Context Sentence</h2>" + sentenceStr);
itemInfo.data("id", rkvId);
itemInfo.data("question-type", readMeanType);
}).fail(function () {
$("#information-offline").show();
});
}
}
});
}
// delayed load to make sure this loads after (and so can replace) the original
setTimeout(function () {
additionalContent.itemInfo = newItemInfo; // replace empty function with new function
additionalContent.itemInfo(); // execute it
console.log('WaniKani Quick Info KT: loaded replacement function');
}, 100);
console.log('WaniKani Quick Info KT: script load end');
}());