// drill.js var transformations = []; var log; const configOptions = { options: ["plain", "polite", "negative", "past", "te-form", "progressive", "potential", "imperative", "passive", "causative", "godan", "ichidan", "iku", "kuru", "suru", "i-adjective", "na-adjective", "ii", "desire", "volitional", "trick", "kana", "furigana_always", "use_voice", "go_to_next_question", "auto_show_explanation", ], selects: ["questionFocus"], inputs: ["numQuestions"] } const defaultConfig = { "plain": true, "polite": true, "negative": true, "past": true, "te-form": false, "progressive": false, "potential": false, "imperative": false, "passive": false, "causative": false, "godan": true, "ichidan": true, "iku": true, "kuru": true, "suru": true, "i-adjective": false, "na-adjective": false, "ii": false, "desire": false, "volitional": false, "trick": true, "kana": false, "furigana_always": true, "go_to_next_question": false, "auto_show_explanation": false, "use_voice": false, "questionFocus": "none", "numQuestions": "10" } const localStorageOptionsKey = "conjugationDrillOptions"; Array.prototype.randomElement = function () { return this[Math.floor(Math.random() * this.length)] } // From: http://stackoverflow.com/a/2897510 new function ($) { $.fn.getCursorPosition = function () { var input = this.get(0); if (!input) return; // No (input) element found if ('selectionStart' in input) { // Standard-compliant browsers return input.selectionStart; } else if (document.selection) { // IE input.fmcus(); var sel = document.selection.createRange(); var selLen = document.selection.createRange().text.length; sel.moveStart('character', -input.value.length); return sel.text.length - selLen; } } }(jQuery); // From: http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area new function ($) { $.fn.setCursorPosition = function (pos) { if (this.setSelectionRange) { this.setSelectionRange(pos, pos); } else if (this.createTextRange) { var range = this.createTextRange(); range.collapse(true); if (pos < 0) { pos = $(this).val().length + pos; } range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } } }(jQuery); // Waaaayy overkill here but these ranges were taken from http://www.unicode.org/charts/ and I kinda got carried away. var japaneseTextPattern = /^[\u{3040}-\u{309f}\u{30a0}-\u{30ff}\u{3190}-\u{319f}\u{31f0}-\u{31ff}\u{3400}-\u{4dbf}\u{4e00}-\u{9ffc}\u{f900}-\u{faff}\u{ff00}-\u{ffef}\u{1b000}-\u{1b0ff}\u{1b100}-\u{1b12f}\u{1b130}-\u{1b16f}\u{20000}-\u{2a6dd}\u{2a700}-\u{2b734}\u{2b740}-\u{2b81d}\u{2b820}-\u{2cea1}\u{2ceb0}-\u{2ebe0}\u{2f800}-\u{2fa1f}\u{30000}-\u{3134a}]*$/u; function commaList(items, conjunction) { if (conjunction == undefined) { conjunction = "and"; } var result = ""; for (var i = 0; i < items.length; i++) { result = result + items[i]; if (i < (items.length - 2)) { result += ", "; } if (i == (items.length - 2)) { result += " " + conjunction + " "; } } return result; } function resetLog() { log = { "history": [] }; } function kanaForm(words) { if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { return word.split(/.\[([^\]]*)\]/).join(""); }); } function kanjiForm(words) { if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { return word.split(/(.)\[[^\]]*\]/).join(""); }); } function getVerbForms(entry) { var result = { "kanji": {}, "hiragana": {}, "furigana": {} }; Object.keys(words[entry].conjugations).forEach(function (key) { result["kanji"][key] = kanjiForm(words[entry].conjugations[key].forms); result["hiragana"][key] = kanaForm(words[entry].conjugations[key].forms); result["furigana"][key] = words[entry].conjugations[key].forms; }); return result; } function wordWithFurigana(words) { var options = getOptions(); if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { var bits = word.split(/(.)\[([^\]]*)\]/); while (bits.length > 1) { if (options["kana"]) { bits[0] = bits[0] + bits[2] + bits[3]; } else if (options["furigana_always"]) { bits[0] = bits[0] + "" + bits[1] + "(" + bits[2] + ")" + bits[3]; } else { bits[0] = bits[0] + "" + bits[1] + "(" + bits[2] + ")" + bits[3]; } bits.splice(1, 3); } return bits[0]; }); } function processAnswerKey() { var el = $('#answer'); var pos = el.getCursorPosition(); var val = el.val(); var last1 = val.slice(pos - 1, pos); var last2 = val.slice(pos - 2, pos); var last3 = val.slice(pos - 3, pos); var replace1 = { "a": "あ", "i": "い", "u": "う", "e": "え", "o": "お" }; var replace2 = { "ka": "か", "ki": "き", "ku": "く", "ke": "け", "ko": "こ", "sa": "さ", "si": "し", "su": "す", "se": "せ", "so": "そ", "ta": "た", "ti": "ち", "tu": "つ", "te": "て", "to": "と", "na": "な", "ni": "に", "nu": "ぬ", "ne": "ね", "no": "の", "ha": "は", "hi": "ひ", "hu": "ふ", "he": "へ", "ho": "ほ", "ma": "ま", "mi": "み", "mu": "む", "me": "め", "mo": "も", "ra": "ら", "ri": "り", "ru": "る", "re": "れ", "ro": "ろ", "ga": "が", "gi": "ぎ", "gu": "ぐ", "ge": "げ", "go": "ご", "za": "ざ", "zi": "じ", "zu": "ず", "ze": "ぜ", "zo": "ぞ", "da": "だ", "di": "ぢ", "du": "づ", "de": "で", "do": "ど", "ba": "ば", "bi": "び", "bu": "ぶ", "be": "べ", "bo": "ぼ", "pa": "ぱ", "pi": "ぴ", "pu": "ぷ", "pe": "ぺ", "po": "ぽ", "qa": "くぁ", "qi": "くぃ", "qu": "く", "qe": "くぇ", "qo": "くぉ", "wa": "わ", "wi": "うぃ", "wu": "う", "we": "うぇ", "wo": "を", "ya": "や", "yi": "い", "yu": "ゆ", "ye": "いぇ", "yo": "よ", "fa": "ふぁ", "fi": "ふぃ", "fu": "ふ", "fe": "ふぇ", "fo": "ふぉ", "ja": "じゃ", "ji": "じ", "ju": "じゅ", "je": "じぇ", "jo": "じょ", "la": "ぁ", "li": "ぃ", "lu": "ぅ", "le": "ぇ", "lo": "ぉ", "za": "ざ", "zi": "じ", "zu": "ず", "ze": "ぜ", "zo": "ぞ", "xa": "ぁ", "xi": "ぃ", "xu": "ぅ", "xe": "ぇ", "xo": "ぉ", "ca": "か", "ci": "し", "cu": "く", "ce": "せ", "co": "こ", "va": "ヴぁ", "vi": "ヴぃ", "vu": "ヴ", "ve": "ヴぇ", "vo": "ヴぉ", "lu": "っ", "nn": "ん", "n'": "ん", "nb": "んb", "nc": "んc", "nd": "んd", "nf": "んf", "ng": "んg", "nh": "んh", "nj": "んj", "nk": "んk", "nl": "んl", "nm": "んm", "np": "んp", "nq": "んq", "nr": "んr", "ns": "んs", "nt": "んt", "nv": "んv", "nw": "んw", "nx": "んx", "nz": "んz", "aa": "っa", "bb": "っb", "cc": "っc", "dd": "っd", "ee": "っe", "ff": "っf", "gg": "っg", "hh": "っh", "ii": "っi", "jj": "っj", "kk": "っk", "ll": "っl", "mm": "っm", "oo": "っo", "pp": "っp", "qq": "っq", "rr": "っr", "ss": "っs", "tt": "っt", "uu": "っu", "vv": "っv", "ww": "っw", "xx": "っx", "yy": "っy", "zz": "っz", }; var replace3 = { "kya": "きゃ", "kyi": "きぃ", "kyu": "きゅ", "kye": "きぇ", "kyo": "きょ", "sha": "しゃ", "shi": "し", "shu": "しゅ", "she": "しぇ", "sho": "しょ", "cha": "ちゃ", "chi": "ち", "chu": "ちゅ", "che": "ちぇ", "cho": "ちょ", "nya": "にゃ", "nyi": "にぃ", "nyu": "にゅ", "nye": "にぇ", "nyo": "にょ", "hya": "ひゃ", "hyi": "ひぃ", "hyu": "ひゅ", "hye": "ひぇ", "hyo": "ひょ", "mya": "みゃ", "myi": "みぃ", "myu": "みゅ", "mye": "みぇ", "myo": "みょ", "rya": "りゃ", "ryi": "りぃ", "ryu": "りゅ", "rye": "りぇ", "ryo": "りょ", "gya": "ぎゃ", "gyi": "ぎぃ", "gyu": "ぎゅ", "gye": "ぎぇ", "gyo": "ぎょ", "zya": "じゃ", "zyi": "じぃ", "zyu": "じゅ", "zye": "じぇ", "zyo": "じょ", "dya": "ぢゃ", "dyi": "ぢぃ", "dyu": "ぢゅ", "dye": "ぢぇ", "dyo": "ぢょ", "bya": "びゃ", "byi": "びぃ", "byu": "びゅ", "bye": "びぇ", "byo": "びょ", "pya": "ぴゃ", "pyi": "ぴぃ", "pyu": "ぴゅ", "pye": "ぴぇ", "pyo": "ぴょ", "shi": "し", "tsu": "つ", }; if (replace3[last3]) { val = val.slice(0, pos - 3) + replace3[last3] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 3 + replace3[last3].length); } else if (replace2[last2]) { val = val.slice(0, pos - 2) + replace2[last2] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 2 + replace2[last2].length); } else if (replace1[last1]) { val = val.slice(0, pos - 1) + replace1[last1] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 1 + replace1[last1].length); } } function processAnswerKeyDown(evt) { if (evt.keyCode == 32) { var options = getOptions(); if (options.use_voice) { window.speechSynthesis.cancel(); textToSpeech(window.questionData.givenWordAsKanji, evt.shiftKey); evt.preventDefault(); } } } function validQuestion(entry, forms, transformation, options) { var valid = true; transformation.tags.forEach(function (type) { if (options[type] == false) { valid = false; } }); if (options[words[entry].group] == false) { valid = false; } if (!forms["furigana"][transformation.from]) valid = false; if (!forms["furigana"][transformation.to]) valid = false; if (valid) { if (options.questionFocus != "none") { if (options.questionFocus == 'tetakei') { // console.log("tetakei", words[entry].conjugations[transformation.from].tetakei, words[entry].conjugations[transformation.to].tetakei) if (words[entry].conjugations[transformation.from].tetakei == words[entry].conjugations[transformation.to].tetakei) { valid = false; } } else if (transformation.type != options.questionFocus) { valid = false; } } } return valid; } function generateQuestion() { var questionText = { "affirmative": "make the following affirmative", "negative": "make the following negative", "present": "convert the following to the present tense", "past": "convert the following to the past tense", "plain": "make the following informal", "polite": "make the following polite", "て": "add the て pattern to the following", "non-て": "remove the て pattern from the following", "potential": "make the following potential", "non-potential": "make the following non-potential", "imperative": "make the following imperative", "non-imperative": "make the following non-imperative", "causative": "make the following causative", "non-causative": "make the following non-causative", "passive": "make the following passive", "active": "make the following active", "progressive": "make the following progressive", "non-progressive": "make the following non-progressive", "'desire'": "convert the following to the 'desire' form", "'non-desire'": "convert the following to the 'non-desire' form", "volitional": "make the following volitional", "non-volitional": "make the following non-volitional" }; var entry; var to_form; var from_form; var forms; var options = getOptions(); var count = 0; while (true) { if (count++ == 10000) { showSplash(); return; } entry = Object.keys(words).randomElement(); transformation = transformations.randomElement(); from_form = transformation.from; to_form = transformation.to; forms = getVerbForms(entry); var valid = validQuestion(entry, forms, transformation, getOptions()); // Modify the chance of trick questions so that they appear on average 25% // of the time. When trick questions are active then 50% of the // transformation structure are trick questions and so a 33% filter here // will achieve the 25% because this test is only performed when a trick // question has been selected. if (transformation.tags.indexOf('trick') != -1) { if (Math.random() > 0.333) { valid = false; } } if (valid) { break; } } var kanjiForms = forms["kanji"]; var kanaForms = forms["hiragana"]; var furiganaForms = forms["furigana"]; var candidates; if (options["kana"]) { candidates = kanaForms[from_form]; } else { candidates = wordWithFurigana(furiganaForms[from_form]); } var candidateIndex = Math.floor(Math.random() * candidates.length); var givenWord = candidates[candidateIndex]; var givenWordAsKanji = kanjiForms[from_form][candidateIndex]; var thisQuestionText = questionText[transformation.phrase]; // thisQuestionText = thisQuestionText[0].toUpperCase() + thisQuestionText.substring(1); var questionFirstHalf = thisQuestionText; var questionSecondHalf = givenWord; var question = questionFirstHalf.replace("the following", questionSecondHalf); var answer = kanjiForms[to_form]; var answer2 = kanaForms[to_form]; var answerWithFurigana = wordWithFurigana(furiganaForms[to_form]); if (options["kana"]) { answer = answer2; answerWithFurigana = kanaForms[to_form]; } $('#questionFirstHalf').html(questionFirstHalf); if (options.use_voice) { $('#questionSecondHalf').html("
Press Space for word
Use Shift key for half speed
"); } else { $('#questionSecondHalf').html(questionSecondHalf); } window.questionData = { entry: entry, transformation: transformation, question: question, answer: answer, answer2: answer2, answerWithFurigana: answerWithFurigana, givenWord: givenWord, givenWordAsKanji: givenWordAsKanji, }; // Construct the explanation page. var data = window.questionData; var groupLabels = { "godan": "godan verb", "ichidan": "ichidan verb", "iku": "godan verb", "suru": "suru verb", "kuru": "special verb", "i-adjective": "い-adjective", "ii": "い-adjective", "na-adjective": "な-adjective", }; var dictionary = words[data.entry].conjugations["dictionary"].forms; if (words[data.entry].group == "na-adjective") { for (var i = 0; i < dictionary.length; i++) { dictionary[i] = dictionary[i].replace(/だ$/, '') } } if (!options["kana"]) { dictionary = wordWithFurigana(dictionary); } else { dictionary = kanaForm(dictionary); } $('#explain-given').html(givenWord); $('#explain-given-tags').html(data.transformation.from_tags.map(function (tag) { return "" + tag + ""; }).join(" ")); $('.explain-given-dictionary').html(dictionary); $('#explain-group').html(groupLabels[words[data.entry].group]); $('.explain-transform').html(data.transformation.phrase); $('.explain-answer-tags').html(data.transformation.to_tags.map(function (tag) { return "" + tag + ""; }).join(" ")); $('.explain-answer-tags2').html(data.transformation.to_tags.join(" ")); $('.explain-answer').html(commaList(questionData.answerWithFurigana, "or")); $('.explain-answer-as-list').empty(); questionData.answerWithFurigana.forEach(function (answer) { $('.explain-answer-as-list').append("
  • " + answer); }); if (window.questionData.transformation.tags.indexOf("trick") != -1) { $('.explain-trick').show(); $('.explain-no-trick').hide(); } else { $('.explain-trick').hide(); $('.explain-no-trick').show(); } if (data.transformation.to == "dictionary") { $('.explain-hide-end').hide(); } else { $('.explain-hide-end').show(); } if (data.answer.length == 1) { $('.explain-answer-single').show(); $('.explain-answer-multiple').hide(); } else { $('.explain-answer-single').hide(); $('.explain-answer-multiple').show(); } $('#response').html(""); $('#message').hide(); $('#proceed').hide(); $('#explanation').hide(); $('#inputArea').show(); $('#answer').focus(); $('#answer').on('input', processAnswerKey); $('#answer').on('keydown', processAnswerKeyDown); } function processAnswer() { var options = getOptions(); var questionData = window.questionData; var response = $('#answer').val().trim(); var shake = false; if (response == "") shake = true; if (!response.match(japaneseTextPattern)) shake = true; if (shake) { shakeInputArea(); return; } var correct = ((questionData.answer.indexOf(response) != -1) || (questionData.answer2.indexOf(response) != -1)); var klass = correct ? "correct" : "incorrect"; log.history.push({ "question": questionData.question, "response": response, "answer": questionData.answerWithFurigana, "kana": questionData.answer2, "correct": correct }); var totalQuestions = $('#numQuestions').val(); var answeredQuestions = log.history.length; updateProgressBar(answeredQuestions / totalQuestions * 100); $('#answer').val(""); $('#responseButton').prop('class', klass).text(response); if (correct) { $('#message').hide(); } else { $('#message').show(); $('#message #correction').html("The correct answer was " + commaList(questionData.answerWithFurigana, "or")); } $('#inputArea').hide(); $('#proceed').show(); $('#explanation').hide(); $('#proceed button').focus(); updateHistoryView(log); window.scrollTo(0, 0); if (correct) { if (options.go_to_next_question) { proceed(); } } else { if (options.auto_show_explanation) { $('#explanation').show(); } } } function shakeInputArea() { var inputArea = $('#inputArea'); var shakeClass = "shake"; inputArea.addClass(shakeClass); setTimeout(function () { inputArea.removeClass(shakeClass) }, 1000); } function updateHistoryView(log) { var review = $('
    '); var total = 0; var correct = 0; log.history.forEach(function (entry, index) { total++; if (entry.correct) { correct++; } var tr = $('
    '); var td1 = $('
    '); var td2 = $('
    '); td1.html((index + 1) + ". " + entry.question + "."); var responseDiv = $('
    '); responseDiv.text(entry.response); if (entry.correct) { responseDiv.append(""); } else { responseDiv.append(" ×"); } td2.append(responseDiv); if (!entry.correct) { var correctDiv = $('
    '); correctDiv.html(commaList(entry.answer, "or")); correctDiv.append(""); td2.append(correctDiv); } tr.append(td1); tr.append(td2); review.append(tr); }); $('#history').empty().append(review); var resultString; if (correct == total) { resultString = "All correct!"; } else if (correct == 0) { resultString = "All incorrect!"; } else { resultString = correct + " of " + total + " correct"; } $('#scoreSectionTitleNarrow').text(resultString); $('#scoreSectionTitleWide').text(resultString); } function updateProgressBar(progress) { $('.progressBar').attr('style', 'width: ' + progress + '%'); } function proceed() { if (log.history.length == $('#numQuestions').val()) { endQuiz(); } else { generateQuestion(); } } function showSplash() { $('#splash').show(); $('#quizSection').hide(); $('#scoreSection').hide(); $('#go').focus(); } function startQuiz() { var options = getOptions(); const voiceSelectError = document.querySelector('#voiceSelectError'); if (options.use_voice && !getVoiceConfig()) { voiceSelectError.style.display = "block"; return; } else { voiceSelectError.style.display = "none"; } updateProgressBar(0); $('#splash').hide(); $('#quizSection').show(); $('#scoreSection').hide(); if (options.furigana_always) { $('body').addClass("furiganaAlways"); } else { $('body').removeClass("furiganaAlways"); } resetLog(); generateQuestion(); } function endQuiz() { $('#splash').hide(); $('#quizSection').hide(); $('#scoreSection').show(); $('#backToStart').focus(); } // Text to Speech function loadVoiceList(callback) { if (window.speechSynthesis.getVoices().length == 0) { window.speechSynthesis.addEventListener('voiceschanged', function () { if (callback) { callback(); } }); } else { if (callback) { callback(); } } } function populateVoiceList() { loadVoiceList(function () { var voiceSelect = document.querySelector("#voice_select"); voiceSelect.innerHTML = "" + window.speechSynthesis.getVoices().map(function (voice) { return "" }).join(""); var currentVoice = getCurrentVoice(); if (currentVoice) { voiceSelect.value = currentVoice; } }); } function getVoiceConfig() { return JSON.parse(localStorage.getItem("voiceConfig")); } function setVoiceConfig(config) { localStorage.setItem("voiceConfig", JSON.stringify(config)); } function getCurrentVoice() { const voiceConfig = getVoiceConfig(); if (voiceConfig) { return voiceConfig.voice; } } function textToSpeech(text, slowMode, callback) { loadVoiceList(function () { const availableVoices = window.speechSynthesis.getVoices(); const voiceConfig = getVoiceConfig(); const currentVoice = voiceConfig.voice; var voice = ''; for (var i = 0; i < availableVoices.length; i++) { if (availableVoices[i].name == currentVoice) { voice = availableVoices[i]; break; } } if (voice === '') { voice = availableVoices[0]; } // new SpeechSynthesisUtterance object var utter = new SpeechSynthesisUtterance(); utter.rate = slowMode ? voiceConfig.rate * 0.5 : voiceConfig.rate;; utter.pitch = voiceConfig.pitch; utter.text = text; utter.voice = voice; // event after text has been spoken utter.onend = function () { if (callback) { callback(undefined); } } // speak window.speechSynthesis.speak(utter); }); } function arrayDifference(a, b) { // From http://stackoverflow.com/a/1723220 return a.filter(function (x) { return b.indexOf(x) < 0 }); } function arrayUnique(arr) { return arr.filter(function (value, index, self) { return self.indexOf(value) === index; }); } function calculateTransitions() { function getTags(str) { var tags = str.split(" "); if ((tags.length == 1) && (tags[0] == "plain")) { tags = []; } return tags; } function calculateTags(tags) { tags = tags.split(" "); if (tags.indexOf("polite") == -1) { tags.splice(0, 0, "plain"); } if (tags.indexOf("dictionary") != -1) { tags.splice(tags.indexOf("dictionary"), 1); } return tags; } var allTags = {}; Object.keys(words).forEach(function (word) { Object.keys(words[word].conjugations).forEach(function (conjugation) { if (conjugation == "dictionary") { conjugation = ""; } allTags[conjugation] = conjugation.split(" "); }); }); Object.keys(allTags).forEach(function (srcTag) { if (srcTag != "") { for (var i = 0; i < allTags[srcTag].length; i++) { var tagWithDrop = allTags[srcTag].slice(); tagWithDrop.splice(i, 1); var dstTag = tagWithDrop.join(" "); if (allTags[dstTag]) { if (srcTag == "") { srcTag = "dictionary"; } if (dstTag == "") { dstTag = "dictionary"; } transformations.push({ from: srcTag, to: dstTag }); transformations.push({ from: dstTag, to: srcTag }); } } } }); transformations.forEach(function (transformation) { var from = getTags(transformation.from); var to = getTags(transformation.to); var from_extra = { "negative": "affirmative", "past": "present", "polite": "plain", "te-form": "non-て", "potential": "non-potential", "imperative": "non-imperative", "causative": "non-causative", "passive": "active", "progressive": "non-progressive", "desire": "'non-desire'", "volitional": "non-volitional", }; var to_extra = { "negative": "negative", "past": "past", "polite": "polite", "te-form": "て", "potential": "potential", "imperative": "imperative", "causative": "causative", "passive": "passive", "progressive": "progressive", "desire": "'desire'", "volitional": "volitional", }; var phrase; phrase = phrase || from_extra[arrayDifference(from, to)[0]]; phrase = phrase || to_extra[arrayDifference(to, from)[0]]; transformation.phrase = phrase; transformation.from_tags = calculateTags(transformation.from); transformation.to_tags = calculateTags(transformation.to); transformation.tags = arrayUnique(calculateTags(transformation.from).concat(calculateTags(transformation.to))); var diffFromTo = arrayDifference(transformation.from_tags, transformation.to_tags); if (diffFromTo.length > 0) { type = diffFromTo[0]; } else { type = arrayDifference(transformation.to_tags, transformation.from_tags)[0]; } if ((type == "plain") || (type == "polite")) { type = "politeness"; } transformation.type = type; }); // Add trick forms var trick_forms = []; transformations.forEach(function (transformation) { trick_forms.push({ from: transformation.to, to: transformation.to, type: transformation.type, phrase: transformation.phrase, from_tags: transformation.to_tags, to_tags: transformation.to_tags, tags: transformation.tags.concat(["trick"]) }); }); transformations = transformations.concat(trick_forms); } function updateOptionSummary() { // Calculate how many questions will apply var options = getOptions(); var applicable = 0; Object.keys(words).forEach(function (word) { var forms = getVerbForms(word); transformations.forEach(function (transformation) { if (validQuestion(word, forms, transformation, options)) { applicable++; } }); }); $("#questionCount").text(applicable); if (!options.plain && !options.polite) { document.querySelector('#politePlainError').style.display = 'block'; } else { document.querySelector('#politePlainError').style.display = 'none'; } } function saveOptions() { localStorage.setItem(localStorageOptionsKey, JSON.stringify(getOptions())); } function restoreDefaults() { localStorage.setItem(localStorageOptionsKey, JSON.stringify(defaultConfig)); loadOptions(); updateOptionSummary(); } function updateVoiceSelect() { const options = getOptions(); const voice_select_options = document.querySelector("#voice_select_options"); if (options.use_voice) { voice_select_options.style.display = "block"; } else { voice_select_options.style.display = "none"; } } function updateVoiceSelection() { const newSelection = document.querySelector("#voice_select").selectedOptions[0].text; const voiceConfig = { voice: document.querySelector("#voice_select").selectedOptions[0].text, rate: 1, pitch: 1 }; setVoiceConfig(voiceConfig); } function explain() { $('#explanation').show(); $('#message').hide(); $('#explain-proceed-button').focus(); } function getOptions() { var result = {}; configOptions.options.forEach(function (option) { result[option] = $('#' + option).is(':checked') != false; }); configOptions.selects.forEach(function (select) { result[select] = $('#' + select).val(); }); configOptions.inputs.forEach(function (input) { result[input] = $('#' + input).val(); }); return result; } function loadOptions() { var storedOptionsText = localStorage.getItem(localStorageOptionsKey); if (storedOptionsText) { var storedOptions = JSON.parse(storedOptionsText); configOptions.options.forEach(function (option) { if (storedOptions[option] != undefined) { $(`#${option}`).prop('checked', storedOptions[option]); } }); configOptions.selects.forEach(function (select) { if (storedOptions[select] != undefined) { $(`#${select} [value=${storedOptions[select]}]`).attr('selected', false) $(`#${select} [value=${storedOptions[select]}]`).attr('selected', 'selected') } }); configOptions.inputs.forEach(function (input) { if (storedOptions[input] != undefined) { $(`#${input}`).val(storedOptions[input]); } }); } } $('window').ready(function () { if (window.speechSynthesis) { populateVoiceList(); $('#useVoiceSection').show(); $('input#use_voice').click(updateVoiceSelect); $('select#voice_select').on('change', updateVoiceSelection); } calculateAllConjugations(); calculateTransitions(); loadOptions(); $('#go').click(startQuiz); $('#defaults').click(restoreDefaults); $('#backToStart').click(showSplash); $('div.options input').click(updateOptionSummary); $('select#questionFocus').on('change', updateOptionSummary); $('input#trick').click(updateOptionSummary); $('input#focus_mode').click(updateOptionSummary); $('select').change(saveOptions); $('input').change(saveOptions); updateOptionSummary(); showSplash(); });