您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a possiblity to hide meaning and reading mnemonics.
- // ==UserScript==
- // @name WaniKani hide mnemonics
- // @namespace wkhidem
- // @description Adds a possiblity to hide meaning and reading mnemonics.
- // @version 1.8
- // @author Niklas Barsk
- // @include https://www.wanikani.com/review/session*
- // @include https://www.wanikani.com/lesson/session*
- // @include https://www.wanikani.com/radicals/*
- // @include https://www.wanikani.com/kanji/*
- // @include https://www.wanikani.com/vocabulary/*
- // @run-at document-end
- // ==/UserScript==
- /*
- * This script is licensed under the MIT licence.
- */
- if (isReview() || isLesson())
- {
- // review/lessons quiz
- var mo = new MutationObserver(initQuiz);
- mo.observe(document.getElementById("item-info-col2"), {'childList': true});
- }
- if (isLesson())
- {
- // Call init whenever the main-info class attribute is updated.
- // This happens whenever the user switch to a new item on the
- // lesson/learning part.
- var mo = new MutationObserver(init);
- mo.observe(document.getElementById("main-info"), {'attributes': true});
- }
- if (isLookup())
- {
- if (document.getElementById("progress").style.display != "none")
- {
- // only run the script on items that has been unlocked since it's
- // not possible to add user mnemonics on locked items.
- init();
- }
- }
- function initQuiz(allmutations)
- {
- if (allmutations[0].addedNodes.length > 0)
- {
- // Ignore the mutation if no nodes are added.
- // When going from one question to the next all elements in
- // item-info-col2 is first removed as one mutation and then
- // the new content is added as a second mutation. So mutations
- // without any added nodes should be ignored
- init();
- }
- }
- function init()
- {
- if (!sanityCheckPassed())
- {
- // Don't try to run the script if the HTML can't be parsed.
- console.warn("WaniKani hide mnemonics need to be updated to support the latest version of WaniKani.");
- return;
- }
- setCorrectText();
- setCorrectVisibility();
- if (isQuiz())
- {
- // Update visibility state when the "Show All Information" button is pressed.
- document.getElementById("all-info").addEventListener("click", setCorrectVisibility);
- }
- // Setup listeners for changes to the note-meaning/reading.
- var mo = new MutationObserver(onNoteChanged);
- var options = {'childList': true};
- mo.observe(getNoteElement("meaning"), options);
- if (!isRadical())
- {
- mo.observe(getNoteElement("reading"), options);
- }
- }
- /**
- * Called whenever the note-reading or note-meaning elements children
- * are updated.
- */
- function onNoteChanged(allmutations)
- {
- // 1 children for the edit note form and 0 children when the
- // note is being displayed. Update visibility when form is closed
- // and note is shown again.
- if (allmutations[0].target.children.length == 0)
- {
- // example id for quiz: 'meaning-note'
- // example id for learning: 'supplement-voc-meaning-notes'
- var index = isLesson() && !isQuiz() ? 2 : 1;
- var which = allmutations[0].target.parentNode.id.split('-')[index];
- setCorrectVisibilityFor(which);
- }
- }
- /**
- * Set the correct text for the meaning and reading headers depending on
- * the current state.
- */
- function setCorrectText()
- {
- setCorrectTextFor("meaning");
- if (!isRadical())
- {
- setCorrectTextFor("reading");
- }
- }
- /**
- * Returns true if the mnemonic is hidden for the current character
- * and the given type.
- */
- function isHidden(which)
- {
- return isManuallyHidden(which) || isAutomaticallyHidden(which);
- }
- /**
- * Returns true if the user has hidden the mnemonic with the hide link.
- */
- function isManuallyHidden(which)
- {
- return localStorage.getItem(getStorageKey(which)) == "0";
- }
- /**
- * Returns true if the mnemonic has been hidden because there
- * is a note present.
- */
- function isAutomaticallyHidden(which)
- {
- return hasNote(which) &&
- localStorage.getItem(getStorageKey(which)) != "1";
- }
- /**
- * Set hidden status for the current character in the localStorage
- * for the give type.
- * @param which "reading" or "meaning" depending on which key is desired.
- * @param what new value for the current character:
- * 0: user has manually hidden the mnemonic.
- * 1: user has shown an automatically hidden mnemonic.
- */
- function setStorage(which, what)
- {
- localStorage.setItem(getStorageKey(which), what);
- }
- /**
- * Remove the stored information about the current character from
- * the localStorage for the give type.
- * @param which "reading" or "meaning" depending on which key is desired.
- */
- function clearStorage(which)
- {
- localStorage.removeItem(getStorageKey(which));
- }
- /**
- * Get the key that the removed state for the current character is
- * stored under in the localStorage.
- * @param "reading" or "meaning" depending on which key is desired.
- */
- function getStorageKey(which)
- {
- return getCharacterType() + "_" + getCharacter() + "_" + which;
- }
- /**
- * Return a textual representation of the current character.
- * For vocabulary, kanji and normal radicals it is the vocabulary,
- * kanji, or radical itself. For radicals that are just an image
- * it is the file name of the image.
- */
- function getCharacter()
- {
- var element;
- if (isLookup())
- {
- element = document.getElementsByClassName("japanese-font-styling-correction")[0];
- }
- else
- {
- element = document.getElementById("character");
- }
- var character = element.textContent.trim();
- if (character == "") // Radical with image instead of text.
- {
- var img = element.children[0];
- // During quiz the image is inside a span, during lessons
- // the image is directly under the character div.
- if (isQuiz())
- {
- img = img.children[0]
- }
- character = img.getAttribute("src").split("/").pop()
- }
- return character
- }
- /**
- * Return the type of the character the page is for: a string containing
- * "vocabulary", "kanji" or "radical".
- */
- function getCharacterType()
- {
- if (isLesson())
- {
- return document.getElementById("main-info").className.trim();
- }
- else if (isReview())
- {
- return document.getElementById("character").className.trim();
- }
- else if (isLookup())
- {
- var character = document.getElementsByClassName("japanese-font-styling-correction")[0];
- var cn = character.parentElement.className;
- return cn.substr(0, cn.indexOf("-"));
- }
- }
- /**
- * Return true if the current page is for a radical.
- */
- function isRadical()
- {
- return getCharacterType() == "radical";
- }
- /**
- * Return true if the current page is for vocabulary.
- */
- function isVocabulary()
- {
- return getCharacterType() == "vocabulary";
- }
- function isLookup()
- {
- return document.URL.indexOf("/radicals/") != -1 ||
- document.URL.indexOf("/kanji/") != -1 ||
- document.URL.indexOf("/vocabulary/") != -1;
- }
- /**
- * Returns true if the current page is a lesson.
- */
- function isLesson()
- {
- return document.URL.indexOf("lesson") != -1;
- }
- /**
- * Returns true if the current page is a review.
- */
- function isReview()
- {
- return document.URL.indexOf("review") != -1;
- }
- /**
- * Returns true if the user is currently doing a quiz.
- */
- function isQuiz()
- {
- if (isReview())
- {
- return true;
- }
- if (isLesson())
- {
- var mainInfo = document.getElementById("main-info");
- return mainInfo.parentElement.className == "quiz";
- }
- return false;
- }
- /**
- * Returns true if the current item has a note set.
- * @param which specifies if it's the reading or meaning
- * note that is of interest.
- */
- function hasNote(which)
- {
- return getNoteElement(which).textContent.trim() != "Click to add note";
- }
- /**
- * Set the correct visibility of the reading and meaning sections.
- */
- function setCorrectVisibility()
- {
- setCorrectVisibilityFor("meaning");
- if (!isRadical())
- {
- setCorrectVisibilityFor("reading");
- }
- }
- /**
- * Set the correct visibility for the specified header depending on the current state.
- * @param which The header that should be updated, either "reading" or "meaning".
- */
- function setCorrectVisibilityFor(which)
- {
- if (hiddenByWaniKani(which))
- {
- // Don't touch visibility for things hidden by WaniKani.
- return;
- }
- if (isHidden(which)) // In this case, should be hidden
- {
- hide(which);
- }
- else
- {
- show(which);
- }
- }
- /**
- * When doing a quiz WaniKani only shows the info that was being asked for
- * to see all info the user need to press a button to display it.
- * This method returns true if the given reading/meaning is currently hidden.
- *
- * @param which "reading" or "meaning"
- */
- function hiddenByWaniKani(which)
- {
- if (!isQuiz())
- {
- return false;
- }
- var infoHidden = document.getElementById("all-info").style.display != "none";
- var questionType = document.getElementById("question-type").className;
- return which != questionType && infoHidden;
- }
- /**
- * Set the correct state in local storage for the current item
- * and mnenemonic based on the given action.
- * @param action "hide" or "show"
- * @param which "reading" or "meaning"
- */
- function setCorrectStorage(action, which)
- {
- var note = hasNote(which);
- if (action == "show" && !note ||
- action == "hide" && note)
- {
- // Default state, cleare any storage
- clearStorage(which);
- }
- else if (action == "show" && note)
- {
- // Force section to be visible
- setStorage(which, 1);
- }
- else if (action == "hide" && !note)
- {
- // Force section to be hidden
- setStorage(which, 0);
- }
- }
- /**
- * Hide the specified section.
- * @param which The section that should be hidden, either "reading" or "meaning".
- */
- function hide(which)
- {
- setCorrectStorage("hide", which);
- setDisplayStyle(which, "none");
- setCorrectText();
- }
- /**
- * Show the specified section.
- * @param which The section that should be shown, either "reading" or "meaning".
- */
- function show(which)
- {
- setCorrectStorage("show", which);
- setDisplayStyle(which, "");
- setCorrectText();
- }
- /**
- * Set the display style of the hidable section.
- * @param which The section that should be updated, either "reading" or "meaning".
- * @param display The new value of the display css property.
- */
- function setDisplayStyle(which, display)
- {
- var children = getHidableElements(which);
- for (i = 0; i < children.length; ++i)
- {
- children[i].style.display = display;
- }
- }
- /**
- * Returns an array with all elements that should be hidden or
- * shown when the hide/show link is clicked.
- * @param Specifies if it's the "reading" or "meaning" that should be hidden
- */
- function getHidableElements(which)
- {
- // return an array of items to hide/show
- var ret = [];
- if (isQuiz())
- {
- ret.push(getMnemonicContainer(which));
- }
- else if (isLesson())
- {
- var children = getLearningContainer(which).children;
- for (i = 0; i < children.length - 2; ++i) // note section is last 2 elements.
- {
- ret.push(children[i]);
- }
- }
- else if (isLookup())
- {
- if (isRadical())
- {
- ret.push(getLookupMnemonicContainer(which));
- }
- else
- {
- var children = getLookupMnemonicContainer(which).children;
- for (i = 0; i < children.length - 1; ++i) // note section is the last element.
- {
- ret.push(children[i]);
- }
- }
- }
- return ret;
- }
- /**
- * Set the correct text for reading/meaning/note header with the apropriate
- * show/hide link depending on what the current state is.
- *
- * @param which Specifies which header should be updated, the "reading" or "meaning" header.
- * @param action Specifies what happens when the header is pressed, either "show" or "hide".
- * @param headerID The ID of the header that should be updated.
- * @param header The DOM element which should have its text updated.
- */
- function textForHeader(which, action, header)
- {
- // Add the show/hide link to the header.
- header.innerHTML = header.firstChild.textContent + getLinkHTML(which, action);
- // Set either hide(which) or show(which) as onclick handler for the new link.
- document.getElementById(getLinkId(which, action)).onclick = function()
- {
- if (action == "show")
- {
- show(which);
- }
- else
- {
- hide(which);
- }
- };
- }
- /**
- * Get the HTML for the show/hide link.
- * @param which Specifies if the link is for "reading" or "meaning".
- * @param action Specifies if the link is "hide" or "show".
- */
- function getLinkHTML(which, action)
- {
- // Examples of what the html looks like:
- // <span id="show-reading">(show original meaning)</span>
- // <span id="hide-meaning">(hide)</span>
- var linkText = action;
- if (action == "show")
- {
- if (isVocabulary())
- {
- linkText += " original explanation";
- }
- else
- {
- linkText += " original mnemonic";
- }
- }
- return "<span id=\"" + getLinkId(which, action) + "\"> (" + linkText + ")</span>";
- }
- /**
- * Return the id of the show/hide link.
- */
- function getLinkId(which, action)
- {
- var quiz = isQuiz() ? "-q" : "";
- return action + "-" + which + "-" + getCharacterType() + quiz;
- }
- /**
- * Set the correct text for the specified header depending on the current state.
- * @param which The header that should be updated, either "reading" or "meaning".
- */
- function setCorrectTextFor(which)
- {
- if (isHidden(which))
- {
- // Display the "show" link in the note.
- textForHeader(which, "show", getNoteHeader(which));
- }
- else
- {
- // Display the "hide" link in the header.
- textForHeader(which, "hide", getMnemonicHeader(which));
- // Make sure the default version of the Note header is displayed.
- var nh = getNoteHeader(which);
- nh.innerHTML = nh.firstChild.textContent;
- }
- }
- /**
- * Get the DOM element that contains the mnemonic.
- * @param which Specifies if the header for the reading or meaning should
- * be returned. The parameter is ignored for radicals since
- * they only have one mnemonic.
- */
- function getMnemonicContainer(which)
- {
- if (isRadical())
- {
- return document.getElementById("item-info-col2").children[0];
- }
- else
- {
- return document.getElementById("item-info-" + which + "-mnemonic");
- }
- }
- /**
- * Return the element that contains the mnemonics for the lookup pages.
- */
- function getLookupMnemonicContainer(which)
- {
- if (isRadical())
- {
- return document.getElementById("note-" + which).previousElementSibling;
- }
- else
- {
- return document.getElementById("note-" + which).parentElement;
- }
- }
- /**
- * Get the DOM element for the mnemonic header.
- * @param which Specifies if the header for the reading or meaning should
- * be returned. The parameter is ignored for radicals since
- * they only have one mnemonic.
- */
- function getMnemonicHeader(which)
- {
- if (isQuiz())
- {
- return getMnemonicContainer(which).children[0];
- }
- else if (isLesson())
- {
- return getLearningContainer(which).children[0];
- }
- else if(isLookup())
- {
- return getLookupMnemonicContainer(which).children[0];
- }
- }
- /**
- * Get the DOM element for the user notes header.
- * @param which Specifies if the notes header for the reading or meaning
- * should be returned.
- */
- function getNoteHeader(which)
- {
- if (isQuiz() || isLookup())
- {
- return document.getElementById("note-" + which).children[0];
- }
- else if (isLesson())
- {
- var container = getLearningContainer(which);
- return container.children[container.children.length - 2];
- }
- }
- /**
- * Returns the DOM element that holds the user note.
- * @param which Specifies if the notes element for the reading or meaning
- * should be returned.
- */
- function getNoteElement(which)
- {
- var element;
- if (isLesson() && !isQuiz())
- {
- var id;
- if (isRadical())
- {
- id = "supplement-rad-name-notes";
- }
- else
- {
- id = "supplement-" + getCharacterType().substring(0,3) + "-" + which + "-notes";
- }
- element = document.getElementById(id).children[0];
- }
- else
- {
- element = document.getElementById("note-" + which).children[1];
- }
- return element;
- }
- /**
- * Get the container element of the mnemonics and notes in the learning
- * part of lessons. There is no id available for the actual headers and
- * mnemonics like in the quiz so the container element is the closest
- * we get.
- *
- * @param which Specifies if it's the container for "reading" or "meaning"
- * that is desired.
- */
- function getLearningContainer(which)
- {
- var id = "supplement-" + getCharacterType().substring(0,3) + "-";
- var className = "pure-u-3-4";
- if (isRadical())
- {
- id += "name";
- className = "pure-u-1"
- }
- else
- {
- id += which;
- }
- return document.getElementById(id).getElementsByClassName(className)[0];
- }
- /**
- * Return true if critical assumptions made about the HTML code holds.
- */
- function sanityCheckPassed()
- {
- try
- {
- if (isLookup())
- {
- sanityCheckLookup();
- }
- if (isQuiz())
- {
- sanityCheckQuiz();
- }
- if (isLesson())
- {
- sanityCheckLesson();
- }
- // Make sure we can get a correct character type.
- var ct = getCharacterType();
- if (ct != "radical" && ct != "vocabulary" && ct != "kanji")
- {
- throw new Error("Unknown character type: " + ct);
- }
- // Make sure we can get a correct storage key
- var parts = getStorageKey("meaning").split("_");
- if (parts.length != 3 || parts[0] == "" ||
- parts[1] == "" || parts[2] == "")
- {
- throw new Error("Unable to generate a correct storage key: " + key);
- }
- }
- catch (e)
- {
- console.error(e.toString());
- return false;
- }
- return true;
- }
- /**
- * Throws an exception if the critical assumptions made about the
- * HTML code in the lookup related code are wrong.
- */
- function sanityCheckLookup()
- {
- if (document.getElementsByClassName("japanese-font-styling-correction").length == 0)
- {
- throw new Error("No element with class 'japanese-font-styling-correction' exists");
- }
- ensureElementExists("note-meaning");
- if (!isRadical())
- {
- ensureElementExists("note-reading");
- }
- }
- /**
- * Throws an exception if the critical assumptions made about the
- * HTML code in the quiz related code are wrong.
- */
- function sanityCheckQuiz()
- {
- ensureElementExists("character");
- ensureElementExists("all-info");
- ensureElementExists("item-info-col2");
- ensureElementExists("note-meaning");
- var questionType = ensureElementExists("question-type");
- questionType = questionType.className;
- if (questionType != "reading" && questionType != "meaning")
- {
- throw new Error("'question-type' is neither \"reading\" nor \"meaning\", it is \"" + questionType + "\"");
- }
- if (!isRadical())
- {
- ensureElementExists("item-info-reading-mnemonic");
- ensureElementExists("item-info-meaning-mnemonic");
- ensureElementExists("note-reading");
- }
- }
- /**
- * Throws an exception if the critical assumptions made about the
- * HTML code in the lesson related code are wrong.
- */
- function sanityCheckLesson()
- {
- ensureElementExists("character");
- var mainInfo = ensureElementExists("main-info");
- // Make sure assumptions for lessons in isQuiz() holds.
- var cn = mainInfo.parentElement.className;
- if (cn != "" && cn != "quiz")
- {
- throw new Error("Parent of 'main-info' is neither empty nor \"quiz\"");
- }
- if (!isQuiz())
- {
- ensureElementExists("supplement-rad-name-notes");
- ensureElementExists("supplement-kan-meaning-notes");
- ensureElementExists("supplement-voc-meaning-notes");
- ensureElementExists("supplement-kan-reading-notes");
- ensureElementExists("supplement-voc-reading-notes");
- ensureElementExistsAndHasClass("supplement-voc-reading", "pure-u-3-4");
- ensureElementExistsAndHasClass("supplement-voc-meaning", "pure-u-3-4");
- ensureElementExistsAndHasClass("supplement-kan-reading", "pure-u-3-4");
- ensureElementExistsAndHasClass("supplement-kan-meaning", "pure-u-3-4");
- ensureElementExistsAndHasClass("supplement-rad-name", "pure-u-1");
- }
- }
- /**
- * Throws an exception if the given id doesn't exist in the DOM tree.
- * @return the element if it exist
- */
- function ensureElementExists(id)
- {
- var element = document.getElementById(id);
- if (element == null)
- {
- throw new Error(id + " does not exist");
- }
- return element;
- }
- /**
- * Throws an exception if the given id doesn't exist in the DOM tree.
- */
- function ensureElementExistsAndHasClass(id, className)
- {
- var element = ensureElementExists(id);
- if (element.getElementsByClassName(className)[0] == null)
- {
- throw new Error(id + " does not contain any element with class: " + className);
- }
- }