// ==UserScript==
// @name Wanikani Self-Study Quiz
// @namespace rfindley
// @description Quiz yourself on Wanikani items
// @version 3.0.0
// @include https://www.wanikani.com/*
// @require https://unpkg.com/wanakana
// @copyright 2018+, Robin Findley
// @license MIT; http://opensource.org/licenses/MIT
// @run-at document-end
// @grant none
// @downloadURL none
// ==/UserScript==
window.ss_quiz = {};
(function(gobj) {
var wkof_version_needed = '1.0.14';
if (!window.wkof) {
alert('Self-Study Quiz script requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.');
window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
return;
}
if (!wkof.version || wkof.version.compare_to(wkof_version_needed) === 'older') {
alert('Self-Study Quiz script requires Wanikani Open Framework version '+wkof_version_needed+'.\nYou will now be forwarded to update page.');
window.location.href = 'https://greasyfork.org/en/scripts/38582-wanikani-open-framework';
return;
}
wkof.include('Menu');
wkof.ready('Menu').then(install_menu);
function install_menu() {
wkof.Menu.insert_script_link({
name: 'selfstudyquiz',
submenu: 'Open',
title: 'Self-Study Quiz',
on_click: open_quiz
});
}
//########################################################################
// QUIZ SETTINGS DIALOG
//########################################################################
//========================================================================
// setup_quiz_settings()
//------------------------------------------------------------------------
var quiz_settings_state = 'init';
function setup_quiz_settings() {
if (quiz_settings_state === 'init') {
quiz_settings_state = 'loading';
return wkof.ready('Settings')
.then(function(){
quiz_settings_state = 'setup';
setup_quiz_settings();
});
}
if (quiz_settings_state !== 'setup') return;
var config = {
script_id: 'ss_quiz',
title: 'Self-Study Quiz',
pre_open: preopen_quiz_settings,
on_save: save_quiz_settings,
on_close: close_quiz_settings,
on_refresh: refresh_quiz_settings,
no_bkgd: true,
settings: {
pg_questions: {type:'page',label:'Questions',hover_tip:'Choose what quiz questions you want to be asked',content:{
grp_qpre_list: {type:'group',label:'Presets List',content:{
active_qpreset: {type:'list',refresh_on_change:true,hover_tip:'Question Presets',content:{}},
}},
grp_qpre: {type:'group',label:'Selected Preset',content:{
sect_qpre_name: {type:'section',label:'Preset Name'},
qpre_name: {type:'text',label:'Edit Preset Name',on_change:refresh_qpresets,path:'@qpresets[@active_qpreset].name',hover_tip:'Enter a name for the selected preset'},
sect_qpre_question: {type:'section',label:'Questions Answers'},
char2mean: {type:'checkbox',label:'Rad/Kan/Voc Meaning',path:'@qpresets[@active_qpreset].content.char2mean',hover_tip:'Question: A radical or kanji character, or vocab word drawn with kanji\nAnswer: The meaning in English'},
char2read: {type:'checkbox',label:'Kan/Voc Reading',path:'@qpresets[@active_qpreset].content.char2read',hover_tip:'Question: A kanji character, or vocab word drawn with kanji\nAnswer: The Japanese reading, in hiragana or katakana'},
read2mean: {type:'checkbox',label:'Voc Reading Meaning',path:'@qpresets[@active_qpreset].content.read2mean',hover_tip:'Question: A kanji or vocab reading, in hiragana or katakana\nAnswer: The meaning in English'},
mean2read: {type:'checkbox',label:'Voc Meaning Reading',path:'@qpresets[@active_qpreset].content.mean2read',hover_tip:'Question: A vocab word in English\nAnswer: The Japanese reading, in hiragana or katakana'},
aud2mean: {type:'checkbox',label:'Voc Audio Meaning',path:'@qpresets[@active_qpreset].content.aud2mean',hover_tip:'Question: A vocab word, in spoken audio\nAnswer: The meaning in English'},
aud2read: {type:'checkbox',label:'Voc Audio Reading',path:'@qpresets[@active_qpreset].content.aud2read',hover_tip:'Question: A vocab word, in spoken audio\nAnswer: The Japanese reading, in hiragana or katakana'},
}},
}},
pg_items: {type:'page',label:'Items',hover_tip:'Choose what items you want to be quizzed on',content:{
grp_ipre_list: {type:'group',label:'Presets List',content:{
active_ipreset: {type:'list',refresh_on_change:true,hover_tip:'Item Presets',content:{}},
}},
grp_ipre: {type:'group',label:'Selected Preset',content:{
sect_ipre_name: {type:'section',label:'Preset Name'},
ipre_name: {type:'text',label:'Edit Preset Name',on_change:refresh_ipresets,path:'@ipresets[@active_ipreset].name',hover_tip:'Enter a name for the selected preset'},
sect_ipre_srcs: {type:'section',label:'Item Sources'},
ipre_srcs: {type:'tabset',content:{}},
}},
}},
pg_msgs: {type:'page',label:'Messages',hover_tip:'Choose what messages you want to display',content:{
grp_msgs: {type:'group',label:'Warning Messages',content:{
show_slightly_off: {type:'checkbox',label:'Answer is slightly off',path:'@messages.show_slightly_off',hover_tip:'Tells you when your answer is slightly off',default:true},
show_multi_reading: {type:'checkbox',label:'Has multiple readings',path:'@messages.show_multi_reading',hover_tip:'Tells you when an item has multiple readings',default:false},
}},
grp_halt: {type:'group',label:'Override Lightning',content:{
halt_slightly_off: {type:'checkbox',label:'Halt if slightly off',path:'@messages.halt_slightly_off',hover_tip:'Override lightning mode when your answer is slightly off',default:true},
halt_multi_reading: {type:'checkbox',label:'Halt if multiple readings',path:'@messages.halt_multi_reading',hover_tip:'Override lightning mode when an item has multiple readings',default:false},
}},
}},
},
};
populate_items_config(config);
quiz.settings_dialog = new wkof.Settings(config);
quiz_settings_state = 'ready';
open_quiz_settings();
}
//========================================================================
// preopen_quiz_settings()
//------------------------------------------------------------------------
function preopen_quiz_settings(dialog) {
var btn_grp =
'
'+
''+
''+
''+
''+
'
';
var wrap = dialog.find('#ss_quiz_active_qpreset').closest('.row');
wrap.addClass('pre_list_wrap');
wrap.prepend(btn_grp.replace(/###/g, 'qpreset'));
wrap.find('.pre_list_btn_grp').on('click', 'button', preset_button_pressed);
wrap = dialog.find('#ss_quiz_active_ipreset').closest('.row');
wrap.addClass('pre_list_wrap');
wrap.prepend(btn_grp.replace(/###/g, 'ipreset'));
wrap.find('.pre_list_btn_grp').on('click', 'button', preset_button_pressed);
$('#ss_quiz_ipre_srcs .row:first-child').each(function(i,e){
var row = $(e);
var right = row.find('>.right');
row.prepend(right);
row.addClass('src_enable');
});
// Customize the item source filters.
var srcs = $('#ss_quiz_ipre_srcs');
var flt_grps = srcs.find('.wkof_group');
flt_grps.addClass('filters');
var filters = flt_grps.find('.row');
filters.prepend('');
filters.on('change', '.enable input[type="checkbox"]', toggle_filter);
init_settings();
refresh_qpresets();
refresh_ipresets();
}
//========================================================================
// open_quiz_settings()
//------------------------------------------------------------------------
function open_quiz_settings() {
if (quiz_settings_state !== 'ready') return setup_quiz_settings();
quiz_settings_state = 'open';
quiz.settings_dialog.open();
}
//========================================================================
// save_quiz_settings()
//------------------------------------------------------------------------
function save_quiz_settings(settings) {
quiz.settings = settings;
populate_presets($('#ss_quiz_qna'), settings.qpresets, settings.active_qpreset);
populate_presets($('#ss_quiz_source'), settings.ipresets, settings.active_ipreset);
fetch_items().then(quiz.start);
}
//========================================================================
// close_quiz_settings()
//------------------------------------------------------------------------
function close_quiz_settings(settings) {
quiz_settings_state = 'setup';
}
//========================================================================
// open_quiz_settings()
//------------------------------------------------------------------------
function refresh_quiz_settings(settings) {
$('#ss_quiz_ipre_srcs .wkof_group .row').each(function(i,e){
var row = $(e);
var panel = row.closest('[role="tabpanel"]');
var source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
var filter_name = row.find('.setting').attr('name').slice((source+'_flt_').length);
var preset = quiz.settings.ipresets[quiz.settings.active_ipreset].content;
var enabled = false;
try {
enabled = preset[source].filters[filter_name].enabled;
} catch(e) {}
if (enabled)
row.addClass('checked');
else
row.removeClass('checked');
row.find('.enable input[type="checkbox"]').prop('checked', enabled);
});
}
//========================================================================
// refresh_qpresets()
//------------------------------------------------------------------------
function refresh_qpresets() {
var settings = quiz.settings;
populate_presets($('#ss_quiz_active_qpreset'), settings.qpresets, settings.active_qpreset);
}
//========================================================================
// refresh_ipresets()
//------------------------------------------------------------------------
function refresh_ipresets() {
var settings = quiz.settings;
populate_presets($('#ss_quiz_active_ipreset'), settings.ipresets, settings.active_ipreset);
}
//========================================================================
// preset_button_pressed()
//------------------------------------------------------------------------
function preset_button_pressed(e) {
var settings = quiz.settings;
var ref = e.currentTarget.attributes.ref.value;
var action = e.currentTarget.attributes.action.value;
var selected = Number(settings['active_'+ref]);
var presets = settings[ref+'s'];
var elem = $('#ss_quiz_active_'+ref);
var dflt;
if (ref === 'qpreset')
dflt = {name:'', content:$.extend(true, {}, qpre_defaults)};
else
dflt = {name:'', content:$.extend(true, {}, ipre_defaults)};
switch (action) {
case 'new':
presets.push(dflt);
selected = presets.length - 1;
settings[ref+'s'] = presets;
settings['active_'+ref] = selected;
populate_presets(elem, presets, selected);
quiz.settings_dialog.refresh();
$('#ss_quiz_'+ref.slice(0,4)+'_name').focus().select();
break;
case 'up':
if (selected <= 0) break;
presets = [].concat(presets.slice(0, selected-1), presets[selected], presets[selected-1], presets.slice(selected+1));
selected--;
settings[ref+'s'] = presets;
settings['active_'+ref] = selected;
populate_presets(elem, presets, selected);
break;
case 'down':
if (selected >= presets.length-1) break;
presets = [].concat(presets.slice(0, selected), presets[selected+1], presets[selected], presets.slice(selected+2));
selected++;
settings[ref+'s'] = presets;
settings['active_'+ref] = selected;
populate_presets(elem, presets, selected);
break;
case 'delete':
presets = presets.slice(0, selected).concat(presets.slice(selected+1));
selected = Math.max(0, selected-1);
settings[ref+'s'] = presets;
settings['active_'+ref] = selected;
populate_presets(elem, presets, selected);
quiz.settings_dialog.refresh();
break;
}
}
//========================================================================
// init_settings()
//------------------------------------------------------------------------
var qpre_defaults = {char2mean:false, char2read:false, read2mean:false, mean2read:false, aud2mean:false, aud2read:false};
function init_settings() {
var idx;
var settings = quiz.settings;
if (settings.qpresets === undefined) {
settings.qpresets = [
{name:'All Questions', content:{char2mean:true, char2read:true, read2mean:true, mean2read:true, aud2mean:true, aud2read:true}},
{name:'JP to EN', content:{char2mean:true, char2read:true, read2mean:true, mean2read:false, aud2mean:true, aud2read:true}},
{name:'EN to JP', content:{char2mean:false, char2read:false, read2mean:false, mean2read:true, aud2mean:false, aud2read:false}},
{name:'Audio Quiz', content:{char2mean:false, char2read:false, read2mean:false, mean2read:false, aud2mean:true, aud2read:true}},
];
settings.active_qpreset = 0;
}
for (idx in settings.qpresets) {
settings.qpresets[idx].content = $.extend(true, {}, qpre_defaults, settings.qpresets[idx].content);
}
if (settings.messages === undefined) {
settings.messages = {show_slightly_off:true, show_multi_reading:false, halt_slightly_off:true, halt_multi_reading:false}
}
if (settings.ipresets === undefined) {
settings.ipresets = [
{name:'All Wanikani Items', content:{wk_items:{enabled:true,filters:{}}}},
{name:'Apprentice Items', content:{wk_items:{enabled:true,filters:{srs:{enabled:true,value:{appr1:true,appr2:true,appr3:true,appr4:true}}}}}},
{name:'Burned Items', content:{wk_items:{enabled:true,filters:{srs:{enabled:true,value:{burn:true}}}}}},
{name:'Resurrected Items', content:{wk_items:{enabled:true,filters:{have_burned:{enabled:true,value:true},srs:{enabled:true,value:{appr1:true,appr2:true,appr3:true,appr4:true,guru1:true,guru2:true,mast:true,enli:true}}}}}},
];
settings.active_ipreset = 0;
}
if (ipre_defaults) {
for (idx in settings.ipresets) {
settings.ipresets[idx].content = $.extend(true, {}, ipre_defaults, settings.ipresets[idx].content);
}
}
}
//========================================================================
// populate_items_config()
//------------------------------------------------------------------------
var ipre_defaults;
function populate_items_config(config) {
var ipre_srcs = config.settings.pg_items.content.grp_ipre.content.ipre_srcs.content;
var srcs = wkof.ItemData.registry.sources;
ipre_defaults = {};
for (var src_name in srcs) {
var src = srcs[src_name];
var pg_content = {};
ipre_srcs['pg_'+src_name] = {type:'page',label:src.description,content:pg_content};
settings = {};
ipre_defaults[src_name] = settings;
pg_content[src_name+'_enable'] = {
type:'checkbox',
label:'Include this source',
path:'@ipresets[@active_ipreset].content["'+src_name+'"].enabled',
hover_tip:'Check to include this data source in the quiz'
};
// Enable Wanikani source by default.
settings.enabled = (src_name === 'wk_items');
// Add 'Options' section. 'wk_items' is handled automatically.
if (src_name !== 'wk_items') {
if (src.options && Object.keys(src.options).length > 0) {
settings.options = {};
var opt_content = {};
pg_content['grp_'+src_name+'_options'] = {type:'group',label:'Options',content:opt_content};
for (var opt_name in src.options) {
var opt = src.options[opt_name];
switch (opt.type) {
case 'checkbox':
opt_content[src_name+'_opt_'+opt_name] = {
type:'checkbox',
label:opt.label,
default:opt.default,
hover_tip:opt.hover_tip
}
break;
}
}
}
}
// Add 'Filters' section.
if (src.filters && Object.keys(src.filters).length > 0) {
settings.filters = {};
var flt_content = {};
pg_content['grp_'+src_name+'_filters'] = {type:'group',label:'Filters',content:flt_content};
for (var flt_name in src.filters) {
var flt = src.filters[flt_name];
settings.filters[flt_name] = {enabled:false, value:flt.default};
switch (flt.type) {
case 'checkbox':
flt_content[src_name+'_flt_'+flt_name] = {
type:'checkbox',
label:flt.label,
default:flt.default,
path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
hover_tip:flt.hover_tip
}
break;
case 'multi':
var dflt = flt.default;
if (typeof flt.filter_value_map === 'function') dflt = flt.filter_value_map(dflt);
flt_content[src_name+'_flt_'+flt_name] = {
type:'list',
multi:true,
size:Math.min(4,Object.keys(flt.content).length),
label:flt.label,
content:flt.content,
default:dflt,
path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
hover_tip:flt.hover_tip
}
settings.filters[flt_name].value = dflt;
break;
case 'text':
case 'number':
case 'input':
flt_content[src_name+'_flt_'+flt_name] = {
type:flt.type,
label:flt.label,
placeholder:flt.placeholder,
default:flt.default,
path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
hover_tip:flt.hover_tip
}
break;
case 'button':
flt_content[src_name+'_flt_'+flt_name] = {
type:flt.type,
label:flt.label,
on_click:flt.on_click,
hover_tip:flt.hover_tip
}
break;
}
}
}
}
}
//========================================================================
// toggle_filter()
//------------------------------------------------------------------------
function toggle_filter(e) {
var row = $(e.delegateTarget);
var panel = row.closest('[role="tabpanel"]');
var source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
var enabled = row.find('.enable input[type="checkbox"]').prop('checked');
var preset = quiz.settings.ipresets[quiz.settings.active_ipreset].content;
var filter_name = row.find('.setting').attr('name').slice((source+'_flt_').length);
if (enabled) {
row.addClass('checked');
} else {
row.removeClass('checked');
}
try {
preset[source].filters[filter_name].enabled = enabled;
} catch(e) {}
}
//########################################################################
// QUIZ DIALOG
//########################################################################
//========================================================================
// install_css()
//------------------------------------------------------------------------
function install_css() {
$('head').append(
''
);
}
//========================================================================
// open_quiz()
//------------------------------------------------------------------------
var quiz_setup_state = 'init';
function open_quiz() {
if (quiz_setup_state === 'init') {
quiz_setup_state = 'loading';
install_css();
wkof.include('Settings');
wkof.ready('Settings').then(function(){
return wkof.Settings.load('ss_quiz');
}).then(function(){
quiz.settings = wkof.settings.ss_quiz;
quiz_setup_state = 'ready';
init_settings();
open_quiz();
});
}
if (quiz_setup_state !== 'ready') return;
var quiz_html =
'