您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Self-study your items via the Wanikani level pages
当前为
// ==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://gf.qytechs.cn/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);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址