Skip to content

Instantly share code, notes, and snippets.

@mcaubrey
Last active Sep 15, 2021
Embed
What would you like to do?
// ==UserScript==
// @name WaniKani Context - Michael's Edit
// @description Shows WaniKani's example sentences as context for kanji.
// @namespace wkcontext
// @include https://www.wanikani.com/review/session*
// @run-at document-end
// @version 0.1.91
// @author Toms Jensen
// @license MIT; http://opensource.org/licenses/MIT
// ==/UserScript==
var character;
var context;
var questionType;
var verbConjugations = [
// Plain
'な',
'ない',
'た',
'なかった',
// Polite
'ます',
'ません',
'ました',
'ませんでした',
'ましょう',
// Progressive
'て', 'で', 'って',
'てる', 'でる', 'ってる',
'ている', 'でいる', 'っている',
];
// Sort verbs by descending length.
verbConjugations = verbConjugations.sort(function(a, b) { return b.length - a.length });
window.addEventListener("load", onLoad);
// Runs on load.
function onLoad() {
// Find the character node.
var characterDiv = document.getElementById('character');
// Find the node that contains our vocab.
character = characterDiv.firstElementChild;
// Copy the span for our own nefarious purposes.
context = character.cloneNode();
context.classList.add('context');
context.show = function(fade) {
character.style.display = 'none';
context.style.display = '';
context.style.opacity = fade;
};
context.hide = function() {
character.style.display = '';
context.style.display = 'none';
context.style.opacity = 0;
};
// Add the copied element to the character div.
characterDiv.appendChild(context);
// Create styles.
// height: <#character.vocab>.line-height / font-size (i.e. 3.21em / 0.4em = 8.025em)
createStyle('.context { display: table; position: relative; width: 100%; height: 8.025em; font-size: 0.4em; line-height: 1; transition: opacity 0.5s }');
createStyle('.context span { font-size: 42px; display: table-cell; vertical-align: middle; padding: 0% 2% }');
createStyle('.context mark { display: inline-block; font-size: 150%; color: unset; background-color: rgba(0, 255, 255, 0.5); }');
// Hide the context.
context.hide();
// Create character observer.
var observer = new MutationObserver(onCharacterChanged);
observer.observe(character, { childList: true });
if (character.innerText != '')
onCharacterChanged();
}
// Runs when a mutation is observed in the character element.
function onCharacterChanged() {
context.innerText = ''; // Remove the text that was added by WK's system.
context.style.fontFamily = character.style.fontFamily; // Match font set by font randomizers.
// Thanks obskyr! (https://community.wanikani.com/t/wanikani-jstorage-documentation/18885)
var currentItem = $.jStorage.get("currentItem");
var currentVocab = currentItem.voc;
// Remove style and break if current item isn't vocab (i.e. radical or kanji).
if (currentVocab)
context.show(0);
else {
context.hide();
return;
}
// Remove squiggly thing if it's there.
if (currentVocab.charAt(0) == '〜')
currentVocab = currentVocab.substr(1);
$.getJSON("/json/vocabulary/" + currentItem.id, function(json) {
var currentSentence = getRandomElement(json.sentences)[0];
var vocabIndex = currentSentence.indexOf(currentVocab);
var vocabLength = currentVocab.length;
// If the raw vocab wasn't found we need to attempt to find the conjugated version.
if (vocabIndex < 0) {
var verbMatch = currentVocab.match(/([぀-ゟ一-龿]+?)([うくぐすずつづぬふぶぷむゆる]|する)/); // i.e. 食べる
if (verbMatch == null)
{
context.hide();
console.log(currentVocab);
console.log(currentSentence);
return;
}
var verbRoot = verbMatch[1]; // i.e. 食
var verbStem = verbMatch[2]; // i.e. べる
// Find the index of the verb root.
vocabIndex = currentSentence.indexOf(verbRoot);
// Find the index of the verb conjugation.
for (var i = 0; i < verbConjugations.length; ++i) {
var conjugation = verbConjugations[i];
var conjugationIndex = currentSentence.indexOf(conjugation, vocabIndex);
if (conjugationIndex < 0) {
continue;
}
var conjugationDistance = conjugationIndex - vocabIndex;
// Found a valid conjugation!
if (conjugationDistance <= vocabLength) {
vocabLength = conjugationDistance + conjugation.length;
break;
}
}
}
unsafeWindow.speak = function() {
const sentence = currentSentence.substr(0, vocabIndex) +
'' + currentSentence.substr(vocabIndex, vocabLength) + '' +
currentSentence.substr(vocabIndex + vocabLength);
var utterThis = new SpeechSynthesisUtterance(sentence);
utterThis.pitch = 1;
utterThis.rate = 0.8;
utterThis.lang = "ja-JP";
speechSynthesis.speak(utterThis);
}
// Build the context html.
context.show(1);
context.innerHTML =
'<span onclick="window.speak(); return false" style="font-size:32px; cursor: pointer"><strong style="font-size: 62px;">' + currentVocab + '</strong> <br />' +
currentSentence.substr(0, vocabIndex) +
'<mark>' + currentSentence.substr(vocabIndex, vocabLength) + '</mark>' +
currentSentence.substr(vocabIndex + vocabLength) +
'</span>';
});
}
// Utility functions.
function createStyle(css) {
var head = document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style)
};
var random = Math.random; // Shouldn't have to do this, but other scripts can canabalize the random function.
function getRandomElement(array) {
var index = Math.floor(random() * array.length);
return array[index];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment