Skip to content

Instantly share code, notes, and snippets.

@daxav
Created February 27, 2021 16:33
Show Gist options
  • Save daxav/ffa5f857300a145d76a9e38cf620d947 to your computer and use it in GitHub Desktop.
Save daxav/ffa5f857300a145d76a9e38cf620d947 to your computer and use it in GitHub Desktop.
My Anki Wanikani Ultimate 2 Changes
Changes for the scripts of the Anki Wanikani Ultimate 2 deck, feel free to use them how you like.
Features:
- Automatic Romaji to Kana conversion in reading input using WanaKana.
- Multiple input support (separated by a space for readings and by a comma for meanings,
eg: "axe, loaf bread counter" or "あめ あま").
- Shows each right and wrong answer from your input.
- Automatic resizing of characters for small screens.
- Reordered the Back template of the cards to be more similar to the Wanikani site (reading related things come first in reading cards,
same for meaning).
Check the comments below for instructions on how to install and screenshots.
If you have any suggestion or some bug be sure to comment below and i will be glad to help.
{{FrontSide}}
<div id="card-back">
<div id="meanings">
<h2>Meanings</h2>
<p>{{Meaning}}</p>
</div>
<div id="components">
<h2>Components</h2>
<div id="component-data" class="flex-container"></div>
</div>
<div id="meaning-mnemonic">
<h2>Meaning Mnemonic</h2>
<p>{{Meaning_Mnemonic}}</p>
<p id="meaning-mnemonic-hint" class="mnemonic-hint">{{Meaning_Info}}</p>
</div>
<div id="readings">
<h2>Readings</h2>
<div class="flex-container">
<div id="onyomi-readings" class="flex-item">
<h3>On'yomi</h3>
<p>{{Reading_Onyomi}}</p>
</div>
<div id="kunyomi-readings" class="flex-item">
<h3>Kun'yomi</h3>
<p>{{Reading_Kunyomi}}</p>
</div>
<div id="nanori-readings" class="flex-item">
<h3>Nanori</h3>
<p>{{Reading_Nanori}}</p>
</div>
<div id="other-readings" class="flex-item">
<p>{{Reading}}</p>
</div>
</div>
<div id="reading-audios">{{Audio}}</div>
</div>
<div id="reading-mnemonic">
<h2>Reading Mnemonic</h2>
<p>{{Reading_Mnemonic}}</p>
<p id="reading-mnemonic-hint" class="mnemonic-hint">{{Reading_Info}}</p>
</div>
<div id="context-sentences">
<h2>Context Sentences</h2>
</div>
</div>
<!-- Insert speech type into meanings -->
<script>
var div = document.getElementById("meanings");
if (`{{Speech_Type}}` !== "") {
var element = document.createElement("p");
element.innerHTML = `<p>Part of Speech: {{Speech_Type}}`;
div.appendChild(element);
}
</script>
<!-- Add context sentences -->
<script>
var div = document.getElementById("context-sentences");
function appendContextSentence(jp, en) {
if (jp !== "" && div) {
var element = document.createElement("p");
element.innerHTML = "<p>" + jp + "</br>" + en + "</p>";
div.appendChild(element);
}
}
appendContextSentence(`{{Context_jp}}`, `{{Context_en}}`);
appendContextSentence(`{{Context_jp_2}}`, `{{Context_en_2}}`);
appendContextSentence(`{{Context_jp_3}}`, `{{Context_en_3}}`);
if (
`{{Context_jp}}{{Context_en}}{{Context_jp_2}}{{Context_en_2}}{{Context_jp_3}}{{Context_en_3}}`.trim() ===
""
) {
div.style.display = "none";
}
</script>
<!-- Hide empty reading sections -->
<script>
function disable_reading_div(id, readings) {
var div = document.getElementById(id);
if (div)
{
if (readings === "") {
div.style.display = "none";
} else {
div.style.display = "";
}
}
}
disable_reading_div("onyomi-readings", `{{Reading_Onyomi}}`);
disable_reading_div("kunyomi-readings", `{{Reading_Kunyomi}}`);
disable_reading_div("nanori-readings", `{{Reading_Nanori}}`);
disable_reading_div("other-readings", `{{Reading}}`);
disable_reading_div(
"readings",
`{{Reading_Onyomi}}{{Reading_Kunyomi}}{{Reading_Nanori}}{{Reading}}`
);
</script>
<!-- Populate components -->
<script>
var components = `{{Components}}`.split(", ").filter((e) => e !== "");
var componentNames = `{{Component_Names}}`
.split(", ")
.filter((e) => e !== "");
var componentTypes = `{{Component_Types}}`
.split(", ")
.filter((e) => e !== "");
var componentDataDiv = document.getElementById("component-data");
for (var i = 0; i < components.length; i++) {
var textDiv = document.createElement(componentTypes[i]);
textDiv.innerHTML = components[i];
var nameDiv = document.createElement("span");
nameDiv.innerHTML = componentNames[i];
var componentDiv = document.createElement("div");
componentDiv.classList.add("component");
componentDiv.classList.add("flex-item");
componentDiv.appendChild(textDiv);
componentDiv.appendChild(nameDiv);
componentDataDiv.appendChild(componentDiv);
}
var componentsDiv = document.getElementById("components");
if (components.length > 0) {
componentsDiv.style.display = "";
} else {
componentsDiv.style.display = "none";
}
</script>
<!-- Hide empty mnemonics -->
<script>
function disableDiv(id, mnemonic) {
var div = document.getElementById(id);
if (div)
{
if (mnemonic === "") {
div.style.display = "none";
} else {
div.style.display = "";
}
}
}
var meaningMnemonic = `{{Meaning_Mnemonic}}`;
var meaningHint = `{{Meaning_Info}}`;
var readingMnemonic = `{{Reading_Mnemonic}}`;
var readingHint = `{{Reading_Info}}`;
disableDiv("meaning-mnemonic", meaningMnemonic);
disableDiv("meaning-mnemonic-hint", meaningHint);
disableDiv("reading-mnemonic", readingMnemonic);
disableDiv("reading-mnemonic-hint", readingHint);
</script>
<!-- Check the answer -->
<script>
//some addons read the script too early:
if (document.getElementById("typeans")) {
var typedAnswer = "";
var typedAnswer_element = document.getElementById("typeans");
if (typedAnswer_element.querySelector(".typegiven")) {
// the "typegiven" class exists only on iphone:
typedAnswer = typedAnswer_element
.querySelector(".typegiven")
.querySelector(".typeBad").innerText;
} else if (typedAnswer_element.querySelector(".typeBad")) {
//for pc and android:
typedAnswer = typedAnswer_element.querySelector(".typeBad").innerText;
}
typedAnswerLower = typedAnswer.toLowerCase();
var meaningWhitelist = `{{Meaning_Whitelist}}`.toLowerCase().split(", ");
var meaningBlacklist = `{{Meaning_Blacklist}}`.toLowerCase().split(", ");
var readingWhitelist = `{{Reading_Whitelist}}`.toLowerCase().split(", ");
var readingBlacklist = `{{Reading_Blacklist}}`.toLowerCase().split(", ");
var correctAnswers = [];
var incorrectAnswers = [];
if (`{{Card}}` === "Meaning") {
correctAnswers = meaningWhitelist;
incorrectAnswers = meaningBlacklist;
} else if (`{{Card}}` === "Reading") {
correctAnswers = readingWhitelist;
incorrectAnswers = readingBlacklist;
}
var answerDiv = document.createElement("div");
answerDiv.setAttribute("id", "typeans");
answerDiv.innerHTML = typedAnswer;
document.getElementById("typeans").replaceWith(answerDiv);
function answerListIncorrect(correctArr, answerStr, separator)
{
const respArr = answerStr.split(separator);
let incorrectList = [];
for (let r of respArr)
{
const trimmed = r.trim();
if (!correctArr.includes(trimmed))
{
incorrectList.push(trimmed);
}
}
return incorrectList;
}
var answersSplit = typedAnswerLower.split(',');
answersSplit = answersSplit.map(ans => ans.trim().toLowerCase());
var incorrectList = answerListIncorrect(correctAnswers, typedAnswerLower, ',');
var incorrectListAnswers = answerListIncorrect(incorrectAnswers, typedAnswerLower, ',');
if (typedAnswerLower === "") {
answerDiv.style.display = "none";
} else if (incorrectListAnswers.length === 0) {
answerDiv.classList.add("incorrect");
} else if (incorrectList.length === 0) {
answerDiv.classList.add("correct");
} else if (incorrectList.length > 0) {
answerDiv.classList.add("incorrect");
} else {
answerDiv.classList.add("incorrect");
}
const cardDiv = document.getElementById('card-back');
const incdiv = document.createElement('div');
incdiv.style = 'width: 100%; text-align: center; margin-top: 12px;';
cardDiv.insertBefore(incdiv, cardDiv.firstChild);
for (const ans of incorrectList)
{
if (ans === '') continue;
const span = document.createElement('span');
span.innerHTML = ans;
span.classList.add('inputBox');
span.classList.add('incorrect');
span.style = 'width: fit-content; padding: 8px 12px 8px 12px; border-radius: 2px; margin-right: 8px;';
incdiv.appendChild(span);
}
const corrdiv = document.createElement('div');
corrdiv.style = 'width: 100%; text-align: center;';
cardDiv.insertBefore(corrdiv, incdiv);
for (const ans of correctAnswers)
{
if (!answersSplit.includes(ans)) continue;
const span = document.createElement('span');
span.innerHTML = ans;
span.classList.add('inputBox');
span.classList.add('correct');
span.style = 'width: fit-content; padding: 8px 12px 8px 12px; border-radius: 2px; margin-right: 8px;';
corrdiv.appendChild(span);
}
}
</script>
<!-- Generate tooltips -->
<script>
function setTooltips(tags, text) {
for (var i = 0; i < tags.length; i++) {
tags[i].setAttribute("title", text);
}
}
var kanji = document.getElementsByTagName("kanji");
var radicals = document.getElementsByTagName("radical");
var vocab = document.getElementsByTagName("vocab");
var reading = document.getElementsByTagName("reading");
setTooltips(kanji, "kanji");
setTooltips(radicals, "radical");
setTooltips(vocab, "vocab");
setTooltips(reading, "reading");
</script>
<div id="card-front">
<div id="quest">
<div id="quest-display">
<span id="quest-char">
{{Characters}}
</span>
</div>
<div id="quest-name"></div>
<div class="input">
{{type:Do_Not_Modify}}
</div>
</div>
</div>
<script>
var injectScript = (src) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
};
var shrink = () => {
var textSpan = document.getElementById("quest-char");
var textDiv = document.getElementById("quest");
if (!textSpan || !textDiv)
return;
var size = 144;
textSpan.style.fontSize = size + 'px';
while (textSpan.offsetWidth > textDiv.offsetWidth)
{
textSpan.style.fontSize = parseInt(textSpan.style.fontSize.replace('px', '')) - 32 + 'px';
}
textSpan.style.fontSize = parseInt(textSpan.style.fontSize.replace('px', '')) - 8 + 'px';
}
shrink();
var questName = "{{Card}}";
if ("{{Object_Type}}" === "Radical") {
questName = "Name";
}
var questNameDiv = document.getElementById("quest-name");
questNameDiv.innerHTML = "{{Object_Type}} <b>" + questName + "</b>";
if ("{{Card}}" === "Meaning") {
questNameDiv.classList.add("quest");
} else if ("{{Card}}" === "Reading") {
questNameDiv.classList.add("quest2");
}
var questDisplayDiv = document.getElementById("quest-display");
if ("{{Object_Type}}" == "Radical") {
questDisplayDiv.classList.add("radical");
} else if ("{{Object_Type}}" == "Kanji") {
questDisplayDiv.classList.add("kanji");
} else if ("{{Object_Type}}" == "Vocabulary") {
questDisplayDiv.classList.add("vocab");
}
var answerDiv = document.getElementById("typeans");
if (answerDiv !== null) {
answerDiv.setAttribute("placeholder", "Your Response");
}
</script>
{{FrontSide}}
<div id="card-back">
<div id="readings">
<h2>Readings</h2>
<div class="flex-container">
<div id="onyomi-readings" class="flex-item">
<h3>On'yomi</h3>
<p>{{Reading_Onyomi}}</p>
</div>
<div id="kunyomi-readings" class="flex-item">
<h3>Kun'yomi</h3>
<p>{{Reading_Kunyomi}}</p>
</div>
<div id="nanori-readings" class="flex-item">
<h3>Nanori</h3>
<p>{{Reading_Nanori}}</p>
</div>
<div id="other-readings" class="flex-item">
<p>{{Reading}}</p>
</div>
</div>
<div id="reading-audios">{{Audio}}</div>
</div>
<div id="reading-mnemonic">
<h2>Reading Mnemonic</h2>
<p>{{Reading_Mnemonic}}</p>
<p id="reading-mnemonic-hint" class="mnemonic-hint">{{Reading_Info}}</p>
</div>
<div id="meanings">
<h2>Meanings</h2>
<p>{{Meaning}}</p>
</div>
<div id="components">
<h2>Components</h2>
<div id="component-data" class="flex-container"></div>
</div>
<div id="context-sentences">
<h2>Context Sentences</h2>
</div>
<div id="meaning-mnemonic">
<h2>Meaning Mnemonic</h2>
<p>{{Meaning_Mnemonic}}</p>
<p id="meaning-mnemonic-hint" class="mnemonic-hint">{{Meaning_Info}}</p>
</div>
</div>
<!-- Insert speech type into meanings -->
<script>
var div = document.getElementById("meanings");
if (`{{Speech_Type}}` !== "") {
var element = document.createElement("p");
element.innerHTML = `<p>Part of Speech: {{Speech_Type}}`;
div.appendChild(element);
}
</script>
<!-- Add context sentences -->
<script>
var div = document.getElementById("context-sentences");
function appendContextSentence(jp, en) {
if (jp !== "") {
var element = document.createElement("p");
element.innerHTML = "<p>" + jp + "</br>" + en + "</p>";
div.appendChild(element);
}
}
appendContextSentence(`{{Context_jp}}`, `{{Context_en}}`);
appendContextSentence(`{{Context_jp_2}}`, `{{Context_en_2}}`);
appendContextSentence(`{{Context_jp_3}}`, `{{Context_en_3}}`);
if (
`{{Context_jp}}{{Context_en}}{{Context_jp_2}}{{Context_en_2}}{{Context_jp_3}}{{Context_en_3}}`.trim() ===
""
) {
div.style.display = "none";
}
</script>
<!-- Hide empty reading sections -->
<script>
function disable_reading_div(id, readings) {
var div = document.getElementById(id);
if (readings === "") {
div.style.display = "none";
} else {
div.style.display = "";
}
}
disable_reading_div("onyomi-readings", `{{Reading_Onyomi}}`);
disable_reading_div("kunyomi-readings", `{{Reading_Kunyomi}}`);
disable_reading_div("nanori-readings", `{{Reading_Nanori}}`);
disable_reading_div("other-readings", `{{Reading}}`);
disable_reading_div(
"readings",
`{{Reading_Onyomi}}{{Reading_Kunyomi}}{{Reading_Nanori}}{{Reading}}`
);
</script>
<!-- Populate components -->
<script>
var components = `{{Components}}`.split(", ").filter((e) => e !== "");
var componentNames = `{{Component_Names}}`
.split(", ")
.filter((e) => e !== "");
var componentTypes = `{{Component_Types}}`
.split(", ")
.filter((e) => e !== "");
var componentDataDiv = document.getElementById("component-data");
for (var i = 0; i < components.length; i++) {
var textDiv = document.createElement(componentTypes[i]);
textDiv.innerHTML = components[i];
var nameDiv = document.createElement("span");
nameDiv.innerHTML = componentNames[i];
var componentDiv = document.createElement("div");
componentDiv.classList.add("component");
componentDiv.classList.add("flex-item");
componentDiv.appendChild(textDiv);
componentDiv.appendChild(nameDiv);
componentDataDiv.appendChild(componentDiv);
}
var componentsDiv = document.getElementById("components");
if (components.length > 0) {
componentsDiv.style.display = "";
} else {
componentsDiv.style.display = "none";
}
</script>
<!-- Hide empty mnemonics -->
<script>
function disableDiv(id, mnemonic) {
var div = document.getElementById(id);
if (mnemonic === "") {
div.style.display = "none";
} else {
div.style.display = "";
}
}
var meaningMnemonic = `{{Meaning_Mnemonic}}`;
var meaningHint = `{{Meaning_Info}}`;
var readingMnemonic = `{{Reading_Mnemonic}}`;
var readingHint = `{{Reading_Info}}`;
disableDiv("meaning-mnemonic", meaningMnemonic);
disableDiv("meaning-mnemonic-hint", meaningHint);
disableDiv("reading-mnemonic", readingMnemonic);
disableDiv("reading-mnemonic-hint", readingHint);
</script>
<!-- Check the answer -->
<script>
//some addons read the script too early:
if (document.getElementById("typeans")) {
var typedAnswer = "";
var typedAnswer_element = document.getElementById("typeans");
if (typedAnswer_element.querySelector(".typegiven")) {
// the "typegiven" class exists only on iphone:
typedAnswer = typedAnswer_element
.querySelector(".typegiven")
.querySelector(".typeBad").innerText;
} else if (typedAnswer_element.querySelector(".typeBad")) {
//for pc and android:
typedAnswer = typedAnswer_element.querySelector(".typeBad").innerText;
}
typedAnswerLower = typedAnswer.toLowerCase();
var meaningWhitelist = `{{Meaning_Whitelist}}`.toLowerCase().split(", ");
var meaningBlacklist = `{{Meaning_Blacklist}}`.toLowerCase().split(", ");
var readingWhitelist = `{{Reading_Whitelist}}`.toLowerCase().split(", ");
var readingBlacklist = `{{Reading_Blacklist}}`.toLowerCase().split(", ");
var correctAnswers = [];
var incorrectAnswers = [];
if (`{{Card}}` === "Meaning") {
correctAnswers = meaningWhitelist;
incorrectAnswers = meaningBlacklist;
} else if (`{{Card}}` === "Reading") {
correctAnswers = readingWhitelist;
incorrectAnswers = readingBlacklist;
}
var answerDiv = document.createElement("div");
answerDiv.setAttribute("id", "typeans");
answerDiv.innerHTML = typedAnswer;
document.getElementById("typeans").replaceWith(answerDiv);
function answerListIncorrect(correctArr, answerStr, separator)
{
const respArr = answerStr.split(separator);
let incorrectList = [];
for (let r of respArr)
{
const trimmed = r.trim();
if (!correctArr.includes(trimmed))
{
incorrectList.push(trimmed);
}
}
return incorrectList;
}
var answersSplit = typedAnswerLower.split(' ');
var incorrectList = answerListIncorrect(correctAnswers, typedAnswerLower, ' ');
var incorrectListAnswers = answerListIncorrect(incorrectAnswers, typedAnswerLower, ' ');
if (typedAnswerLower === "") {
answerDiv.style.display = "none";
} else if (incorrectListAnswers.length === 0) {
answerDiv.classList.add("incorrect");
} else if (incorrectList.length === 0) {
answerDiv.classList.add("correct");
} else if (incorrectList.length > 0) {
answerDiv.classList.add("incorrect");
} else {
answerDiv.classList.add("incorrect");
}
const cardDiv = document.getElementById('card-back');
const incdiv = document.createElement('div');
incdiv.style = 'width: 100%; text-align: center; margin-top: 12px;';
cardDiv.insertBefore(incdiv, cardDiv.firstChild);
for (const ans of incorrectList)
{
if (ans === '') continue;
const span = document.createElement('span');
span.innerHTML = ans;
span.classList.add('inputBox');
span.classList.add('incorrect');
span.style = 'width: fit-content; padding: 8px 12px 8px 12px; border-radius: 2px; margin-right: 8px;';
incdiv.appendChild(span);
}
const corrdiv = document.createElement('div');
corrdiv.style = 'width: 100%; text-align: center;';
cardDiv.insertBefore(corrdiv, incdiv);
for (const ans of correctAnswers)
{
if (!answersSplit.includes(ans)) continue;
const span = document.createElement('span');
span.innerHTML = ans;
span.classList.add('inputBox');
span.classList.add('correct');
span.style = 'width: fit-content; padding: 8px 12px 8px 12px; border-radius: 2px; margin-right: 8px;';
corrdiv.appendChild(span);
}
}
</script>
<!-- Generate tooltips -->
<script>
function setTooltips(tags, text) {
for (var i = 0; i < tags.length; i++) {
tags[i].setAttribute("title", text);
}
}
var kanji = document.getElementsByTagName("kanji");
var radicals = document.getElementsByTagName("radical");
var vocab = document.getElementsByTagName("vocab");
var reading = document.getElementsByTagName("reading");
setTooltips(kanji, "kanji");
setTooltips(radicals, "radical");
setTooltips(vocab, "vocab");
setTooltips(reading, "reading");
</script>
<div id="card-front">
<div id="quest">
<div id="quest-display">
<span id="quest-char">
{{Characters}}
</span>
</div>
<div id="quest-name"></div>
<div class="input" id="quest_input">
{{type:Do_Not_Modify}}
</div>
</div>
</div>
<script>
var injectScript = (src) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
};
var shrink = () => {
var textSpan = document.getElementById("quest-char");
var textDiv = document.getElementById("quest");
if (!textSpan || !textDiv)
return;
textSpan.style.fontSize = 144 + 'px';
while (textSpan.offsetWidth > textDiv.offsetWidth)
{
textSpan.style.fontSize = parseInt(textSpan.style.fontSize.replace('px', '')) - 32 + 'px';
}
textSpan.style.fontSize = parseInt(textSpan.style.fontSize.replace('px', '')) - 8 + 'px';
}
(async () => {
if (typeof eviter_offset === 'undefined') {
await injectScript('_wanakana.min.js');
}
if (!wanakana) return;
var inputDiv = document.getElementById('quest_input');
const inputs = inputDiv.getElementsByTagName('input');
for(let input of inputs)
{
wanakana.bind(input);
input.placeholder = 'かな';
}
shrink();
})();
var questName = "{{Card}}";
if ("{{Object_Type}}" === "Radical") {
questName = "Name";
}
var questNameDiv = document.getElementById("quest-name");
questNameDiv.innerHTML = "{{Object_Type}} <b>" + questName + "</b>";
if ("{{Card}}" === "Meaning") {
questNameDiv.classList.add("quest");
} else if ("{{Card}}" === "Reading") {
questNameDiv.classList.add("quest2");
}
var questDisplayDiv = document.getElementById("quest-display");
if ("{{Object_Type}}" == "Radical") {
questDisplayDiv.classList.add("radical");
} else if ("{{Object_Type}}" == "Kanji") {
questDisplayDiv.classList.add("kanji");
} else if ("{{Object_Type}}" == "Vocabulary") {
questDisplayDiv.classList.add("vocab");
}
var answerDiv = document.getElementById("typeans");
if (answerDiv !== null) {
answerDiv.setAttribute("placeholder", "Your Response");
}
</script>
@KoxuKoshu
Copy link

KoxuKoshu commented Aug 20, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment