您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds information to Wanikani about kanji that use Phonetic-Semantic Composition.
当前为
// ==UserScript== // @name WaniKani Phonetic-Semantic Composition Rebirth // @namespace wk_phon_rebirth // @include http://www.wanikani.com/kanji/* // @include http://www.wanikani.com/review/session* // @include http://www.wanikani.com/lesson/session* // @include https://www.wanikani.com/kanji/* // @include https://www.wanikani.com/review/session* // @include https://www.wanikani.com/lesson/session* // @author acm // @description Adds information to Wanikani about kanji that use Phonetic-Semantic Composition. // @version 1.1.0 // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // @grant GM_addStyle // @require http://code.jquery.com/jquery-2.0.3.min.js // @require https://greasyfork.org/scripts/34328-wanikani-phonetic-semantic-composition-original-database/code/Wanikani%20Phonetic-Semantic%20Composition%20Original%20Database.js // ==/UserScript== /* * ==== Wanikani Phonetic-Semantic Composition ==== * == by ruipgpinheiro == * = modifications by acm = * * It seems that many kanji were created using a process called phonetic-semantic * composition. This process joins two (or more) kanji (radicals), one (or more of them) * usually called the bushu or dictionary section header establishes the meaning of the * kanji, and another one, the phonetic component that establishes the (on'yomi) sound. * * This means that a lot of kanji have a built-in mnemonic that I haven't seen being * referred to in Wanikani, and so it's quite useful to know some of them, especially * when having trouble with a specific reading! * * * * For example (using non-Wanikani kanji names): * * 反・はん "to rebel" ("anti" by Wanikani mnemonics) * * 飯・はん "rice" * 版・はん "print" * 板・はん "a board" * 坂・はん "slope" * 販・はん "sale" * 叛・はん "to betray" * * As you can notice, these kanji all use the first one as a phonetic component, placing it * to the right of the semantic component (mostly, phonetic components are drawn right-most). * Due to the evolution of the language, many such kanji have since then slightly changed * pronunciation (仮・か "temporary"), but knowing this information can be a major help. * * This script imports a database of over 100 phonetic components with over 400 regular Kanji * that use their on'yomi reading onto Wanikani. This means that over a fourth of Wanikani's * Kanji should be included in here somewhere and have a "built-in mnemonic" of sorts. * Depending on how you study, it could be a huge help (or no help at all - you decide what's * best for your brain). The information will be shown on the Kanji info page, during reviews * (if you check the details for a Kanji) and during lessons, provided the relevant Kanji is * included in the database. * * Note that the database used in this script was automatically generated from a PDF file, * and even though I tried to check it for mistakes, it is possible that it contains an error or two. * This userscript contains the whole Kanji table from Hiroko Townsend's Thesis about phonetic * components, which means the script's database includes 143 different phonetic components * encompassing 417 regular kanji (kanji that use the on'yomi reading from the phonetic component) * and 210 irregular ones (kanji that use a different reading, though with - supposedly - similar * roots). Some of these Kanji aren't available on Wanikani, though, even though they'll be shown * by the userscript as they are part of its database. * */ /* * ==== LICENSE INFORMATION ==== * * This script contains a database of phonetic components adapted under Fair Use * (for nonprofit educational purposes) from * Phonetic Components in Japanese Characters * by Hiroko Townsend * Master of Arts in Linguistics, San Diego State University, 2011 * Obtain a complete copy of the Thesis at http://sdsu-dspace.calstate.edu/bitstream/handle/10211.10/1203/Townsend_Hiroko.pdf * Thank you Hiroko for the very useful thesis! * * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ /* * === Changelog === * * 1.1.0 (October 2017) * - Cleaned up code * - Added new section with more compositions * - Added 'nonphon' to the database, where components are the same as the phonetic components, but used for its meaning * - Corrected mistakes and moved items around. * * 1.0.5 (11 March 2014) * - Relicensed under the GPLv3. * * 1.0.4 (23 January 2014) * - Now supports the HTTPS protocol. * * 1.0.3 (24 November 2013) * - Corrected 症, which has the wrong reading in the thesis used for creating the DB. * It's now listed as irregular with the reading しょう, even though its phonetic component can also (rarely) be read the same way. * * 1.0.2 (24 November 2013) * - Fixed a bug in the code that automatically generated the DB, which would misread phonetic components with a single, * irregular kanji, like 刃, and put them inside the DB entry of the previous phonetic component. * Therefore, the DB was regenerated from scratch. Updated the DB count in the description accordingly. * * 1.0.1 (23 November 2013) * - Kanji links now open in a new tab, to fix a bug where clicking them would just restart the current reviews/lessons session. * * 1.0.0 (22 November 2013) * - First release. */ /* * Debug Settings */ var debugLogEnabled = false; var debugAlwaysUseFirstDBEntry = false; var scriptShortName = "WKPSC"; scriptLog = debugLogEnabled ? function(msg) { if (typeof msg === 'string') console.log(scriptShortName + ": " + msg); else console.log(msg); } : function() {}; /* * Global Variables/Objects/Classes */ // Stores the current Wanikani page we're on var PageEnum = Object.freeze({ unknown:0, kanji:1, reviews:2, lessons:3 }); var curPage = PageEnum.unknown; /* * Database Functions * * Searches the DB for a Kanji * If found, returns an object of the form {entry, regular, irregular}, where: * entry - reference to the DB entry containing the Kanji * type - 'regular' or 'irregular', specifies in which DB sub-array the kanji was found * kanji - Kanji found (same as the input kanji) * If not found, returns null */ function searchDBForKanji(kanji) { for (var i = 0; i < database.length; i++) { var cur = database[i]; if (kanji == cur.phonetic) return {entry:cur, type:"phonetic", kanji:cur.phonetic}; if ("regular" in cur) { for(var j = 0; j < cur.regular.length; j++) { if (kanji == cur.regular[j]) return {entry:cur, type:"regular", kanji:cur.regular[j]}; } } if ("irregular" in cur) { for (var k = 0; k < cur.irregular.length; k++) { if (kanji == cur.irregular[k][0]) return {entry:cur, type:"irregular", kanji:cur.irregular[k][0], irregular:cur.irregular[k]}; } } } return null; } /* * Injected Elements and Related Functions */ // Toggles the "More Information" button function WKPSC_moreInformation_onClick(e) { var obj = e.target || e.srcElement; var elem = obj.nextSibling; if (elem.getAttribute('class') == "WKPSC-more-information-hidden") { obj.innerHTML = 'Less Information <i class="icon-chevron-up">'; elem.setAttribute('class', "WKPSC-more-information-show"); } else { obj.innerHTML = 'More Information <i class="icon-chevron-down">'; elem.setAttribute('class', "WKPSC-more-information-hidden"); } } // Generates HTML for the injected Element function generateHTML(dbEntry) { var html; var regularText; // Detect whether mostly regular or not var regularCount = 0; var irregularCount = 0; if ("regular" in dbEntry.entry) regularCount = dbEntry.entry.regular.length; if ("irregular" in dbEntry.entry) irregularCount = dbEntry.entry.irregular.length; if (regularCount >= irregularCount) { if (irregularCount === 0) regularText = "completely regular"; else regularText = "mostly regular"; } else { if (regularCount === 0) regularText = "completely irregular"; else regularText = "mostly irregular"; } var totalCount = regularCount + irregularCount; // Generate correct HTML from templates var htmlTemplateThisKanji = '<span rel="tooltip" class="kanji-highlight" data-original-title="This Kanji" lang="ja">{0}</span>'; html = htmlTemplateThisKanji.format(dbEntry.kanji); var htmlTemplatePhonetic = ''; if(dbEntry.type == "phonetic") { htmlTemplatePhonetic = ' is a <b>{1}</b> phonetic component used in {2} Kanji to represent the <i>on\'yomi</i> reading <text class="WKPSC-hiragana">{3}</text>.'; html += htmlTemplatePhonetic.format(dbEntry.entry.phonetic, regularText, totalCount, dbEntry.entry.reading); } else { if(dbEntry.entry.phonetic == "obsolete") { htmlTemplateNonPhonetic = ' was formed using phonetic-semantic composition. However, with the passing of time, the phonetic component used became obsolete as a Kanji, so the radical cannot be shown here. Nevertheless, this kanji contains a <b>{1}</b> phonetic component, also used in {2} other Kanji to represent the <i>on\'yomi</i> reading <text class="WKPSC-hiragana">{3}</text>. You should be able to compare Kanji beloging to the same set to figure out what it looks like.'; } else { htmlTemplateNonPhonetic = ' was formed using phonetic-semantic composition. Therefore it contains the <b>{1}</b> phonetic component <a href="http://www.wanikani.com/kanji/{0}" target="_blank"><span rel="tooltip" class="kanji-highlight" data-original-title="Phonetic Component" lang="ja">{0}</span></a>, also used in {2} other Kanji to represent the <i>on\'yomi</i> reading <text class="WKPSC-hiragana">{3}</text>.'; } html += htmlTemplateNonPhonetic.format(dbEntry.entry.phonetic, regularText, totalCount-1, dbEntry.entry.reading); } if (dbEntry.type == "irregular") html += ' <u>Make sure to note that this Kanji is irregular!</u>'; var htmlTemplateMoreInformation = '</p><span id="WKPSC_info_btn" class="WKPSC-more-information-button WKPSC-more-information-button-margin">More Information <i class="icon-chevron-down"></i></span><span class="WKPSC-more-information-hidden">This series of phonetic-semantically composed Kanji consists of the '; html += htmlTemplateMoreInformation; var entryLength = 0; var cur = ""; var i = 0; // Generate the list of 'regular' Kanji from templates, if any exist if ("regular" in dbEntry.entry) { var regularKanjiTemplate = '<a href="http://www.wanikani.com/kanji/{0}" target="_blank"><span rel="tooltip" class="kanji-highlight" data-original-title="Kanji" lang="ja">{0}</span></a>'; var regIrregKanjiJoiningTemplate = '<br> There are also the following '; var regularTemplate = '<span rel="tooltip" class="reading-highlight" data-original-title="Same On\'yomi Reading">regular</span> Kanji '; html += regularTemplate; entryLength = dbEntry.entry.regular.length; for (i = 0; i < entryLength; i++) { cur = dbEntry.entry.regular[i]; if (i > 0 && i < entryLength-1) html += ", "; else if (i == entryLength-1 && i > 0) html += " and "; html += regularKanjiTemplate.format(cur); } html += '.'; if ("irregular" in dbEntry.entry) html += regIrregKanjiJoiningTemplate; } // Generate the table of 'irregular' Kanji from templates, if any exist if ("irregular" in dbEntry.entry) { var irregularKanjiTemplate = '<span rel="tooltip" class="reading-highlight" data-original-title="Similar On\'yomi Reading (shared historical roots)">irregular</span> Kanji:<table style="text-align:center; line-height:1.7" align="center" width="200px"><td class="span6"><h3>Kanji</h3></td><td class="span6"><h3>Reading</h3></td></tr>{0}</table>'; var rowTemplate = '<tr><td><a href="http://www.wanikani.com/kanji/{0}" target="_blank"><span class="kanji-highlight" lang="ja">{0}</span></a></td><td class="WKPSC-hiragana" lang="ja">{1}</td></tr>'; var tableHTML = ""; entryLength = dbEntry.entry.irregular.length; for (i = 0; i < entryLength; i++) { cur = dbEntry.entry.irregular[i]; tableHTML += rowTemplate.format(cur[0], cur[1]); } html += irregularKanjiTemplate.format(tableHTML); } // Close the remaining tag and return html += '</span>'; return html; } // Create the element to be injected, set its id, class and HTML content function createHTMLElement(dbEntry) { var elmnt; if (curPage == PageEnum.kanji) elmnt = document.createElement('aside'); else elmnt = document.createElement('blockquote'); elmnt.setAttribute('id', 'WKPSC-extra-information'); elmnt.setAttribute('class', 'additional-info'); elmnt.innerHTML = '<h3><i class="icon-info-sign"></i> Phonetic-Semantic Composition</h3><p>' + generateHTML(dbEntry) + '</p>'; return elmnt; } // Stores the old element, since we might have to clean it up when in the lessons module var oldElement = null; // Detects current Kanji, searches DB, and if a match is found, creates and injects the corresponding HTML Element function addElement(node) { // If required (lessons module), clean up the previously created element if (!isEmpty(oldElement) && !isEmpty(oldElement.parentNode)) oldElement.parentNode.removeChild(oldElement); oldElement = null; // Find the current kanji var kanji; if (debugAlwaysUseFirstDBEntry) kanji = database[0].phonetic; else { switch(curPage) { case PageEnum.kanji: kanji = getCurrentKanji_Kanji(); break; case PageEnum.reviews: kanji = getCurrentKanji_Reviews(); break; case PageEnum.lessons: kanji = getCurrentKanji_Lessons(); break; default: throw Error("Unknown page!"); } } scriptLog(kanji); // Check whether the current kanji is in the database var dbEntry = searchDBForKanji(kanji); if (isEmpty(dbEntry)) { scriptLog("Kanji not in DB. Ignoring."); return; } scriptLog(dbEntry); // Create custom element var newElmnt = createHTMLElement(dbEntry); // Insert element switch(curPage) { case PageEnum.kanji: $('section#note-reading').before(newElmnt); break; case PageEnum.reviews: $('section#item-info-reading-mnemonic').append(newElmnt); break; case PageEnum.lessons: $('div#supplement-kan-reading div:contains("Reading Mnemonic") blockquote:last').after(newElmnt); oldElement = newElmnt; break; default: throw Error("Unknown page type!"); } document.getElementById("WKPSC_info_btn").addEventListener("click", WKPSC_moreInformation_onClick); } /* * Kanji Info Pages */ function getCurrentKanji_Kanji() { var kanjiNode = document.getElementsByClassName("kanji-icon")[0].childNodes[0]; var kanji = kanjiNode.innerHTML.trim(); if (kanji.length == 1) return kanji; else throw new Error("Wrong 'kanji' length (" + kanji.length + "). kanji='"+ kanji +"'"); } function kanjiInfo_init() { GM_addStyle('.WKPSC-hiragana { font-weight: bold; }'); GM_addStyle('.WKPSC-more-information-button-margin { margin-bottom: -10px !important; display:block; }'); GM_addStyle('.WKPSC-more-information-show { margin-top: 40px; margin-bottom: -10px !important; display:block; }'); } /* * Reviews page */ function reviews_init() { GM_addStyle('.WKPSC-hiragana { font-weight: normal; }'); GM_addStyle('.WKPSC-more-information-button-margin { margin-bottom: 0 !important; display:block; }'); GM_addStyle('span.reading-highlight { background-color: #474747; } span.kanji-highlight { background-color: #FF00AA; };'); GM_addStyle('span.reading-highlight, span.kanji-highlight {-moz-box-sizing: border-box; border-radius: 3px; box-shadow: 0 -3px 0 rgba(0, 0, 0, 0.2) inset, 0 0 10px rgba(255, 255, 255, 0.5); color: #FFFFFF; display: inline-block; height: 1.8em; line-height: 1.7em; text-align: center; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3); padding-left: 3px; padding-right: 3px; }'); GM_addStyle('.WKPSC-more-information-show { margin-top: 20px; margin-bottom: -10px !important; display:block; }'); } function getCurrentKanji_Reviews() { var curItem = $.jStorage.get("currentItem"); if ("kan" in curItem) return curItem.kan.trim(); else return null; } /* * Lessons page */ lessons_init = reviews_init; function getCurrentKanji_Lessons() { var kanjiNode = document.getElementById("character"); if (isEmpty(kanjiNode)) return null; var kanji = kanjiNode.innerHTML.trim(); if (kanji.length == 1) return kanji; else throw new Error("Wrong 'kanji' length (" + kanji.length + "). kanji='"+ kanji +"'"); } /* * Init Functions * Set up the hooks needed. */ function scriptEventFired(node) { try { scriptLog("Event fired!"); addElement(node); } catch(err) { logError(err); } } function scriptInit() { // Add global CSS styles GM_addStyle('.WKPSC-more-information-button { color: #888888; cursor: pointer; text-align: center; background-image: url("/assets/default-v2/top-inset-shadow-290f5bd0a4f35ec34dd42c6c1f56a2f3.png"); background-position: center top; background-repeat: no-repeat; margin-top: 15px;} }'); GM_addStyle('.WKPSC-more-information-hidden { display:block; visibility:hidden; height:0; }'); scriptLog("loaded"); // Set up hooks try { if (/\/kanji\/./.test(document.URL)) /* Kanji Pages */ { scriptLog("Kanji Page"); curPage = PageEnum.kanji; kanjiInfo_init(); addElement(); } else if (/\/review/.test(document.URL)) /* Reviews Pages */ { scriptLog("Reviews page"); curPage = PageEnum.reviews; reviews_init(); waitForKeyElements("section[id=item-info-reading-mnemonic]", scriptEventFired, false); } else if (/\/lesson/.test(document.URL)) /* Lessons Pages */ { scriptLog("Lessons page"); curPage = PageEnum.lessons; lessons_init(); waitForKeyElements("li.active", scriptEventFired, false); } } catch(err) { logError(err); } } /* * Helper Functions/Variables */ //$ = unsafeWindow.$; function isEmpty(value){ return (typeof value === "undefined" || value === null); } if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; } /* * Error handling * Can use 'error.stack', not cross-browser (though it should work on Firefox and Chrome) */ function logError(error) { var stackMessage = ""; if ("stack" in error) stackMessage = "\n\tStack: " + error.stack; console.error(scriptShortName + " Error: " + error.name + "\n\tMessage: " + error.message + stackMessage); } /* * Code by BrockA, thanks! * Taken from https://gist.github.com/BrockA/2625891 */ /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts, that detects and handles AJAXed content. Usage example: waitForKeyElements ( "div.comments" , commentCallbackFunction ); //--- Page-specific function to do what we want when the node is found. function commentCallbackFunction (jNode) { jNode.text ("This comment changed by waitForKeyElements()."); } IMPORTANT: This function requires your script to have loaded jQuery. */ function waitForKeyElements ( selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents() .find(selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each( function() { var jThis = $(this); var alreadyFound = jThis.data ('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction(jThis); if (cancelFound) btargetsFound = false; else jThis.data('alreadyFound', true); } }); } else btargetsFound = false; //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace (/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval (timeControl); delete controlObj[controlKey]; } else { //--- Set a timer, if needed. if (!timeControl) { timeControl = setInterval( function() { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector ); }, 300 ); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } /* End of Code by BrockA */ /* * Start the script */ scriptInit();