// ==UserScript==
// @name Wanikani Self-Study Quiz Edition
// @namespace rfindley
// @description Self-study your items via the Wanikani level pages
// @version 2.0.20
// @include https://www.wanikani.com/level/*
// @exclude https://www.wanikani.com/level/*/*
// @include https://www.wanikani.com/radicals*
// @exclude https://www.wanikani.com/radicals/*
// @include https://www.wanikani.com/kanji*
// @exclude https://www.wanikani.com/kanji/*
// @include https://www.wanikani.com/vocabulary*
// @exclude https://www.wanikani.com/vocabulary/*
// @require https://greasyfork.org/scripts/19781-wanakana/code/WanaKana.js?version=126349
// @copyright 2016+, Robin Findley
// @license MIT; http://opensource.org/licenses/MIT
// @run-at document-end
// @grant none
// ==/UserScript==
window.wkselfstudy = {};
(function(gobj) {
var settings = {
compatible: 2,
// ss_hidelocked - Hide locked items
// ss_hideunlocked - Hide unlocked items (includes ss_hideburn)
// ss_hideburned - Hide burned items
// ss_hideunburned - Hide unburned items (includes ss_hidelock)
// ss_hidechar - Hide the radical/kanji/vocab characters
// ss_hideread - Hide the reading
// ss_hidemean - Hide the meaning
// ss_quizctom - Character -> Meaning
// ss_quizctor - Character -> Reading
// ss_quizrtom - Reading -> Meaning
// ss_quizmtor - Meaning -> Reading
// ss_quizator - Audio -> Reading
// ss_quizatom - Audio -> Meaning
configs: [
['Japanese to English', 'ss_hidelocked ss_hideread ss_hidemean ss_quizctom ss_quizctor'],
['English to Japanese', 'ss_hidelocked ss_hideread ss_hidechar ss_quizmtor'],
['[BURNED] Japanese to English', 'ss_hideunburned ss_hideread ss_hidemean ss_quizctom ss_quizctor'],
['[BURNED] English to Japanese', 'ss_hideunburned ss_hideread ss_hidechar ss_quizmtor'],
['Listening Quiz', 'ss_hidelocked ss_hideread ss_hidechar ss_hidemean ss_quizator ss_quizator'],
],
selected_config: 0,
enabled: true,
randomize_on_load: true,
lightning_mode: false, // Skip 'correct', jump to next item.
audio_mode: false, // Auto-play audio files (i.e. readings).
quiz_pairing: 1, // 0=none, 1=Reading first, 2=Meaning first
quiz_repeat: true, // Repeat after finishing quiz.
quiz_shuffle: true, // Shuffle before repeating quiz.
quiz_typo: true // Allow typos in English answers
};
gobj.settings = settings;
var html =
'<div class="selfstudy">'+
' <label>Self-study:</label>'+
' <div class="btn-group">'+
' <button class="btn enable" title="Enable/Disable self-study plugin">OFF</button>'+
' <button class="btn quiz" title="Open the quiz window">Quiz</button>'+
' <button class="btn shuffle" title="Shuffle the list of items below">Shuffle</button>'+
' <select class="btn config" title="Select a self-study preset"></select>'+
' <button class="btn config" title="Configure self-study presets"><i class="icon-gear"></i></button>'+
' </div>'+
'</div>';
var config_html =
'<div id="ss_config" class="hidden">'+
' <div class="section"><label>Presets</label>'+
' <div class="btns">'+
' <button class="btn new">New</button>'+
' <button class="btn up">Up</button>'+
' <button class="btn dn">Down</button>'+
' <button class="btn del">Delete</button>'+
' </div>'+
' <div class="list">'+
' <select class="configs" size="7"></select>'+
' </div>'+
' <div class="hide_cfg">'+
' <div class="txtline">'+
' <label>Edit name:</label>'+
' <div class="expand"><input type="text" class="preset"></div>'+
' </div>'+
' </div>'+
' </div>'+
' <div class="section"><label>Items</label>'+
' <div class="cbbox">'+
' <div><label>Remove Locked:</label><input type="checkbox" name="ss_hidelocked"></div>'+
' <div><label>Remove Unlocked:</label><input type="checkbox" name="ss_hideunlocked"></div>'+
' </div>'+
' <div class="cbbox">'+
' <div><label>Remove Burned:</label><input type="checkbox" name="ss_hideburned"></div>'+
' <div><label>Remove Unburned:</label><input type="checkbox" name="ss_hideunburned"></div>'+
' </div>'+
' </div>'+
' <div class="section"><label>Information</label>'+
' <div class="cbbox">'+
' <div><label>Hide Rad/Kan/Voc:</label><input type="checkbox" name="ss_hidechar"></div>'+
' </div>'+
' <div class="cbbox">'+
' <div><label>Hide Reading:</label><input type="checkbox" name="ss_hideread"></div>'+
' <div><label>Hide Meaning:</label><input type="checkbox" name="ss_hidemean"></div>'+
' </div>'+
' </div>'+
' <div class="section"><label>Quiz</label>'+
' <div class="cbbox">'+
' <div><label>Rad/Kan/Voc <i class="icon-circle-arrow-right"></i> Meaning:</label><input type="checkbox" name="ss_quizctom"></div>'+
' <div><label>Kan/Voc <i class="icon-circle-arrow-right"></i> Reading:</label><input type="checkbox" name="ss_quizctor"></div>'+
' <div><label>Reading <i class="icon-circle-arrow-right"></i> Meaning:</label><input type="checkbox" name="ss_quizrtom"></div>'+
' <div><label>Meaning <i class="icon-circle-arrow-right"></i> Reading:</label><input type="checkbox" name="ss_quizmtor"></div>'+
' </div>'+
' <div class="cbbox">'+
' <div><label>Voc Audio <i class="icon-circle-arrow-right"></i> Reading:</label><input type="checkbox" name="ss_quizator"></div>'+
' <div><label>Voc Audio <i class="icon-circle-arrow-right"></i> Meaning:</label><input type="checkbox" name="ss_quizatom"></div>'+
' </div>'+
' </div>'+
' <div class="dlg_close">'+
' <div class="btn-group">'+
' <button class="btn save">Save</button>'+
' <button class="btn cancel">Cancel</button>'+
' </div>'+
' </div>'+
'</div>';
var quiz_html =
'<div id="ss_quiz" class="hidden kanji meaning">'+
' <div class="topbar">'+
' <div class="settings noselect">'+
' <span class="icon-bolt ss_lightning" title="Lightning Mode: Skip <enter> on correct answers (Ctrl-L)"></span>'+
' <span class="icon-retweet ss_repeat" title="Repeat after finishing quiz (Ctrl-R)"></span>'+
' <span class="icon-random ss_shuffle" title="Shuffle before repeating quiz (Ctrl-S)"></span>'+
' <span class="icon-audio ss_audio" title="Auto-play audio (Ctrl-Shift-A; Ctrl-A to play)"></span>'+
' <span class="icon-warning-sign ss_typo" title="Allow typos (oops) in English answers (Ctrl-O)" style="padding-left: 0px;"></span>'+
' <span class="icon-question-sign ss_help" title="Help: Peek at item info (F1, Ctrl-H, or ?)"></span>'+
' <span class="ss_done" title="End the quiz and show summary (Esc or Ctrl-E)"><strong>%</strong></span><br />'+
' <span class="ss_pair" data-value="0" title="Pairing mode: Group reading and meaning together (Ctrl-P)">Pairing: <span class="data">Disabled</span></span>'+
' </div>'+
' <div class="stats"></div>'+
' <div class="stats_labels">Round:<br>Remaining:<br>Correct:<br>Incorrect:</div>'+
' </div>'+
' <div class="qwrap">'+
' <div class="prev" title="Previous question (Ctrl-Left)"><i class="icon-chevron-left"></i></div>'+
' <div class="next" title="Next question (Ctrl-Right)"><i class="icon-chevron-right"></i></div>'+
' <div class="question"></div>'+
' <div class="help"></div>'+
' <div class="summary center">'+
' <h3>Summary - <span class="percent">100%</span> Correct <button class="btn requiz" title="Re-quiz wrong items">Re-quiz</button></h3>'+
' <ul class="errors"></ul>'+
' </div>'+
' <div class="round center"><span class="center">Round 1</span></div>'+
' </div>'+
' <div class="qtype"></div>'+
' <div class="answer"><input type="text" value=""></div>'+
'</div>';
var css =
'.noselect {-webkit-touch-callout:none; -webkit-user-select:none; -khtml-user-select:none; -moz-user-select: none;'+
'-ms-user-select:none; user-select: none;}'+
'.selfstudy {margin-left:20px; margin-bottom:10px; position:relative;}'+
'.selfstudy label {display:inline; vertical-align:middle; padding-right:4px; color:#999; font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif; text-shadow:0 1px 0 #fff;}'+
'.selfstudy button.enable {width:55px;}'+
'.ss_active .selfstudy button.enable.on {background-color:#b3e6b3; background-image:linear-gradient(to bottom, #ecf9ec, #b3e6b3);}'+
'.selfstudy select.config {width:300px;}'+
'.selfstudy .center {display:block; position:relative; top:50%; left:50%; transform:translate(-50%,-50%);}'+
'section[id^="level-"].ss_active.ss_hidechar .character-item a span:not(.dummy) {opacity:0; transition:opacity ease-in-out 0.15s}'+
'section[id^="level-"].ss_active.ss_hideread .character-item a li[lang="ja"] {opacity:0; transition:opacity ease-in-out 0.15s}'+
'section[id^="level-"].ss_active.ss_hidemean .character-item a li:not([lang="ja"]) {opacity:0; transition:opacity ease-in-out 0.15s}'+
'section[id^="level-"].ss_active.ss_hideburned .character-item.burned {display:none;}'+
'section[id^="level-"].ss_active.ss_hidelocked .character-item.locked {display:none;}'+
'section[id^="level-"].ss_active.ss_hideunburned .character-item:not(.burned) {display:none;}'+
'section[id^="level-"].ss_active.ss_hideunlocked .character-item:not(.locked) {display:none;}'+
'section.ss_active .character-item:hover a span {opacity: initial !important; transition:opacity ease-in-out 0.05s !important;}'+
'section.ss_active .character-item:hover a li {opacity: initial !important; transition:opacity ease-in-out 0.05s !important;}'+
'#ss_config {position:absolute; z-index:1029; width:573px; background-color:rgba(0,0,0,0.9); border-radius:8px; padding:8px;}'+
'#ss_config select.configs {width:475px;}'+
'#ss_config label {color:#ccc; text-shadow:initial; text-align:right; vertical-align:baseline;}'+
'#ss_config .btns {display:inline-block; float:left; vertical-align:top; margin-right:8px;}'+
'#ss_config .btns .btn {display:block; margin-bottom:5px;}'+
'#ss_config .btn {width:70px;}'+
'#ss_config .list {overflow-x:auto;}'+
'#ss_config .list select.configs {width:100%; height:135px;}'+
'#ss_config .section {border-top:1px solid #ccc; padding:0 0 8px 0;}'+
'#ss_config .section > label {display:block; text-align:left; color:#ffc; font-size:1.2em; font-weight:bold; padding-left:4px; margin-bottom:4px; background-color:#2e2e2e; background-image:linear-gradient(to bottom, #3c3c3c, #1a1a1a); background-repeat:repeat-x;}'+
'#ss_config .txtline label {display:inline-block; float:left; margin-right:8px; width:100px; line-height:30px; clear:both;}'+
'#ss_config .txtline .expand {overflow-x:auto;}'+
'#ss_config .txtline input {box-sizing:border-box; width:100%; height:30px;}'+
'#ss_config .cbbox {display:inline-block; width:49%; vertical-align:top;}'+
'#ss_config .cbbox label {display:inline-block; float:left; margin:0 8px 0 0; width:190px; line-height:20px;}'+
'#ss_config .cbbox input {position:relative; overflow-x:auto; height:20px; margin:0; top:1px;}'+
'#ss_config [class*="icon-"] {color:#fff;}'+
'#ss_config .dlg_close {text-align:center; margin-top:16px; margin-bottom:8px;}'+
'#ss_quiz [lang="ja"] {font-family: "Meiryo","Yu Gothic","Hiragino Kaku Gothic Pro","TakaoPGothic","Yu Gothic","ヒラギノ角ゴ Pro W3","メイリオ","Osaka","MS PGothic","MS Pゴシック",sans-serif;}'+
'#ss_quiz {position:absolute; z-index:1028; width:573px; background-color:rgba(0,0,0,0.85); border-radius:8px; border:8px solid rgba(0,0,0,0.85); font-size:2em;}'+
'#ss_quiz * {text-align:center;}'+
'#ss_quiz .qwrap {height:8em; position:relative; clear:both;}'+
'#ss_quiz.radicals .qwrap, #ss_quiz.radicals .summary .que {background-color:#0af;}'+
'#ss_quiz.kanji .qwrap, #ss_quiz.kanji .summary .que {background-color:#f0a;}'+
'#ss_quiz.vocabulary .qwrap, #ss_quiz.vocabulary .summary .que {background-color:#a0f;}'+
'#ss_quiz .prev, #ss_quiz .next {display:inline-block; width:80px; color:#fff; line-height:8em; cursor:pointer;}'+
'#ss_quiz .prev:hover {background-image:linear-gradient(to left, rgba(0,0,0,0), rgba(0,0,0,0.2));}'+
'#ss_quiz .next:hover {background-image:linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.2));}'+
'#ss_quiz .prev {float:left;}'+
'#ss_quiz .next {float:right;}'+
'#ss_quiz .topbar {font-size:0.5em; line-height:1em; color: rgba(255,255,255,0.5);}'+
'#ss_quiz .settings {float:left; padding:6px 8px; text-align:left; line-height:1.5em;}'+
'#ss_quiz .settings span[class*="icon-"] {font-size:1.3em; padding:0 2px;}'+
'#ss_quiz .settings .ss_audio {padding-left:0; padding-right:4px;}'+
'#ss_quiz .settings .ss_typo {padding-left:0px;}'+
'#ss_quiz .settings .ss_done {font-size:1.25em;}'+
'#ss_quiz .settings .ss_pair {font-weight:bold;}'+
'#ss_quiz .settings span {cursor:pointer;}'+
'#ss_quiz .settings span:hover {color:rgba(255,255,204,0.8);}'+
'#ss_quiz .settings span.active {color:#ffc;}'+
'#ss_quiz.help .settings .ss_help {color:#ffc;}'+
'#ss_quiz .stats_labels {text-align:right; font-family:monospace;}'+
'#ss_quiz .stats {float:right; text-align:right; color:rgba(255,255,255,0.8); font-family:monospace; padding:0 5px;}'+
'#ss_quiz .round {display:none; font-weight:bold; position:absolute; box-sizing:border-box; width:60%; height:75%; border-radius:24px; border:2px solid #000; background-color:#fff;}'+
'#ss_quiz.round .round {display:block;}'+
'#ss_quiz .question {'+
' overflow-x:auto; overflow-y:hidden; position:relative; top:50%; transform:translateY(-50%);'+
' color:#fff; text-align:center; line-height:1.1em; font-size:1em; font-weight:bold; cursor:default;'+
'}'+
'#ss_quiz .question[data-type="char"] {font-size:2em;}'+
'#ss_quiz .icon-audio:before {content:"\\f028";}'+
'#ss_quiz .question .icon-audio {font-size:2.5em; cursor:pointer;}'+
'#ss_quiz.summary .question {display:none;}'+
'#ss_quiz .qtype {line-height:2em; cursor:default; text-transform:capitalize;}'+
'#ss_quiz .qtype.reading {color:#fff; text-shadow:-1px -1px 0 #000; border-top:1px solid #555; border-bottom:1px solid #000; background-color:#2e2e2e; background-image:linear-gradient(to bottom, #3c3c3c, #1a1a1a); background-repeat:repeat-x;}'+
'#ss_quiz .qtype.meaning {color:#555; text-shadow:-1px -1px 0 rgba(255,255,255,0.1); border-top:1px solid #d5d5d5; border-bottom:1px solid #c8c8c8; background-color:#e9e9e9; background-image:linear-gradient(to bottom, #eee, #e1e1e1); background-repeat:repeat-x;}'+
'#ss_quiz .help {display:none;'+
' position:absolute; top:3%; left:13%; width:74%; box-sizing:border-box; border:2px solid #000; border-radius:15px; padding:4px;'+
' color:#555; text-shadow:2px 2px 0 rgba(0,0,0,0.2); background-color:rgba(255,255,255,0.9); font-size:0.8em; line-height:1.2em;'+
'}'+
'#ss_quiz.help .help {display:inherit;}'+
'#ss_quiz .answer {background-color:#ddd; padding:8px;}'+
'#ss_quiz .answer input {'+
' width:100%; background-color:#fff; height:2em; margin:0; border:2px solid #000; padding:0;'+
' box-sizing:border-box; border-radius:0; font-size:1em;'+
'}'+
'#ss_quiz .answer input.correct {color:#fff; background-color:#8c8; text-shadow:2px 2px 0 rgba(0,0,0,0.2);}'+
'#ss_quiz .answer input.incorrect {color:#fff; background-color:#f03; text-shadow:2px 2px 0 rgba(0,0,0,0.2);}'+
'#ss_quiz.loading .qwrap, #ss_quiz.loading .answer {display:none;}'+
'#ss_quiz .summary {display:none; position:absolute; width:74%; height:100%; background-color:rgba(0,0,0,0.7); color:#fff; font-weight:bold;}'+
'#ss_quiz.summary .summary {display:block;}'+
'#ss_quiz .summary h3 {'+
' background-image:linear-gradient(to bottom, #3c3c3c, #1a1a1a); background-repeat:repeat-x;'+
' border-top:1px solid #777; border-bottom:1px solid #000; margin:0; box-sizing:border-box;'+
' text-shadow:2px 2px 0 rgba(0,0,0,0.5); color:#fff; font-size:0.8em; font-weight:bold; line-height:40px;'+
'}'+
'#ss_quiz .summary .errors {position:absolute; top:40px; bottom:0px; width:100%; margin:0; overflow-y:auto; list-style-type:none;}'+
'#ss_quiz .summary li {margin:4px 0 0 0; font-size:0.6em; font-weight:bold; line-height:1.4em;}'+
'#ss_quiz .summary .errors span {display:inline-block; padding:2px 4px 0px 4px; border-radius:4px; line-height:1.1em; max-width:50%; vertical-align:middle; cursor:pointer;}'+
'#ss_quiz .summary .ans {background-color:#fff; color:#000;}'+
'#ss_quiz .summary .wrong {color:#f22;}'+
'#ss_quiz .btn.requiz {position:absolute; top:6px; right:6px; padding-left:6px; padding-right:6px;}'+
'';
var cfg_tmp;
// Jaro-Winkler Distance
function jw_distance(a, c) {
var h, b, d, k, e, g, f, l, n, m, p;
if (a.length > c.length) {
c = [c, a];
a = c[0];
c = c[1];
}
k = ~~Math.max(0, c.length / 2 - 1);
e = [];
g = [];
b = n = 0;
for (p = a.length; n < p; b = ++n) {
for (h = a[b], l = Math.max(0, b - k), f = Math.min(b + k + 1, c.length), d = m = l; l <= f ? m < f : m > f; d = l <= f ? ++m : --m) {
if (g[d] === undefined && h === c[d]) {
e[b] = h;
g[d] = c[d];
break;
}
}
}
e = e.join("");
g = g.join("");
d = e.length;
if (d) {
b = f = k = 0;
for (l = e.length; f < l; b = ++f) {
h = e[b];
if (h !== g[b]) k++;
}
b = g = e = 0;
for (f = a.length; g < f; b = ++g) {
if (h = a[b], h === c[b])
e++;
else
break;
}
a = (d/a.length + d/c.length + (d - ~~(k/2))/d)/3;
a += 0.1 * Math.min(e, 4) * (1 - a);
} else {
a = 0;
}
return a;
}
//-------------------------------------------------------------------
// Open the configuration dialog.
//-------------------------------------------------------------------
function configure(e) {
var sel, ssgrp, dialog;
function setup() {
dialog = $(config_html).appendTo(ssgrp);
sel = $('#ss_config select.configs');
// "New" handler
dialog.find('button.new').on('click', function() {
cfg_tmp.push(['<new>','']);
sel.append('<option value="'+(cfg_tmp.length-1)+'"><new></option>');
select_config(sel.children().length-1);
$('#ss_config .preset').focus().select();
});
// "Delete" handler
dialog.find('button.del').on('click', function() {
var opt = sel.find(':selected');
var idx = opt.index();
opt.remove();
var len = sel.children().length;
if (idx >= len) idx = len-1;
select_config(idx);
});
// "Up" handler
dialog.find('button.up').on('click', function() {
var opt = sel.find(':selected');
if (opt.index() > 0) opt.insertBefore(opt.prev());
});
// "Down" handler
dialog.find('button.dn').on('click', function() {
var opt = sel.find(':selected');
if (opt.index() < sel.children().length-1) opt.insertAfter(opt.next());
});
// "Configs" selection changed
sel.on('change', function() {
select_config(sel.find(':selected').index());
});
// "Preset" name changed
dialog.find('.preset').on('change', function(e) {
var opt = sel.find(':selected');
var text = e.currentTarget.value;
opt.text(text);
var idx = opt.val();
cfg_tmp[idx][0] = text;
});
// "Checkbox" changed
dialog.find('input[type="checkbox"]').on('change', function() {
var opt = sel.find(':selected');
var idx = opt.val();
var props = [];
dialog.find('input[type="checkbox"]:checked').each(function(i,e){props.push(e.name);});
cfg_tmp[idx][1] = props.join(' ');
});
// "Save" handler
dialog.find('button.save').on('click', save_config);
// "Cancel" handler
dialog.find('button.cancel').on('click', cancel_config);
}
function save_config() {
settings.configs = [];
sel.children().each(function(i,v){
var idx = $(v).val();
settings.configs.push(cfg_tmp[idx].slice(0));
});
settings.selected_config = sel.find(':selected').index();
save_settings();
dialog.addClass('hidden');
populate_presets();
set_config(settings.selected_config);
}
function cancel_config() {
cfg_tmp = undefined;
dialog.addClass('hidden');
}
function select_config(idx) {
var opt = sel.children().eq(idx);
opt.prop('selected',true);
$('#ss_config input.preset').val(opt.text());
var props = cfg_tmp[opt.val()][1];
$('#ss_config .cbbox input').prop('checked', false);
props.split(' ').forEach(function(prop,i){
$('#ss_config .cbbox input[name="'+prop+'"]').prop('checked', true);
});
}
ssgrp = $(e.currentTarget).closest('.selfstudy');
dialog = $('#ss_config');
if (dialog.length === 0) {
setup();
} else if (dialog.is(':visible')) {
return cancel_config();
} else {
ssgrp.append(dialog);
sel = $('#ss_config select.configs');
}
// Clone the existing settings.
var options = [];
cfg_tmp = settings.configs.map(function(e,i){
options.push('<option value="'+i+'">'+e[0]+'</option>');
return e.slice(0);
});
// Populate configs.
sel.html(options.join(''));
select_config(settings.selected_config);
// Unhide the config dialog.
var top = ssgrp.find('.btn-group').height() + 4;
dialog.css('top',top).removeClass('hidden');
}
//-------------------------------------------------------------------
// Save settings.
//-------------------------------------------------------------------
function save_settings() {
localStorage.setItem('selfstudy_settings', JSON.stringify(settings));
}
//-------------------------------------------------------------------
// Button event handler.
//-------------------------------------------------------------------
function toggle_enable() {
settings.enabled = !settings.enabled;
save_settings();
set_enable();
}
//-------------------------------------------------------------------
// Button event handler.
//-------------------------------------------------------------------
function config_change_event(e) {
set_config(Number(e.currentTarget.value));
}
//-------------------------------------------------------------------
// Add a shuffle function to Array and jQuery.
//-------------------------------------------------------------------
function fisher_yates_shuffle() {
var i = this.length, j, temp;
if (i===0) return this;
while (--i) {
j = Math.floor(Math.random()*(i+1));
temp = this[i]; this[i] = this[j]; this[j] = temp;
}
return this;
}
if (typeof Array.prototype.shuffle !== 'function') Array.prototype.shuffle = fisher_yates_shuffle;
$.fn.shuffle = fisher_yates_shuffle;
//-------------------------------------------------------------------
// Shuffle items.
//-------------------------------------------------------------------
function shuffle(e) {
if (e === undefined) {
// Shuffle all
$('section[id^="level-"]').each(function(){
var sec = $(this);
sec.find('[class$="-character-grid"]').append(sec.find('.character-item').detach().shuffle());
});
} else {
// Shuffle specific group
var btn = $(e.currentTarget);
var sec = btn.closest('section[id^="level-"]');
sec.find('[class$="-character-grid"]').append(sec.find('.character-item').detach().shuffle());
quiz.refresh();
}
}
//-------------------------------------------------------------------
// Enable or disable the plugin.
//-------------------------------------------------------------------
function set_enable() {
var btns = $('.selfstudy button.enable');
var secs = $('section[id^="level-"]');
if (settings.enabled) {
secs.addClass('ss_active');
btns.addClass('on').text('ON');
} else {
secs.removeClass('ss_active');
btns.removeClass('on').text('OFF');
}
}
//-------------------------------------------------------------------
// Select a configuration.
//-------------------------------------------------------------------
function set_config(val) {
var secs = $('section[id^="level-"]');
// Remove all ss_* classes except ss_alive
secs.each(function(i,e){
e.className = e.className.split(' ').filter(function(v){return (v.match(/^ss_(?!active)/) === null);}).join(' ');
});
settings.selected_config = val;
save_settings();
$('.selfstudy select.config').val(val);
settings.configs[settings.selected_config][1].split(' ').forEach(function(cfgopt,idx){
secs.addClass(cfgopt);
});
quiz.refresh();
}
//-------------------------------------------------------------------
// Populate the presets into the drop-down box.
//-------------------------------------------------------------------
function populate_presets() {
var options = [];
settings.configs.forEach(function(config,idx){
var cfgname = config[0];
var cfgopts = config[1];
options.push('<option value="'+idx+'">'+cfgname+'</option>');
});
$('.selfstudy select.config').html(options.join(''));
}
//-------------------------------------------------------------------
// Make first letter of each word upper-case.
//-------------------------------------------------------------------
function toTitleCase(str) {
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
//-------------------------------------------------------------------
// Trim surrounding whitespace
//-------------------------------------------------------------------
function trim(str) {
return str.replace(/^\s*|\s*$/g,'');
}
var quiz = {};
gobj.quiz = quiz;
(function (quiz) {
var ssgrp, sec, dialog, apikey, itype, ichar, level, items, order, requiz_order, wanakana_isbound = false;
var round, correct, incorrect, quiz_idx, quiz_max, answered;
var items_cache = {radicals:[],kanji:[],vocabulary:[]};
var good_answers, all_answers, alang, qlang, atype, qtype, force_summary;
// Make the items cache accessible from the console (via wkselfstudy.items_cache)
gobj.items_cache = items_cache;
//-------------------------------------------------------------------
// Quiz on all the items in the selected section.
//-------------------------------------------------------------------
quiz.open = function(e) {
if (e !== undefined && setup(e) === 'closed') return;
fetch_data();
};
quiz.close = function() {
dialog.addClass('hidden');
$('body').off('.ss_quiz');
};
quiz.is_open = function() {
var dlg = $('#ss_quiz');
if (dlg.length===0 || dlg.hasClass('hidden')) return false;
return true;
};
quiz.refresh = function() {
if (!quiz.is_open()) return;
quiz.open();
};
function reset_answer() {
$('#ss_quiz .answer input').prop('readonly',false).removeClass('correct incorrect').val('').focus();
}
function set_pairing(value) {
var text = [
'Disabled',
'Reading first',
'Meaning first'
][settings.quiz_pairing];
$('#ss_quiz .ss_pair').attr('data-value',settings.quiz_pairing).children('.data').text(text);
quiz.refresh();
}
function set_help(value) {
if (value) {
dialog.addClass('help');
} else {
dialog.removeClass('help');
}
}
function toggle_pair() {
var elem = $('#ss_quiz .settings .ss_pair');
var pairing = (Number(elem.attr('data-value'))+1)%3;
settings.quiz_pairing = pairing;
set_pairing(pairing);
save_settings();
}
function toggle_lightning() {
var elem = $('#ss_quiz .settings .ss_lightning');
elem.toggleClass('active');
settings.lightning_mode = elem.hasClass('active');
save_settings();
}
function toggle_audio() {
var elem = $('#ss_quiz .settings .ss_audio');
elem.toggleClass('active');
settings.audio_mode = elem.hasClass('active');
if (settings.audio_mode && itype==='vocabulary' && atype==='reading') audio.load();
save_settings();
}
function toggle_repeat() {
var elem = $('#ss_quiz .settings .ss_repeat');
elem.toggleClass('active');
settings.quiz_repeat = elem.hasClass('active');
save_settings();
}
function toggle_shuffle() {
var elem = $('#ss_quiz .settings .ss_shuffle');
elem.toggleClass('active');
settings.quiz_shuffle = elem.hasClass('active');
save_settings();
}
function toggle_typo() {
var elem = $('#ss_quiz .settings .ss_typo');
elem.toggleClass('active');
settings.quiz_typo = elem.hasClass('active');
save_settings();
}
function toggle_help() {
if (quiz_idx < 0 || quiz_idx > quiz_max) return;
$('#ss_quiz').toggleClass('help');
if (settings.audio_mode && $('#ss_quiz').hasClass('help') && itype==='vocabulary' && atype==='reading') {
audio.play();
}
}
var audio = {
urls: [],
name: '',
level: 0,
request_load: false,
loaded: false,
clear: function() {
audio.name = '';
audio.level = 0;
audio.request_load = false;
audio.loaded = false;
dialog.find('audio.old').remove();
dialog.find('audio').each(function(i,tag){
if (tag.paused) {
$(tag).remove();
} else {
$(tag).addClass('old');
tag.onended = function(event) {
$(event.target).remove();
}
}
});
},
load_urls: function(level) {
if (audio.urls[level] !== undefined) return;
$.getJSON('https://www.idigtech.com/wanikani/json/audio_urls/'+level+'.json', function(json, status, xhr){
if (status !== 'success') return;
audio.urls[level] = json;
if (audio.request_load) {
audio.set(audio.name, audio.level, audio.request_load);
}
});
},
load: function() {
if (audio.loaded) return;
dialog.find('audio:not(.old)').each(function(i,tag){
tag.load();
audio.request_load = false;
audio.loaded=true;
});
},
set: function(name, level, preload) {
if (name !== audio.name) audio.loaded = false;
if (audio.loaded) return;
audio.clear();
audio.name = name;
audio.level = level;
audio.request_load = preload;
if (audio.urls[level] === undefined) return audio.load_urls(level);
dialog.append('<audio><source src="'+audio.urls[level][name]+'" type="audio/mpeg"></audio>');
if (audio.request_load) audio.load();
},
play: function() {
if (!audio.loaded) audio.load();
dialog.find('audio:not(.old)').each(function(i,tag){
if (!tag.paused) {
tag.currentTime = 0;
} else {
tag.play();
}
});
}
};
quiz.audio = audio;
function goto_summary() {
quiz_idx = quiz_max;
force_summary = true;
next_question();
}
function settings_handler(e) {
var cname = $(e.currentTarget).attr('class').match(/\bss_\S*\b/)[0];
switch (cname) {
case 'ss_pair': toggle_pair(); break;
case 'ss_lightning': toggle_lightning(); break;
case 'ss_audio': toggle_audio(); break;
case 'ss_repeat': toggle_repeat(); break;
case 'ss_shuffle': toggle_shuffle(); break;
case 'ss_typo': toggle_typo(); break;
case 'ss_help': toggle_help(); break;
case 'ss_done': goto_summary(); break;
}
}
function scroll_errors(e) {
var t = e.currentTarget;
// If scrollbar is visible...
if (t.scrollHeight > t.clientHeight) {
var delta = e.originalEvent.deltaY;
// ...and we are scrolling beyond the limit...
if ((delta < 0 && (t.scrollTop <= 0)) ||
(delta > 0 && (t.scrollTop+t.clientHeight >= t.scrollHeight))) {
// ...prevent scroll from bubbling to window.
e.preventDefault();
e.stopPropagation();
}
}
}
function requiz(e) {
order = requiz_order.shuffle().slice(0);
requiz_order = [];
round = 1;
correct = 0;
incorrect = 0
answered = false;
quiz_idx = 0;
quiz_max = order.length-1;
force_summary = false;
dialog.find('.errors').html('');
do_quiz();
}
function setup(e) {
ssgrp = $(e.currentTarget).closest('.selfstudy');
sec = $(e.currentTarget).closest('section[id^="level-"]');
var id = sec.attr('id').split('-');
level = id[1];
itype = window.location.pathname.split('/')[1];
if (itype === 'level') itype = id[2];
dialog = $('#ss_quiz');
if (dialog.length === 0) {
dialog = $(quiz_html).appendTo(ssgrp);
// "Prev" button handler
dialog.find('.prev').on('click', prev_question);
// "Next" button handler
dialog.find('.next').on('click', next_question);
// "Enter" handler for answer
dialog.find('.answer input').on('keydown keypress', quiz_key);
// "Enter" handler for answer
dialog.find('.settings').on('click', '>span', settings_handler);
// Handle scrollbar inside errors window.
dialog.find('.errors').on('wheel', scroll_errors);
// "Re-quiz" handler
dialog.find('.summary .requiz').on('click', requiz);
// Audio-click handler
dialog.find('.question').on('click', '.icon-audio', audio.play);
} else if (dialog.is(':visible')) {
if (ssgrp.find('#ss_quiz').length === 0) {
ssgrp.append(dialog);
} else {
dialog.addClass('hidden');
return 'closed';
}
} else {
ssgrp.append(dialog);
}
if (settings.lightning_mode) dialog.find('.ss_lightning').addClass('active');
if (settings.audio_mode) dialog.find('.ss_audio').addClass('active');
if (settings.quiz_repeat) dialog.find('.ss_repeat').addClass('active');
if (settings.quiz_shuffle) dialog.find('.ss_shuffle').addClass('active');
if (settings.quiz_typo) dialog.find('.ss_typo').addClass('active');
set_pairing(settings.quiz_pairing);
}
function show_error() {
console.log('wkselfstudy: Failed to get API key!');
}
function is_apikey_valid(apikey) {
return (apikey !== null && apikey.match(/^[0-9a-f]{32}$/) !== null);
}
function get_apikey() {
apikey = localStorage.getItem('apiKey');
if (is_apikey_valid(apikey)) return true;
$.get('/settings/account')
.done(function(page, status, xhr){
if (status !== 'success') return show_error();
apikey = $(page).find('#user_api_key').attr('value');
if (apikey === undefined || apikey.match(/^[0-9a-f]{32}$/) === null) return show_error();
localStorage.setItem('apiKey', apikey);
fetch_data();
})
.fail(show_error);
return false;
}
function fetch_data() {
round = 1;
correct = 0;
incorrect = 0;
answered = false;
quiz_idx = 0;
force_summary = false;
dialog.find('.summary .errors').html('');
if (!get_apikey()) return;
if (itype==='vocabulary') audio.load_urls(level);
if (items_cache[itype][level] !== undefined) return create_quiz();
dialog.attr('class','loading');
dialog.find('.qtype').removeClass('meaning').addClass('reading').html('<strong>Loading...</strong>');
$.getJSON('/api/user/'+apikey+'/'+itype+'/'+level)
.done(function(json, status, xhr){
if (status !== 'success') return show_error();
items_cache[itype][level] = json.requested_information;
create_quiz();
})
.fail(show_error);
}
function is_unlocked(item) {
return (item.user_specific !== null) && (item.user_specific.unlocked_date !== null) && (item.user_specific.unlocked_date > 0);
}
function is_locked(item) {
return !is_unlocked(item);
}
function is_burned(item) {
return is_unlocked(item) && (item.user_specific.burned === true || item.user_specific.burned_date > 0);
}
function is_unburned(item) {
return !is_burned(item);
}
function create_quiz() {
var char_to_mean, char_to_read, read_to_mean, mean_to_read, aud_to_read, aud_to_mean;
items = items_cache[itype][level].slice(0);
// Remove any items that aren't included in the current selection.
if (sec.hasClass('ss_hidelocked')) items = items.filter(is_unlocked);
if (sec.hasClass('ss_hideunlocked')) items = items.filter(is_locked);
if (sec.hasClass('ss_hideburned')) items = items.filter(is_unburned);
if (sec.hasClass('ss_hideunburned')) items = items.filter(is_burned);
if (itype==='radicals') {
char_to_mean = true;
char_to_read = false;
read_to_mean = false;
mean_to_read = false;
aud_to_mean = false;
} else {
char_to_mean = sec.hasClass('ss_quizctom');
char_to_read = (sec.hasClass('ss_quizctor') && (itype!=='radicals'));
read_to_mean = sec.hasClass('ss_quizrtom');
mean_to_read = sec.hasClass('ss_quizmtor');
aud_to_read = (sec.hasClass('ss_quizator') && (itype==='vocabulary'));
aud_to_mean = (sec.hasClass('ss_quizatom') && (itype==='vocabulary'));
}
var idx, idx2, max = items.length;
order = []; requiz_order=[];
var tmp_order = [];
switch (settings.quiz_pairing) {
case 0: // No pairing
for (idx=0; idx<max; idx++) {
if (aud_to_read) order.push([idx,3,1]);
if (aud_to_mean) order.push([idx,3,2]);
if (char_to_mean) order.push([idx,0,2]);
if (char_to_read) order.push([idx,0,1]);
if (read_to_mean) order.push([idx,1,2]);
if (mean_to_read) order.push([idx,2,1]);
}
order.shuffle();
do_quiz(true /* requeue when wrong */);
break;
case 1: // Reading first
for (idx=0; idx<max; idx++) tmp_order.push(idx);
tmp_order.shuffle();
for (idx2=0; idx2<max; idx2++) {
idx = tmp_order[idx2];
if (aud_to_read) order.push([idx,3,1]);
if (aud_to_mean) order.push([idx,3,2]);
if (char_to_read) order.push([idx,0,1]);
if (mean_to_read) order.push([idx,2,1]);
if (char_to_mean) order.push([idx,0,2]);
if (read_to_mean) order.push([idx,1,2]);
}
do_quiz(false /* repeat when wrong */);
break;
case 2: // Meaning first
for (idx=0; idx<max; idx++) tmp_order.push(idx);
tmp_order.shuffle();
for (idx2=0; idx2<max; idx2++) {
idx = tmp_order[idx2];
if (aud_to_mean) order.push([idx,3,2]);
if (char_to_mean) order.push([idx,0,2]);
if (read_to_mean) order.push([idx,1,2]);
if (aud_to_read) order.push([idx,3,1]);
if (char_to_read) order.push([idx,0,1]);
if (mean_to_read) order.push([idx,2,1]);
}
do_quiz(false /* repeat when wrong */);
break;
}
}
function do_quiz(requeue_when_wrong) {
quiz_idx = (round > 1 ? -1 : 0);
// Unhide the config dialog.
var top = ssgrp.find('.btn-group').height() + 4;
dialog.css('top',top).attr('class', itype);
quiz_max = order.length-1;
update_stats();
update_question();
}
function item_meaning(item) {
var arr = [];
if (item.user_specific && item.user_specific.user_synonyms !== null)
arr = item.user_specific.user_synonyms.map(function(v){return trim(v.replace('-',' '));});
arr = arr.concat(item.meaning.split(',').map(function(v){return trim(v.replace('-',' '));}));
return arr;
}
function item_reading(item, good_only) {
var arr = [];
if (item.kana) { // vocab
arr = arr.concat(item.kana.split(',').map(function(v){return v.replace(/^\s*|\..*\s*$/g,'');}));
} else if (item.important_reading) { // kanji
if (good_only) {
arr = arr.concat(item[item.important_reading].split(',').map(function(v){return v.replace(/^\s*|\..*\s*$/g,'');}));
} else {
if (item.onyomi) arr = arr.concat(item.onyomi.split(',').map(function(v){return v.replace(/^\s*|\..*\s*$/g,'');}));
if (item.kunyomi) arr = arr.concat(item.kunyomi.split(',').map(function(v){return v.replace(/^\s*|\..*\s*$/g,'');}));
if (item.nanori) arr = arr.concat(item.nanori.split(',').map(function(v){return v.replace(/^\s*|\..*\s*$/g,'');}));
}
}
return arr;
}
function update_stats() {
var remaining = quiz_max;
if (quiz_idx > 0) remaining -= quiz_idx;
if (!answered) remaining++;
$('#ss_quiz .stats').html([round, remaining, correct, incorrect].join('<br>'));
}
function update_question() {
if (quiz_idx === -1) {
dialog.find('.round span').text('Round '+round);
dialog.find('.qtype').removeClass('meaning').addClass('reading').html('Press [enter] to continue');
dialog.addClass('round');
$('body').on('keydown.ss_quiz keypress.ss_quiz', quiz_key);
return;
} else if (quiz_idx > quiz_max) {
var percent = Math.floor(100*correct/Math.max(1,(correct+incorrect)));
dialog.find('.summary .percent').text(percent+'%');
if ((percent === 100) || (correct+incorrect === 0))
dialog.find('.summary .requiz').addClass('hidden');
else
dialog.find('.summary .requiz').removeClass('hidden');
dialog.find('.qtype').removeClass('meaning').addClass('reading').html('Press [enter] to continue');
dialog.removeClass('round').addClass('summary');
$('body').on('keydown.ss_quiz keypress.ss_quiz', quiz_key);
return;
}
if (quiz_idx === 0) dialog.removeClass('round');
var qinfo = order[quiz_idx];
qtype = ['char','read','mean', 'aud'][qinfo[1]];
atype = ['','reading','meaning'][qinfo[2]];
var qtext;
var answer = $('#ss_quiz .answer input');
qlang = ''; alang = '';
reset_answer();
answered = false;
all_answers = [];
answer.val('');
if (atype === 'reading') {
if (!wanakana_isbound) {
wanakana.bind(answer[0]);
wanakana_isbound = true;
}
} else {
if (wanakana_isbound) {
wanakana.unbind(answer[0]);
wanakana_isbound = false;
}
}
item = items[qinfo[0]];
if (itype === 'radicals') {
qlang = 'ja';
if (item.character !== null)
qtext = item.character;
else
qtext = '<i class="radical-'+item.meaning+'"></i>';
good_answers = item_meaning(item);
all_answers = good_answers;
} else {
var mean_arr = item_meaning(item);
var imp_read_arr = item_reading(item,true);
var all_read_arr = item_reading(item);
switch (qtype) {
case 'char':
qlang = 'ja';
qtext = item.character;
break;
case 'read':
qlang = 'ja';
qtext = toTitleCase(all_read_arr.join(', '));
break;
case 'mean':
qtext = toTitleCase(item_meaning(item).join(', '));
break;
case 'aud':
qtext = '<i class="icon-audio"></i>';
ichar = item.character;
break;
}
if (atype === 'reading') {
alang = 'ja';
good_answers = imp_read_arr;
all_answers = all_read_arr.concat(mean_arr);
} else {
good_answers = mean_arr;
all_answers = mean_arr.concat(all_read_arr);
}
}
var help_text = toTitleCase(good_answers.join(', '));
if (qtype !== 'char') help_text += '<br>(<span lang="ja">'+item.character+'</span>)';
dialog.find('.question').attr('data-type', qtype).attr('lang',qlang).html(qtext);
dialog.find('.help').html(help_text).attr('lang',alang);
var type_text = (itype==='radicals' ? 'radical' : itype) + ' <strong>'+atype+'</strong>';
dialog.find('.qtype').removeClass('reading meaning').addClass(atype).html(type_text);
if (itype==='vocabulary') {
var play_audio_now = (qtype==='aud');
var preload_audio = (play_audio_now || (settings.audio_mode && atype==='reading'));
audio.set(item.character, level, preload_audio);
if (play_audio_now) {
audio.play();
}
} else {
audio.clear();
}
$('#ss_quiz .answer input').attr('lang',alang).focus().select();
}
function prev_question(e) {
if (quiz_idx === 0) return;
quiz_idx--;
if (e !== undefined) update_stats();
if (quiz_idx === quiz_max) dialog.removeClass('round summary');
update_question();
}
function next_question(e, prevent_exit) {
quiz_idx++;
if (quiz_idx > quiz_max) {
if (!settings.quiz_repeat || force_summary) {
if (quiz_idx > quiz_max+1) {
if (!prevent_exit)
quiz.close();
else
quiz_idx--;
return;
}
if (e !== undefined) update_stats();
} else if (settings.quiz_shuffle) {
dialog.removeClass('round summary');
round++;
answered = false;
update_stats();
create_quiz();
}
} else {
if (e !== undefined) update_stats();
}
update_question();
}
function shake(elem) {
var dist = '25px';
var speed = 100;
var right = {padding:'0 '+dist+' 0 0'}, left = {padding:'0 0 0 '+dist}, center = {padding:"0 0 0 0"};
elem.animate(left,speed/2).animate(right,speed)
.animate(left,speed).animate(right,speed)
.animate(left,speed).animate(center,speed/2);
}
function quiz_submit(e) {
var input = $('#ss_quiz .answer input');
// Handle keys for 'Round X' and 'Summary'
if (quiz_idx === -1 || quiz_idx > quiz_max) {
next_question();
return;
}
if (input.hasClass('correct')) {
set_help(false);
reset_answer();
next_question();
return;
} else if (input.hasClass('incorrect')) {
set_help(false);
reset_answer();
return;
}
var answer = trim(input.val().toLowerCase());
if (answer === '') return;
var is_correct;
if (settings.quiz_typo && alang != 'ja') {
is_correct = (good_answers.filter(function(a){return (jw_distance(a,answer)>0.9);}).length > 0);
} else {
is_correct = (good_answers.indexOf(answer) >= 0);
}
if (is_correct) {
if (!answered) {
correct++;
answered = true;
update_stats();
}
if (itype==='vocabulary' && settings.audio_mode && qtype!=='aud' && atype==='reading') {
audio.play();
}
if (settings.lightning_mode) return next_question();
input.addClass('correct').prop('readonly',true);
$('body').on('keydown.ss_quiz keypress.ss_quiz', quiz_key);
} else if (all_answers.indexOf(answer) >= 0 ||
(alang === 'ja' ?
(!wanakana.isKana(answer) || all_answers.indexOf(wanakana.toRomaji(answer)) >= 0) :
(all_answers.indexOf(wanakana.toHiragana(answer)) >= 0)
)
) {
shake(input);
} else {
if (!answered) {
incorrect++;
answered = true;
update_stats();
requiz_order.push(order[quiz_idx]);
}
input.addClass('incorrect').prop('readonly',true);
$('body').on('keydown.ss_quiz keypress.ss_quiz', quiz_key);
dialog.find('.summary .errors').append(
'<li><span class="que"'+(qlang==='ja'?' lang="ja"':'')+' title="'+toTitleCase(dialog.find('.qtype').text())+'">'+
(qtype==='aud' ? ichar+' ' : '')+dialog.find('.question').html()+'</span> '+
'<i class="icon-long-arrow-right"></i> '+
'<span class="ans"'+(alang==='ja'?' lang="ja"':'')+' title="'+good_answers.join(', ')+'">'+
answer+' <i class="icon-remove-sign wrong"></i></span></li>'
);
}
}
function quiz_key(e) {
$('body').off('.ss_quiz');
var code;
if (e.type==='keydown') {
if (e.originalEvent.code === undefined) {
// Shim for Safari's lack of support for KeyboardEvent.code
switch (e.originalEvent.keyCode) {
case 8: code = 'Backspace'; break;
case 13: code = 'Enter'; break;
case 27: code = 'Escape'; break;
case 37: code = 'ArrowLeft'; break;
case 39: code = 'ArrowRight'; break;
case 65: code = 'KeyA'; break;
case 69: code = 'KeyE'; break;
case 72: code = 'KeyH'; break;
case 76: code = 'KeyL'; break;
case 79: code = 'KeyO'; break;
case 82: code = 'KeyR'; break;
case 83: code = 'KeyS'; break;
case 112: code = 'F1'; break;
default: code = 'Unknown'; break;
}
} else {
code = e.originalEvent.code;
}
} else {
code = String.fromCharCode(e.charCode);
}
if (code==='Enter') {
quiz_submit(e);
} else if (code==='Escape') { // Esc
if (quiz_idx > quiz_max)
quiz_submit(e);
else
goto_summary();
} else if (code==='F1' || code==='?') { // F1 or ?
toggle_help();
} else if (code==='Backspace' && $('.answer input').prop('readonly')) { // Prevent backspace from navigating away from the page
e.preventDefault();
e.stopPropagation();
} else if (e.ctrlKey || e.metaKey) {
switch(code) {
case 'KeyA':
if (e.shiftKey) {
toggle_audio();
} else {
if (itype==='vocabulary') {
audio.play();
}
}
break; // Audio
case 'KeyE': goto_summary(); break; // End
case 'KeyH': toggle_help(); break; // Help
case 'KeyL': toggle_lightning(); break; // Lightning
case 'KeyO': toggle_typo(); break; // Typo ("oops")
case 'KeyR': toggle_repeat(); break; // Repeat
case 'KeyS': if (e.shiftKey) quiz.refresh(); else toggle_shuffle(); break; // Shuffle
case 'ArrowLeft' : prev_question(true /* update stats */, true /* prevent_exit */); break; // Previous question
case 'ArrowRight': next_question(true /* update stats */, true /* prevent_exit */); break; // Next question
}
} else {
return;
}
e.preventDefault();
e.stopPropagation();
}
})(quiz);
//-------------------------------------------------------------------
// Startup. Runs at document 'load' event.
//-------------------------------------------------------------------
function startup() {
// Load settings.
var s = localStorage.getItem('selfstudy_settings');
if (s) {
s = JSON.parse(s);
if (s.compatible !== undefined && s.compatible == settings.compatible) {
delete settings.configs;
$.extend(true, settings, s);
}
}
// Insert CSS
$('head').append('<style type="text/css">'+css+'</style>');
// Insert HTML
$('section[id^="level-"]').prepend(html);
populate_presets();
// Install handlers
$('.selfstudy button.enable').on('click', toggle_enable);
$('.selfstudy button.quiz').on('click', quiz.open);
$('.selfstudy button.shuffle').on('click', shuffle);
$('.selfstudy select.config').on('change', config_change_event);
$('.selfstudy button.config').on('click', configure);
set_config(settings.selected_config);
if (settings.enabled) {
set_enable();
shuffle();
}
}
// Run startup() after window.onload event.
if (document.readyState === 'complete')
startup();
else
window.addEventListener("load", startup, false);
})(window.wkselfstudy);