您需要先安装一个扩展,例如 篡改猴、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); } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址