您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extends the WaniKani lattice and allows to choose what data to display
// ==UserScript== // @name WaniKani Lattice Extension // @description Extends the WaniKani lattice and allows to choose what data to display // @namespace irx.wanikani.lattice_extension // @include https://www.wanikani.com/ // @include https://www.wanikani.com/dashboard // @include https://www.wanikani.com/review/session* // @include https://www.wanikani.com/lattice/radicals/meaning // @include https://www.wanikani.com/lattice/kanji/combined // @include https://www.wanikani.com/lattice/kanji/meaning // @include https://www.wanikani.com/lattice/kanji/reading // @include https://www.wanikani.com/lattice/vocabulary/combined // @include https://www.wanikani.com/lattice/vocabulary/meaning // @include https://www.wanikani.com/lattice/vocabulary/reading // @version 3.0 // @copyright 2016, Ingo Radax // @license MIT; http://opensource.org/licenses/MIT // @grant none // ==/UserScript== // LZString library from pieroxy // http://pieroxy.net/blog/pages/lz-string/index.html // Licence: WTFPL (http://www.wtfpl.net/) var LZString = (function() { // private property var f = String.fromCharCode; var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; var baseReverseDic = {}; function getBaseValue(alphabet, character) { if (!baseReverseDic[alphabet]) { baseReverseDic[alphabet] = {}; for (var i=0 ; i<alphabet.length ; i++) { baseReverseDic[alphabet][alphabet.charAt(i)] = i; } } return baseReverseDic[alphabet][character]; } var LZString = { compressToBase64 : function (input) { if (input == null) return ""; var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);}); switch (res.length % 4) { // To produce valid Base64 default: // When could this happen ? case 0 : return res; case 1 : return res+"==="; case 2 : return res+"=="; case 3 : return res+"="; } }, decompressFromBase64 : function (input) { if (input == null) return ""; if (input == "") return null; return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); }); }, compressToUTF16 : function (input) { if (input == null) return ""; return LZString._compress(input, 15, function(a){return f(a+32);}) + " "; }, decompressFromUTF16: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; }); }, //compress into uint8array (UCS-2 big endian format) compressToUint8Array: function (uncompressed) { var compressed = LZString.compress(uncompressed); var buf=new Uint8Array(compressed.length*2); // 2 bytes per character for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) { var current_value = compressed.charCodeAt(i); buf[i*2] = current_value >>> 8; buf[i*2+1] = current_value % 256; } return buf; }, //decompress from uint8array (UCS-2 big endian format) decompressFromUint8Array:function (compressed) { if (compressed===null || compressed===undefined){ return LZString.decompress(compressed); } else { var buf=new Array(compressed.length/2); // 2 bytes per character for (var i=0, TotalLen=buf.length; i<TotalLen; i++) { buf[i]=compressed[i*2]*256+compressed[i*2+1]; } var result = []; buf.forEach(function (c) { result.push(f(c)); }); return LZString.decompress(result.join('')); } }, //compress into a string that is already URI encoded compressToEncodedURIComponent: function (input) { if (input == null) return ""; return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);}); }, //decompress from an output of compressToEncodedURIComponent decompressFromEncodedURIComponent:function (input) { if (input == null) return ""; if (input == "") return null; input = input.replace(/ /g, "+"); return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); }); }, compress: function (uncompressed) { return LZString._compress(uncompressed, 16, function(a){return f(a);}); }, _compress: function (uncompressed, bitsPerChar, getCharFromInt) { if (uncompressed == null) return ""; var i, value, context_dictionary= {}, context_dictionaryToCreate= {}, context_c="", context_wc="", context_w="", context_enlargeIn= 2, // Compensate for the first entry which should not count context_dictSize= 3, context_numBits= 2, context_data=[], context_data_val=0, context_data_position=0, ii; for (ii = 0; ii < uncompressed.length; ii += 1) { context_c = uncompressed.charAt(ii); if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) { context_dictionary[context_c] = context_dictSize++; context_dictionaryToCreate[context_c] = true; } context_wc = context_w + context_c; if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) { context_w = context_wc; } else { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { if (context_w.charCodeAt(0)<256) { for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } } value = context_w.charCodeAt(0); for (i=0 ; i<8 ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } else { value = 1; for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1) | value; if (context_data_position ==bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = 0; } value = context_w.charCodeAt(0); for (i=0 ; i<16 ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } // Add wc to the dictionary. context_dictionary[context_wc] = context_dictSize++; context_w = String(context_c); } } // Output the code for w. if (context_w !== "") { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { if (context_w.charCodeAt(0)<256) { for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } } value = context_w.charCodeAt(0); for (i=0 ; i<8 ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } else { value = 1; for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1) | value; if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = 0; } value = context_w.charCodeAt(0); for (i=0 ; i<16 ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } } // Mark the end of the stream value = 2; for (i=0 ; i<context_numBits ; i++) { context_data_val = (context_data_val << 1) | (value&1); if (context_data_position == bitsPerChar-1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } // Flush the last char while (true) { context_data_val = (context_data_val << 1); if (context_data_position == bitsPerChar-1) { context_data.push(getCharFromInt(context_data_val)); break; } else context_data_position++; } return context_data.join(''); }, decompress: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); }, _decompress: function (length, resetValue, getNextValue) { var dictionary = [], next, enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = [], i, w, bits, resb, maxpower, power, c, data = {val:getNextValue(0), position:resetValue, index:1}; for (i = 0; i < 3; i += 1) { dictionary[i] = i; } bits = 0; maxpower = Math.pow(2,2); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } switch (next = bits) { case 0: bits = 0; maxpower = Math.pow(2,8); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 1: bits = 0; maxpower = Math.pow(2,16); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 2: return ""; } dictionary[3] = c; w = c; result.push(c); while (true) { if (data.index > length) { return ""; } bits = 0; maxpower = Math.pow(2,numBits); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } switch (c = bits) { case 0: bits = 0; maxpower = Math.pow(2,8); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize-1; enlargeIn--; break; case 1: bits = 0; maxpower = Math.pow(2,16); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize-1; enlargeIn--; break; case 2: return result.join(''); } if (enlargeIn == 0) { enlargeIn = Math.pow(2, numBits); numBits++; } if (dictionary[c]) { entry = dictionary[c]; } else { if (c === dictSize) { entry = w + w.charAt(0); } else { return null; } } result.push(entry); // Add w+entry[0] to the dictionary. dictionary[dictSize++] = w + entry.charAt(0); enlargeIn--; w = entry; if (enlargeIn == 0) { enlargeIn = Math.pow(2, numBits); numBits++; } } } }; return LZString; })(); var WaniKani = (function() { var local_storage_prefix = 'wk_toolkit_'; var api_key = null; var radical_data = null; var kanji_data = null; var vocabulary_data = null; function log(msg) { console.log(msg); } function is_on_wanikani() { return (window.location.host == 'www.wanikani.com'); } function is_on_dashboard() { return is_on_wanikani() && ((window.location.pathname == '/dashboard') || (window.location.pathname == '/')); } function is_on_review_session_page() { return is_on_wanikani() && (window.location.pathname == '/review/session'); } function is_on_review_page() { return is_on_wanikani() && (window.location.pathname == '/review'); } function is_on_lesson_session_page() { return is_on_wanikani() && (window.location.pathname == '/lesson/session'); } function is_on_lesson_page() { return is_on_wanikani() && (window.location.pathname == '/lesson'); } function is_on_lattice_radicals_meaning() { return is_on_wanikani() && (window.location.pathname == '/lattice/radicals/meaning'); } function is_on_lattice_radicals_progress() { return is_on_wanikani() && (window.location.pathname == '/lattice/radicals/progress'); } function is_on_lattice_kanji_combined() { return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/combined'); } function is_on_lattice_kanji_meaning() { return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/meaning'); } function is_on_lattice_kanji_reading() { return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/reading'); } function is_on_lattice_kanji_progress() { return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/status'); } function is_on_lattice_vocabulary_combined() { return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/combined'); } function is_on_lattice_vocabulary_meaning() { return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/meaning'); } function is_on_lattice_vocabulary_reading() { return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/reading'); } function is_on_lattice_vocabulary_progress() { return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/status'); } //------------------------------------------------------------------- // Try to parse the url and detect if it belongs to a single item. // e.g. 'https://www.wanikani.com/level/1/radicals/construction' // will be parsed as 'radicals' and 'construction' //------------------------------------------------------------------- function parse_item_url(url) { url = decodeURI(url); var parsed = /.*\/(radicals|kanji|vocabulary)\/(.+)/.exec(url); if (parsed) { return {type:parsed[1], name:parsed[2]}; } else { return null; } } function clear_local_storage() { localStorage.removeItem(local_storage_prefix + 'last_review_time'); localStorage.removeItem(local_storage_prefix + 'next_review_time'); localStorage.removeItem(local_storage_prefix + 'last_unlock_time'); localStorage.removeItem(local_storage_prefix + 'api_key'); localStorage.removeItem(local_storage_prefix + 'api_user_radicals'); localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time'); localStorage.removeItem(local_storage_prefix + 'api_user_kanji'); localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time'); localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary'); localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time'); } function track_times() { if (is_on_review_session_page()) { localStorage.setItem(local_storage_prefix + 'last_review_time', now()); var lastUnlockTime = new Date($('.recent-unlocks time:nth(0)').attr('datetime'))/1000; localStorage.setItem(local_storage_prefix + 'last_unlock_time', now()); } if (is_on_dashboard()) { var next_review = Number($('.review-status .timeago').attr('datetime')); // Workaround for "WaniKani Real Times" script, which deletes the element we were looking for above. if (isNaN(next_review)) { next_review = Number($('.review-status time1').attr('datetime')); // Conditional divide-by-1000, in case someone fixed this error in Real Times script. if (next_review > 10000000000) next_review /= 1000; } localStorage.setItem(local_storage_prefix + 'next_review_time', next_review); } } function get_last_review_time() { return Number(localStorage.getItem(local_storage_prefix + 'last_review_time') || 0); } function get_next_review_time() { return Number(localStorage.getItem(local_storage_prefix + 'next_review_time') || 0); } function get_last_unlock_time() { return Number(localStorage.getItem(local_storage_prefix + 'last_unlock_time') || 0); } function now() { return Math.floor(new Date() / 1000); } function ajax_retry(url, retries, timeout) { retries = retries || 2; timeout = timeout || 3000; function action(resolve, reject) { $.ajax({ url: url, timeout: timeout }) .done(function(data, status){ if (status === 'success') resolve(data); else reject(); }) .fail(function(xhr, status, error){ if (status === 'error' && --retries > 0) action(resolve, reject); else reject(); }); } return new Promise(action); } function get_api_key() { return new Promise(function(resolve, reject) { api_key = localStorage.getItem(local_storage_prefix + 'api_key'); if (typeof api_key === 'string' && api_key.length == 32) { log("Already having API key"); return resolve(); } log("Loading API key"); ajax_retry('/account').then(function(page) { log("Loading API key ... SUCCESS"); // --[ SUCCESS ]---------------------- // Make sure what we got is a web page. if (typeof page !== 'string') {return reject();} // Extract the user name. page = $(page); // Extract the API key. api_key = page.find('#api-button').parent().find('input').attr('value'); if (typeof api_key !== 'string' || api_key.length !== 32) {return reject();} localStorage.setItem(local_storage_prefix + 'api_key', api_key); resolve(); },function(result) { log("Loading API key ... ERROR"); // --[ FAIL ]------------------------- reject(new Error('Failed to fetch API key!')); }); }); } function call_api_user_radicals() { return new Promise(function(resolve, reject) { log("Calling API: User radicals"); $.getJSON('/api/user/' + api_key + '/radicals/', function(json){ if (json.error && json.error.code === 'user_not_found') { log("Calling API: User radicals ... ERROR") localStorage.removeItem(local_storage_prefix + 'api_user_radicals'); localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time'); localStorage.removeItem(local_storage_prefix + 'api_key'); location.reload(); reject(); return; } log("Calling API: User radicals ... SUCCESS"); localStorage.setItem(local_storage_prefix + 'api_user_radicals', JSON.stringify(json)); localStorage.setItem(local_storage_prefix + 'api_user_radicals_fetch_time', now()); radical_data = json; resolve(); }); }); } function call_api_user_kanji() { return new Promise(function(resolve, reject) { log("Calling API: User kanji"); $.getJSON('/api/user/' + api_key + '/kanji/', function(json){ if (json.error && json.error.code === 'user_not_found') { log("Calling API: User kanji ... ERROR") localStorage.removeItem(local_storage_prefix + 'api_user_kanji'); localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time'); localStorage.removeItem(local_storage_prefix + 'api_key'); location.reload(); reject(); return; } log("Calling API: User kanji ... SUCCESS"); localStorage.setItem(local_storage_prefix + 'api_user_kanji', JSON.stringify(json)); localStorage.setItem(local_storage_prefix + 'api_user_kanji_fetch_time', now()); kanji_data = json; resolve(); }); }); } function call_api_user_vocabulary() { return new Promise(function(resolve, reject) { log("Calling API: User vocabulary"); $.getJSON('/api/user/' + api_key + '/vocabulary/', function(json){ if (json.error && json.error.code === 'user_not_found') { log("Calling API: User vocabulary ... ERROR") localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary'); localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time'); localStorage.removeItem(local_storage_prefix + 'api_key'); location.reload(); reject(); return; } log("Calling API: User vocabulary ... SUCCESS"); localStorage.setItem(local_storage_prefix + 'api_user_vocabulary', JSON.stringify(json)); localStorage.setItem(local_storage_prefix + 'api_user_vocabulary_fetch_time', now()); vocabulary_data = json; resolve(); }); }); } function get_last_fetch_time_api_user_radicals() { return Number(localStorage.getItem(local_storage_prefix + 'api_user_radicals_fetch_time')); } function get_last_fetch_time_api_user_kanji() { return Number(localStorage.getItem(local_storage_prefix + 'api_user_kanji_fetch_time')); } function get_last_fetch_time_api_user_vocabulary() { return Number(localStorage.getItem(local_storage_prefix + 'api_user_vocabulary_fetch_time')); } function load_radical_data() { var next_review_time = get_next_review_time(); var last_review_time = get_last_review_time(); var last_unlock_time = get_last_unlock_time(); var last_fetch_time = get_last_fetch_time_api_user_radicals(); if ((last_fetch_time <= last_unlock_time) || (last_fetch_time <= last_review_time) || ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) { log("Clearing previous fetched radical data"); radical_data = null; localStorage.removeItem(local_storage_prefix + 'api_user_radicals'); localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time'); } if (radical_data == null) { var stringified = localStorage.getItem(local_storage_prefix + 'api_user_radicals'); if (stringified != null) { log("Radical data loaded from local storage"); radical_data = JSON.parse(stringified); } } if (radical_data != null) { log("Radical data already loaded"); return Promise.resolve(); } return new Promise(function(resolve, reject) { get_api_key() .then(call_api_user_radicals) .then(function() { resolve(); }); }); } function load_kanji_data() { var next_review_time = get_next_review_time(); var last_review_time = get_last_review_time(); var last_unlock_time = get_last_unlock_time(); var last_fetch_time = get_last_fetch_time_api_user_kanji(); if ((last_fetch_time <= last_unlock_time) || (last_fetch_time <= last_review_time) || ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) { log("Clearing previous fetched kanji data"); kanji_data = null; localStorage.removeItem(local_storage_prefix + 'api_user_kanji'); localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time'); } if (kanji_data == null) { var stringified = localStorage.getItem(local_storage_prefix + 'api_user_kanji'); if (stringified != null) { log("Kanji data loaded from local storage"); kanji_data = JSON.parse(stringified); } } if (kanji_data != null) { log("Kanji data already loaded"); return Promise.resolve(); } return new Promise(function(resolve, reject) { get_api_key() .then(call_api_user_kanji) .then(function() { resolve(); }); }); } function load_vocabulary_data() { var next_review_time = get_next_review_time(); var last_review_time = get_last_review_time(); var last_unlock_time = get_last_unlock_time(); var last_fetch_time = get_last_fetch_time_api_user_vocabulary(); if ((last_fetch_time <= last_unlock_time) || (last_fetch_time <= last_review_time) || ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) { log("Clearing previous fetched vocabulary data"); vocabulary_data = null; localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary'); localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time'); } if (vocabulary_data == null) { var stringified = localStorage.getItem(local_storage_prefix + 'api_user_vocabulary'); if (stringified != null) { log("Vocabulary data loaded from local storage"); vocabulary_data = JSON.parse(stringified); } } if (vocabulary_data != null) { log("Vocabulary data already loaded"); return Promise.resolve(); } return new Promise(function(resolve, reject) { get_api_key() .then(call_api_user_vocabulary) .then(function() { resolve(); }); }); } function get_radical_data() { return radical_data; } function get_kanji_data() { return kanji_data; } function get_vocabulary_data() { return vocabulary_data; } function find_radical(meaning) { if (radical_data == null) { return null; } var numRadicals = radical_data.requested_information.length; for (var i = 0; i < numRadicals; i++) { if (radical_data.requested_information[i].meaning == meaning) { return radical_data.requested_information[i]; } } return null; } function find_kanji(character) { if (kanji_data == null) { return null; } var numKanji = kanji_data.requested_information.length; for (var i = 0; i < numKanji; i++) { if (kanji_data.requested_information[i].character == character) { return kanji_data.requested_information[i]; } } return null; } function find_vocabulary(character) { if (vocabulary_data == null) { return null; } var numVocabulary = vocabulary_data.requested_information.general.length; for (var i = 0; i < numVocabulary; i++) { if (vocabulary_data.requested_information.general[i].character == character) { return vocabulary_data.requested_information.general[i]; } } return null; } function find_item(type, name) { if (type == 'radicals') { return find_radical(name); } else if(type == 'kanji') { return find_kanji(name); } else if(type == 'vocabulary') { return find_vocabulary(name); } else { return null; } } return { is_on_wanikani: is_on_wanikani, is_on_dashboard: is_on_dashboard, is_on_review_session_page: is_on_review_session_page, is_on_review_page: is_on_review_page, is_on_lesson_session_page: is_on_lesson_session_page, is_on_lesson_page: is_on_lesson_page, is_on_lattice_radicals_meaning: is_on_lattice_radicals_meaning, is_on_lattice_radicals_progress: is_on_lattice_radicals_progress, is_on_lattice_kanji_combined: is_on_lattice_kanji_combined, is_on_lattice_kanji_meaning: is_on_lattice_kanji_meaning, is_on_lattice_kanji_reading: is_on_lattice_kanji_reading, is_on_lattice_kanji_progress: is_on_lattice_kanji_progress, is_on_lattice_vocabulary_combined: is_on_lattice_vocabulary_combined, is_on_lattice_vocabulary_meaning: is_on_lattice_vocabulary_meaning, is_on_lattice_vocabulary_reading: is_on_lattice_vocabulary_reading, is_on_lattice_vocabulary_progress: is_on_lattice_vocabulary_progress, parse_item_url: parse_item_url, clear_local_storage: clear_local_storage, track_times: track_times, get_last_review_time: get_last_review_time, get_next_review_time: get_next_review_time, load_radical_data: load_radical_data, get_radical_data: get_radical_data, find_radical: find_radical, load_kanji_data: load_kanji_data, get_kanji_data: get_kanji_data, find_kanji: find_kanji, load_vocabulary_data: load_vocabulary_data, get_vocabulary_data: get_vocabulary_data, find_vocabulary: find_vocabulary, find_item: find_item, }; })(); var UI = (function() { function addStyle(aCss) { var head, style; head = document.getElementsByTagName('head')[0]; if (head) { style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = aCss; head.appendChild(style); return style; } return null; } function initCss() { var css = '#lattice_extension {' + ' display:none;' + '}' + '#lattice_extension h3 {' + ' height: 24px;' + ' margin-top: 10px;' + ' margin-bottom: 0px;' + ' padding: 5px 20px;' + ' border-radius: 5px 5px 0 0;' + ' background-color: seagreen;' + ' color: white;' + ' text-shadow: none;' + '}' + '#lattice_extension section {' + ' background-color: lightgrey;' + '}' + '#lattice_extension table {' + '}' + '#lattice_extension td {' + ' padding: 2px 8px;' + '}' + '#lattice_extension .close_button {' + ' float: right;' + ' height: 24px;' + '}' ; addStyle(css); } function buildIdAttr(id) { if (id && id != '') return 'id="' + id + '"'; return ''; } function addOnChangeListener(id, data, listener) { $('#' + id).off('change'); $('#' + id).on('change', data, listener); } function addOnClickListener(id, data, listener) { $('#' + id).off('click'); $('#' + id).on('click', data, listener); } function buildWindow(id, title) { var html = '<div ' + buildIdAttr(id) + ' class="container">' + '<div class="row">' + '<div class="span12" >' + '<section id="' + id + '_body">' + '<h3>' + title + '<button class="close_button" " ' + buildIdAttr(id + '_close_btn') + '>Close</button>' + '</h3>' + '</section>' + '</div>' + '</div>' + '</div>' ; return html; } function buildTable(id) { var html = '<table ' + buildIdAttr(id) + '>' + '<tbody ' + buildIdAttr(id + '_body') + '>' + '</tbody>' + '</table>'; return html; } function buildTableRow(id, column1, column2, column3 = null) { var html = '<tr' + buildIdAttr(id) + '>' + '<td>' + column1 + '</td>' + '<td>' + column2 + '</td>' + (column3 == null ? '' : '<td>' + column3 + '</td>') + '</tr>'; return html; } function buildSelection(id, tooltip) { var html = '<select ' + buildIdAttr(id) + ' class="input" name="' + id + '" title="' + tooltip + '" />'; return html; } function addSelectionOption(selectId, value, label, selected) { $('#' + selectId).append( '<option value="' + value + '" ' + (selected ? 'selected' : '') + '>' + label + '</option>'); } function buildCheckBox(id, checked) { var html = '<input ' + buildIdAttr(id) + ' type="checkbox" ' + (checked ? 'checked' : '') + '>'; return html; } function buildButton(id, label) { var html = '<button ' + buildIdAttr(id) + '>' + label + '</button>'; return html; } return { initCss: initCss, buildWindow: buildWindow, buildTable: buildTable, buildTableRow: buildTableRow, buildCheckBox: buildCheckBox, buildButton: buildButton, buildSelection: buildSelection, addSelectionOption: addSelectionOption, addOnChangeListener: addOnChangeListener, addOnClickListener: addOnClickListener, }; })(); (function(gobj) { var settingsWindowAdded = false; var dropDownMenuExtended = false; var localStoragePrefix = 'LatticeExtension_'; var loaded_snapshot = {id:0}; var snapshot_index = { next_id: 1, snapshots: [] }; var max_num_snapshots = 5; var statistics = {}; var classification_thresholds = {}; var radicals_data_labels = { meaning_percent_answered_correct: "Meaning (% correct)", meaning_correct: "Meaning (correct)", meaning_incorrect: "Meaning (incorrect)", meaning_total: "Meaning (correct + incorrect)", meaning_current_streak: "Meaning (current streak)", meaning_max_streak: "Meaning (max streak)", }; var kanji_data_labels = { combined_percent_answered_correct: "Combined (% correct)", combined_correct: "Combined (correct)", combined_incorrect: "Combined (incorrect)", combined_total: "Combined (correct + incorrect)", meaning_percent_answered_correct: "Meaning (% correct)", meaning_correct: "Meaning (correct)", meaning_incorrect: "Meaning (incorrect)", meaning_total: "Meaning (correct + incorrect)", meaning_current_streak: "Meaning (current streak)", meaning_max_streak: "Meaning (max streak)", reading_percent_answered_correct: "Reading (% correct)", reading_correct: "Reading (correct)", reading_incorrect: "Reading (incorrect)", reading_total: "Reading (correct + incorrect)", reading_current_streak: "Reading (current streak)", reading_max_streak: "Reading (max streak)", }; var vocabulary_data_labels = { combined_percent_answered_correct: "Combined (% correct)", combined_correct: "Combined (correct)", combined_incorrect: "Combined (incorrect)", combined_total: "Combined (correct + incorrect)", meaning_percent_answered_correct: "Meaning (% correct)", meaning_correct: "Meaning (correct)", meaning_incorrect: "Meaning (incorrect)", meaning_total: "Meaning (correct + incorrect)", meaning_current_streak: "Meaning (current streak)", meaning_max_streak: "Meaning (max streak)", reading_percent_answered_correct: "Reading (% correct)", reading_correct: "Reading (correct)", reading_incorrect: "Reading (incorrect)", reading_total: "Reading (correct + incorrect)", reading_current_streak: "Reading (current streak)", reading_max_streak: "Reading (max streak)", }; var used_labels = null; function setupStatistics() { for(var key in used_labels){ statistics[key] = {min: 10000, max:-10000}; } } function removeText(jq_expression) { $(jq_expression).contents().filter(function () { return this.nodeType === 3; }).remove(); } function calculate_classification_thresholds(min, max) { if (max - min < 5) { if (max >= 4) { classification_thresholds = { t0: max - 4, t1: max - 4, t2: max - 3, t3: max - 2, t4: max - 1, t5: max, }; } else { classification_thresholds = { t0: min, t1: min, t2: min + 1, t3: min + 2, t4: min + 3, t5: min + 4, }; } } else { var len = (max - min) / 5; classification_thresholds = { t0: min, t1: Math.floor(min + len), t2: Math.floor(min + 2 * len), t3: Math.floor(min + 3 * len), t4: Math.floor(min + 4 * len), t5: max, }; } } function classify_item(value) { var t = classification_thresholds; if (value <= t.t1) return 'percentage-0-20'; else if ((value >= t.t1 + 1) && (value <= t.t2)) return 'percentage-21-40'; else if ((value >= t.t2 + 1) && (value <= t.t3)) return 'percentage-41-60'; else if ((value >= t.t3 + 1) && (value <= t.t4)) return 'percentage-61-80'; else return 'percentage-81-100'; } function updateLegend() { var dataToDisplay = $('#data_to_display').val(); var unit = dataToDisplay.includes('percent') ? '%' : '';; var t = classification_thresholds; $('aside.additional-info ul li:first div span:nth(0)').attr('data-original-title', t.t0 + unit + ' - ' + t.t1 + unit); $('aside.additional-info ul li:first div span:nth(1)').attr('data-original-title', (t.t1 + 1) + unit + ' - ' + t.t2 + unit); $('aside.additional-info ul li:first div span:nth(2)').attr('data-original-title', (t.t2 + 1) + unit + ' - ' + t.t3 + unit); $('aside.additional-info ul li:first div span:nth(3)').attr('data-original-title', (t.t3 + 1) + unit + ' - ' + t.t4 + unit); $('aside.additional-info ul li:first div span:nth(4)').attr('data-original-title', (t.t4 + 1) + unit + ' - ' + t.t5 + unit); } function updateKanji() { var dataToDisplay = $('#data_to_display').val(); var zeroAsMinimum = $('#zero_as_minimum').is(':checked'); if (zeroAsMinimum) { var max = statistics[dataToDisplay].max; calculate_classification_thresholds(0, max); } else { var min = statistics[dataToDisplay].min; var max = statistics[dataToDisplay].max; calculate_classification_thresholds(min, max); } updateLegend(); var kanji = $('section.lattice-single-character a'); kanji.each(function(i, item) { var isLocked = $(this).parent().attr('is_locked'); if (isLocked == 'true') { return; } $(this).removeClass('percentage-0-20 percentage-21-40 percentage-41-60 percentage-61-80 percentage-81-100'); var value = $(this).parent().attr('data_' + dataToDisplay); $(this).addClass(classify_item(value)); }); } function updateHtmlItems() { var dataToDisplay = $('#data_to_display').val(); var zeroAsMinimum = $('#zero_as_minimum').is(':checked'); if (zeroAsMinimum) { var max = statistics[dataToDisplay].max; calculate_classification_thresholds(0, max); } else { var min = statistics[dataToDisplay].min; var max = statistics[dataToDisplay].max; calculate_classification_thresholds(min, max); } updateLegend(); var htmlItems = $('section.lattice-single-character a, section.lattice-multi-character a'); htmlItems.each(function(i, item) { var isLocked = $(this).parent().attr('is_locked'); if (isLocked != 'false') { return; } $(this).removeClass('percentage-0-20 percentage-21-40 percentage-41-60 percentage-61-80 percentage-81-100'); var value = $(this).parent().attr('data_' + dataToDisplay); $(this).addClass(classify_item(value)); }); } function buildSelectBox() { var isOnLatticeCombined = WaniKani.is_on_lattice_kanji_combined() || WaniKani.is_on_lattice_vocabulary_combined(); var isOnLatticeMeaning = WaniKani.is_on_lattice_kanji_meaning() || WaniKani.is_on_lattice_vocabulary_meaning(); var isOnLatticeReading = WaniKani.is_on_lattice_kanji_reading() || WaniKani.is_on_lattice_vocabulary_reading(); var selectBox = '<select id="data_to_display" class="input" name="data_to_display" title="Select what data to display.">'; for(var key in used_labels){ var selected = ''; if (isOnLatticeCombined) { if (key == 'combined_percent_answered_correct') { selected = 'selected'; } } else if (isOnLatticeMeaning) { if (key == 'meaning_percent_answered_correct') { selected = 'selected'; } } else if (isOnLatticeReading) { if (key == 'reading_percent_answered_correct') { selected = 'selected'; } } selectBox = selectBox + '<option value="' + key + '" ' + selected + '>' + used_labels[key] + '</option>'; } selectBox = selectBox + '</select>' return selectBox; } function addToStatistics(attribute, value) { var currentMin = statistics[attribute].min; var currentMax = statistics[attribute].max; statistics[attribute].min = (currentMin < value) ? currentMin : value; statistics[attribute].max = (currentMax < value) ? value : currentMax; } function cleanUpItem(item) { var title = item.attr('data-original-title'); var index = title.indexOf('<br />'); if (index != -1) { title = title.substr(0, index); item.attr('data-original-title', title); } } function extendItem(value, item, dataAttr) { var newTitle = item.attr('data-original-title'); var valueText; if (loaded_snapshot.id == 0) { valueText = value; } else { if (value < 0) { valueText = value; } else if (value > 0) { valueText = '+' + value; } else { valueText = 'no change'; } } newTitle += '<br />' + used_labels[dataAttr] + ': ' + valueText; item.attr('data-original-title', newTitle); item.parent().attr('data_' + dataAttr, value); addToStatistics(dataAttr, value); } function calcPercentAnsweredCorrect(correct, incorrect) { if (correct + incorrect > 0) return Math.round(100 * correct / (correct + incorrect)); else return 0; } function extendLatticeRadicals() { used_labels = radicals_data_labels; removeText('aside.additional-info ul li:first'); $('aside.additional-info ul li:first').append(buildSelectBox()); $('#data_to_display').on('change', function() { updateHtmlItems(); }); $('aside.additional-info ul li:first').append('<br />'); $('aside.additional-info ul li:first').append(UI.buildSelection('selected_snapshot', 'Snapshot to use')); UI.addSelectionOption('selected_snapshot', 'none', 'No comparison', true); for (var i = 0; i < snapshot_index.snapshots.length; i++) { var snapshot = snapshot_index.snapshots[i]; var snapshot_label = 'Compare to snapshot [' + snapshot.id + "] - " + new Date(snapshot.date_taken).toLocaleString(); UI.addSelectionOption('selected_snapshot', snapshot.id, snapshot_label, false); } UI.addOnChangeListener('selected_snapshot', function() { var selected = $('#selected_snapshot').val(); if (selected == 'none') resetLoadedSnapshot(); else loadSnapshot(selected); extendRadicalItems(); updateHtmlItems(); }); $('aside.additional-info ul li:first').append( '<br /><input id="zero_as_minimum" type="checkbox" name="zero_as_minimum" checked>Use 0 as minimum<br>'); $('#zero_as_minimum').on('change', function() { updateHtmlItems(); }); extendRadicalItems(); } function extendRadicalItems() { setupStatistics(); var htmlItems = $('section.lattice-single-character a'); htmlItems.each(function(i, item) { var uri = WaniKani.parse_item_url($(this).attr('href')); var itemInfo = WaniKani.find_item(uri.type, uri.name); if ((itemInfo == null) || (itemInfo.user_specific == null)) { $(this).parent().attr('is_locked', 'true'); return; } var meaning = itemInfo.meaning; itemInfo = itemInfo.user_specific; var snapshotInfo = { meaning_correct: 0, meaning_incorrect: 0, meaning_current_streak: 0, meaning_max_streak: 0 }; if (loaded_snapshot.id != 0) { var numRadicals = loaded_snapshot.radical_data.requested_information.length; for (var j = 0; j < numRadicals; j++) { if (loaded_snapshot.radical_data.requested_information[j].meaning == meaning) { if (loaded_snapshot.radical_data.requested_information[j].user_specific != null) { snapshotInfo = loaded_snapshot.radical_data.requested_information[j].user_specific; } break; } } } $(this).parent().attr('is_locked', 'false'); cleanUpItem($(this)); { var correct = itemInfo.meaning_correct; var incorrect = itemInfo.meaning_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.meaning_correct; var snapshotIncorrect = snapshotInfo.meaning_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'meaning_percent_answered_correct'); extendItem(total - snapshotTotal, $(this), 'meaning_total'); } extendItem(itemInfo.meaning_correct - snapshotInfo.meaning_correct, $(this), 'meaning_correct'); extendItem(itemInfo.meaning_incorrect - snapshotInfo.meaning_incorrect, $(this), 'meaning_incorrect'); extendItem(itemInfo.meaning_current_streak - snapshotInfo.meaning_current_streak, $(this), 'meaning_current_streak'); extendItem(itemInfo.meaning_max_streak - snapshotInfo.meaning_max_streak, $(this), 'meaning_max_streak'); }); } function extendLatticeKanji() { used_labels = kanji_data_labels; removeText('aside.additional-info ul li:first'); $('aside.additional-info ul li:first').append(buildSelectBox()); $('#data_to_display').on('change', function() { updateHtmlItems(); }); $('aside.additional-info ul li:first').append('<br />'); $('aside.additional-info ul li:first').append(UI.buildSelection('selected_snapshot', 'Snapshot to use')); UI.addSelectionOption('selected_snapshot', 'none', 'No comparison', true); for (var i = 0; i < snapshot_index.snapshots.length; i++) { var snapshot = snapshot_index.snapshots[i]; var snapshot_label = 'Compare to snapshot [' + snapshot.id + "] - " + new Date(snapshot.date_taken).toLocaleString(); UI.addSelectionOption('selected_snapshot', snapshot.id, snapshot_label, false); } UI.addOnChangeListener('selected_snapshot', function() { var selected = $('#selected_snapshot').val(); if (selected == 'none') resetLoadedSnapshot(); else loadSnapshot(selected); extendKanjiItems(); updateHtmlItems(); }); $('aside.additional-info ul li:first').append( '<br /><input id="zero_as_minimum" type="checkbox" name="zero_as_minimum" checked>Use 0 as minimum<br>'); $('#zero_as_minimum').on('change', function() { updateHtmlItems(); }); extendKanjiItems(); } function extendKanjiItems() { setupStatistics(); var htmlItems = $('section.lattice-single-character a'); htmlItems.each(function(i, item) { var uri = WaniKani.parse_item_url($(this).attr('href')); var itemInfo = WaniKani.find_item(uri.type, uri.name); if ((itemInfo == null) || (itemInfo.user_specific == null)) { $(this).parent().attr('is_locked', 'true'); return; } var character = itemInfo.character; itemInfo = itemInfo.user_specific; var snapshotInfo = { meaning_correct: 0, meaning_incorrect: 0, meaning_current_streak: 0, meaning_max_streak: 0, reading_correct: 0, reading_incorrect: 0, reading_current_streak: 0, reading_max_streak: 0 }; if (loaded_snapshot.id != 0) { var numKanji = loaded_snapshot.kanji_data.requested_information.length; for (var j = 0; j < numKanji; j++) { if (loaded_snapshot.kanji_data.requested_information[j].character == character) { if (loaded_snapshot.kanji_data.requested_information[j].user_specific != null) { snapshotInfo = loaded_snapshot.kanji_data.requested_information[j].user_specific; } break; } } } $(this).parent().attr('is_locked', 'false'); cleanUpItem($(this)); { var correct = itemInfo.meaning_correct + itemInfo.reading_correct; var incorrect = itemInfo.meaning_incorrect + itemInfo.reading_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.meaning_correct + snapshotInfo.reading_correct; var snapshotIncorrect = snapshotInfo.meaning_incorrect + snapshotInfo.reading_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'combined_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'combined_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'combined_incorrect'); extendItem(total - snapshotTotal, $(this), 'combined_total'); } { var correct = itemInfo.meaning_correct; var incorrect = itemInfo.meaning_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.meaning_correct; var snapshotIncorrect = snapshotInfo.meaning_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'meaning_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'meaning_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'meaning_incorrect'); extendItem(total - snapshotTotal, $(this), 'meaning_total'); } { var correct = itemInfo.reading_correct; var incorrect = itemInfo.reading_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.reading_correct; var snapshotIncorrect = snapshotInfo.reading_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'reading_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'reading_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'reading_incorrect'); extendItem(total - snapshotTotal, $(this), 'reading_total'); } extendItem(itemInfo.meaning_correct - snapshotInfo.meaning_correct, $(this), 'meaning_correct'); extendItem(itemInfo.meaning_incorrect - snapshotInfo.meaning_incorrect, $(this), 'meaning_incorrect'); extendItem(itemInfo.meaning_current_streak - snapshotInfo.meaning_current_streak, $(this), 'meaning_current_streak'); extendItem(itemInfo.meaning_max_streak - snapshotInfo.meaning_max_streak, $(this), 'meaning_max_streak'); extendItem(itemInfo.reading_correct - snapshotInfo.reading_correct, $(this), 'reading_correct'); extendItem(itemInfo.reading_incorrect - snapshotInfo.reading_incorrect, $(this), 'reading_incorrect'); extendItem(itemInfo.reading_max_streak - snapshotInfo.reading_max_streak, $(this), 'reading_max_streak'); extendItem(itemInfo.reading_current_streak - snapshotInfo.reading_current_streak, $(this), 'reading_current_streak'); }); } function extendLatticeVocabulary() { used_labels = vocabulary_data_labels; removeText('aside.additional-info ul li:first'); $('aside.additional-info ul li:first').append(buildSelectBox()); $('#data_to_display').on('change', function() { updateHtmlItems(); }); $('aside.additional-info ul li:first').append('<br />'); $('aside.additional-info ul li:first').append(UI.buildSelection('selected_snapshot', 'Snapshot to use')); UI.addSelectionOption('selected_snapshot', 'none', 'No comparison', true); for (var i = 0; i < snapshot_index.snapshots.length; i++) { var snapshot = snapshot_index.snapshots[i]; var snapshot_label = 'Compare to snapshot [' + snapshot.id + "] - " + new Date(snapshot.date_taken).toLocaleString(); UI.addSelectionOption('selected_snapshot', snapshot.id, snapshot_label, false); } UI.addOnChangeListener('selected_snapshot', function() { var selected = $('#selected_snapshot').val(); if (selected == 'none') resetLoadedSnapshot(); else loadSnapshot(selected); extendVocabularyItems(); updateHtmlItems(); }); $('aside.additional-info ul li:first').append( '<br /><input id="zero_as_minimum" type="checkbox" name="zero_as_minimum" checked>Use 0 as minimum<br>'); $('#zero_as_minimum').on('change', function() { updateHtmlItems(); }); extendVocabularyItems(); } function extendVocabularyItems() { setupStatistics(); var vocabulary = $('section.lattice-multi-character a'); vocabulary.each(function(i, item) { var uri = WaniKani.parse_item_url($(this).attr('href')); var itemInfo = WaniKani.find_item(uri.type, uri.name); if ((itemInfo == null) || (itemInfo.user_specific == null)) { $(this).parent().attr('is_locked', 'true'); return; } var character = itemInfo.character; itemInfo = itemInfo.user_specific; var snapshotInfo = { meaning_correct: 0, meaning_incorrect: 0, meaning_current_streak: 0, meaning_max_streak: 0, reading_correct: 0, reading_incorrect: 0, reading_current_streak: 0, reading_max_streak: 0 }; if (loaded_snapshot.id != 0) { var numVocabulary = loaded_snapshot.vocabulary_data.requested_information.general.length; for (var j = 0; j < numVocabulary; j++) { if (loaded_snapshot.vocabulary_data.requested_information.general[j].character == character) { if (loaded_snapshot.vocabulary_data.requested_information.general[j].user_specific != null) { snapshotInfo = loaded_snapshot.vocabulary_data.requested_information.general[j].user_specific; } break; } } } $(this).parent().attr('is_locked', 'false'); cleanUpItem($(this)); { var correct = itemInfo.meaning_correct + itemInfo.reading_correct; var incorrect = itemInfo.meaning_incorrect + itemInfo.reading_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.meaning_correct + snapshotInfo.reading_correct; var snapshotIncorrect = snapshotInfo.meaning_incorrect + snapshotInfo.reading_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'combined_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'combined_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'combined_incorrect'); extendItem(total - snapshotTotal, $(this), 'combined_total'); } { var correct = itemInfo.meaning_correct; var incorrect = itemInfo.meaning_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.meaning_correct; var snapshotIncorrect = snapshotInfo.meaning_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'meaning_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'meaning_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'meaning_incorrect'); extendItem(total - snapshotTotal, $(this), 'meaning_total'); } { var correct = itemInfo.reading_correct; var incorrect = itemInfo.reading_incorrect; var total = correct + incorrect; var percentAnsweredCorrect = calcPercentAnsweredCorrect(correct, incorrect); var snapshotCorrect = snapshotInfo.reading_correct; var snapshotIncorrect = snapshotInfo.reading_incorrect; var snapshotTotal = snapshotCorrect + snapshotIncorrect; var snapshotPercentAnsweredCorrect = calcPercentAnsweredCorrect(snapshotCorrect, snapshotIncorrect); extendItem(percentAnsweredCorrect - snapshotPercentAnsweredCorrect, $(this), 'reading_percent_answered_correct'); extendItem(correct - snapshotCorrect, $(this), 'reading_correct'); extendItem(incorrect - snapshotIncorrect, $(this), 'reading_incorrect'); extendItem(total - snapshotTotal, $(this), 'reading_total'); } extendItem(itemInfo.meaning_correct - snapshotInfo.meaning_correct, $(this), 'meaning_correct'); extendItem(itemInfo.meaning_incorrect - snapshotInfo.meaning_incorrect, $(this), 'meaning_incorrect'); extendItem(itemInfo.meaning_current_streak - snapshotInfo.meaning_current_streak, $(this), 'meaning_current_streak'); extendItem(itemInfo.meaning_max_streak - snapshotInfo.meaning_max_streak, $(this), 'meaning_max_streak'); extendItem(itemInfo.reading_correct - snapshotInfo.reading_correct, $(this), 'reading_correct'); extendItem(itemInfo.reading_incorrect - snapshotInfo.reading_incorrect, $(this), 'reading_incorrect'); extendItem(itemInfo.reading_max_streak - snapshotInfo.reading_max_streak, $(this), 'reading_max_streak'); extendItem(itemInfo.reading_current_streak - snapshotInfo.reading_current_streak, $(this), 'reading_current_streak'); }); } function extendDropDownMenu() { if (dropDownMenuExtended) { return; } $('<li><a href="#lattice_extension">Lattice Extension</a></li>') .insertBefore($('.account .dropdown-menu .nav-header:eq(1)')) .on('click', toggleSettingsWindow); dropDownMenuExtended = true; } function buildSettingsWindow() { UI.initCss(); var html; html = UI.buildWindow('lattice_extension', 'Lattice Extension'); $(html).insertAfter($('.navbar')); updateSettingsWindow(); UI.addOnClickListener('lattice_extension_close_btn', {}, function(e) { toggleSettingsWindow(e); }); settingsWindowAdded = true; } function updateSettingsWindow() { var html; $('#lattice_extension_settings').remove(); html = UI.buildTable('lattice_extension_settings'); $('#lattice_extension_body').append(html); if (snapshot_index.snapshots.length >= max_num_snapshots) { html = UI.buildTableRow( '', '', 'Max. number of snapshots reached. Delete some to create new ones.'); $('#lattice_extension_settings_body').append(html); } else { html = UI.buildTableRow( '', '', UI.buildButton('new_snapshot', 'Create new snapshot')); $('#lattice_extension_settings_body').append(html); UI.addOnClickListener('new_snapshot', {}, function(e) { createSnapshot(); }); } for (var i = 0; i < snapshot_index.snapshots.length; i++) { var snapshot = snapshot_index.snapshots[i]; var info = 'Radicals: ' + snapshot.num_radicals + ', Kanji: ' + snapshot.num_kanji + ', Vocabulary: ' + snapshot.num_vocabulary; html = UI.buildTableRow( '', 'Snapshot [' + snapshot.id + "] - " + new Date(snapshot.date_taken).toLocaleString(), UI.buildButton('delete_snapshot_' + snapshot.id, 'Delete snapshot'), info); $('#lattice_extension_settings_body').append(html); UI.addOnClickListener('delete_snapshot_' + snapshot.id, {id: snapshot.id}, function(e) { var selection = confirm('Are you sure you want to delete this snapshot?'); if (selection == true) { deleteSnapshot(e.data.id); } }); } } function createSnapshot() { $('#new_snapshot').remove(); html = UI.buildTableRow( '', '<b>Please wait! A new snapshot is being created ...</b>', ''); $('#lattice_extension_settings_body').append(html); alert('A new snapshot is being created!\n' + 'Click now on OK and wait for the snapshot list to update.'); WaniKani .load_radical_data() .then(WaniKani.load_kanji_data) .then(WaniKani.load_vocabulary_data) .then(createSnapshotDataLoaded); } function createSnapshotDataLoaded() { var new_id = snapshot_index.next_id; snapshot_index.next_id++; loaded_snapshot = {}; loaded_snapshot.id = new_id; loaded_snapshot.date_taken = new Date(); loaded_snapshot.radical_data = WaniKani.get_radical_data(); loaded_snapshot.kanji_data = WaniKani.get_kanji_data(); loaded_snapshot.vocabulary_data = WaniKani.get_vocabulary_data(); var index_entry = {}; index_entry.id = new_id; index_entry.date_taken = loaded_snapshot.date_taken; index_entry.num_radicals = loaded_snapshot.radical_data.requested_information.length; index_entry.num_kanji = loaded_snapshot.kanji_data.requested_information.length; index_entry.num_vocabulary = loaded_snapshot.vocabulary_data.requested_information.general.length; snapshot_index.snapshots.push(index_entry); var saved = saveLoadedSnapshot(); if (saved) { saved = saveSnapshotIndex(); } if (saved) { updateSettingsWindow(); } } function deleteSnapshot(snapshot_id) { var index = snapshot_index.snapshots.findIndex(function(item) { return (item.id == snapshot_id); }); if (index > -1) { snapshot_index.snapshots.splice(index, 1); var saved = saveSnapshotIndex(); if (saved) { deleteLoadedSnapshot(); updateSettingsWindow(); } } } function resetLoadedSnapshot() { loaded_snapshot = {id:0}; } function loadSnapshot(snapshot_id) { var loaded = localStorage.getItem(localStoragePrefix + 'snapshot' + snapshot_id); if (loaded != null) { var tmp = JSON.parse(LZString.decompressFromUTF16(loaded)); if (tmp == null) { alert('Failed to load snapshot ' + snapshot_id); } else { loaded_snapshot = tmp; } } } function loadSnapshotIndex() { var loaded = localStorage.getItem(localStoragePrefix + 'snapshot_index'); if (loaded != null) { snapshot_index = JSON.parse(LZString.decompressFromUTF16(loaded)); if (snapshot_index == null) { alert('Failed to load snapshot index'); return false; } } return true; } function saveSnapshotIndex() { try { var stringified = JSON.stringify(snapshot_index); var compressed = LZString.compressToUTF16(stringified); if (LZString.decompressFromUTF16(compressed) == null) { alert('Failed to save snapshot index: Compression error'); } else { localStorage.setItem(localStoragePrefix + 'snapshot_index', compressed); } } catch (err) { alert(err.message); return false; } return true; } function saveLoadedSnapshot() { try { var stringified = JSON.stringify(loaded_snapshot); var compressed = LZString.compressToUTF16(stringified); if (LZString.decompressFromUTF16(compressed) == null) { alert('Failed to save snapshot ' + loaded_snapshot.id + ': Compression error'); } else { localStorage.setItem(localStoragePrefix + 'snapshot' + loaded_snapshot.id, compressed); } } catch (err) { alert(err.message); return false; } return true; } function deleteLoadedSnapshot() { try { localStorage.removeItem(localStoragePrefix + 'snapshot' + loaded_snapshot.id); loaded_snapshot = {id: 0}; } catch (err) { alert(err.message); return false; } return true; } function toggleSettingsWindow(e) { if (e !== undefined) e.preventDefault(); // Add the manager if not already. if (!settingsWindowAdded) buildSettingsWindow(); $('#lattice_extension').slideToggle(); $('html, body').animate({scrollTop: 0}, 800); } //------------------------------------------------------------------- // Main function //------------------------------------------------------------------- function main() { console.log('START - WaniKani Lattice Extension'); var indexLoaded = loadSnapshotIndex(); if (indexLoaded) { extendDropDownMenu(); WaniKani.track_times(); if (WaniKani.is_on_lattice_radicals_meaning()) { WaniKani.load_radical_data().then(extendLatticeRadicals); } else if (WaniKani.is_on_lattice_kanji_combined() || WaniKani.is_on_lattice_kanji_meaning() || WaniKani.is_on_lattice_kanji_reading()) { WaniKani.load_kanji_data().then(extendLatticeKanji); } else if (WaniKani.is_on_lattice_vocabulary_combined() || WaniKani.is_on_lattice_vocabulary_meaning() || WaniKani.is_on_lattice_vocabulary_reading()) { WaniKani.load_vocabulary_data().then(extendLatticeVocabulary); } } console.log('END - WaniKani Lattice Extension'); } window.addEventListener('load', main, false); }());