您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows how many Kanji, Vocabulary, and/or radical items you've learned in the dashboard. See SETTINGS.
// ==UserScript== // @name Wanikani Number of Learned Kanji, Vocab // @namespace https://www.wanikani.com // @description Shows how many Kanji, Vocabulary, and/or radical items you've learned in the dashboard. See SETTINGS. // @author Saimin // @version 0.8.3 // @include /^https://(www|preview).wanikani.com/(dashboard)?$/ // @grant none // ==/UserScript== // Introduction to this script, explanation of settings, leave feedback: // https://community.wanikani.com/t/userscript-number-of-learned-kanji-vocabulary/41042 // some code adapted with thanks from following scripts (which this is compatible with): // Progress Percentages by Kumirei // https://community.wanikani.com/t/userscript-progress-percentages/35080 // SRS and Leech breakdown script by seanblue. // https://community.wanikani.com/t/userscript-wanikani-dashboard-srs-and-leech-breakdown/32756 (function() { 'use strict'; // --- don't change this (see Settings below) --- // const sectionsToPlaceAround = { reviewStatus: "review-status", srsProgress: "wk-panel wk-panel--level-progress", levelProgression: "progression", } const placements = { before: 0, after: 1 } // --- don't change above --- // // --- SETTINGS (change things here) --- // let placeBeforeOrAfterSection = placements.after; // you can change the "before" to "after" to place the section after the section you specify below let sectionToPlaceAround = sectionsToPlaceAround.srsProgress; // you can change the "srsProgress" to e.g. "progression" (see above) to place this section before/after the progression section let srsStageLearned = 5; // 5: kanji counts as learned when it's Guru+. To set it to apprentice+, set this to 1. burned is 9. let srsStageLearnedDescription = "(Guru+)"; let showKanjiLearned = 1; // set to 0 to not show number of kanji learned let showVocabLearned = 1; // set to 0 to not show number of vocabulary items learned let showRadicalsLearned = 1; // set to 0 to not show number of radicals learned let learnedPrefix = "Learned"; // you can change this to any text. To remove this text completely, set it to "". // --- SETTINGS end --- // const config = { wk_items: { options: { assignments: true } } }; if (!window.wkof) { let response = confirm('WK Number of Learned Items script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.'); if (response) { window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'; } return; } let style = `<style id="number-of-learned-items"> .number-of-learned-items { //position: relative; margin: 5px 0 5px; //grid-column: 1/span 6; //grid-row: / 5; } .number-of-learned-items > div { border-radius: 3px; background: #F4F4F4 !important; padding: 5px 0px; } .noli-item { display: inline-block; //width: 40%; text-align: left; } .noli-first-item { margin-left: 20px; } .noli span { display: inline; font-size: 14px; border-radius: 5px; } .number-of-learned-items .item-description { //font-weight: bold; } .number-of-learned-items .items-learned-number { font-weight: bold; } .dashboard section.srs-progress span { margin-bottom: 0.0em; } </style>` const head = document.head; head.insertAdjacentHTML( 'beforeend', style); if (is_dark_theme()) head.insertAdjacentHTML( 'beforeend', '<style id="number-of-learned-items_dark">'+ '.number-of-learned-items {'+ ' box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 2px 2px 2px rgba(0, 0, 0, 0.7);'+ '}'+ '.number-of-learned-items > div {'+ ' background: #232629 !important;'+ '}'+ '.noli-item {'+ ' color: rgb(240,240,240);'+ '}'+ '</style>'); wkof.include('ItemData'); wkof.ready('ItemData').then(getItems).then(mapItemsToSrs).then(addNumberOfLearnedItemsSection); function getItems(items) { return wkof.ItemData.get_items(config); //return wkof.ItemData.get_items(config).then(filterToActiveAssignments); } function filterToActiveAssignments(items) { return items.filter(itemIsActiveAssignment); } function itemIsActiveAssignment(item) { let assignments = item.assignments; if (assignments === undefined) { return false; } let srsStage = getSrsStage(assignments); return srsStage >= 1 && srsStage <= 9; } function getSrsStage(assignments) { if (!assignments) { return 0; // not yet learned } return assignments.srs_stage; } function mapItemsToSrs(items) { let itemsBySrs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].reduce((result, srs) => { // stage 0 = not yet learned result[srs] = { kanji: 0, vocab: 0, radicals: 0 }; return result; }, {}); // separation by srs stage may be necessary in future if we want to show kanji learned per SRS stage etc. itemsBySrs.kanjiLearned = 0; itemsBySrs.radicalsLearned = 0; itemsBySrs.vocabLearned = 0; itemsBySrs.totalKanji = 0; itemsBySrs.totalRadicals = 0; itemsBySrs.totalVocab = 0; items.forEach(function(item) { let srsStage = getSrsStage(item.assignments); if (showKanjiLearned && item.object == 'kanji') { itemsBySrs[srsStage].kanji++; itemsBySrs.totalKanji++; if (srsStage >= srsStageLearned) { itemsBySrs.kanjiLearned++; } } else if (showVocabLearned && item.object == 'vocabulary') { itemsBySrs[srsStage].vocab++; itemsBySrs.totalVocab++; if (srsStage >= srsStageLearned) { itemsBySrs.vocabLearned++; } } else if (showRadicalsLearned && item.object == 'radical') { itemsBySrs[srsStage].radicals++; itemsBySrs.totalRadicals++; if (srsStage >= srsStageLearned) { itemsBySrs.radicalsLearned++; } } }); return itemsBySrs; } /*function addNumberOfLearnedItemsSection(itemsBySrs) { /*let itemInfo = { kanjiLearned = 0, vocabLearned = 0, radicalsLearned = 0, totalKanji = 0, totalVocab = 0, totalRadicals = 0 } let kanjiLearned = 0; let vocabLearned = 0; let radicalsLearned = 0; let totalKanji = 0; let totalVocab = 0; let totalRadicals = 0; for (let i=1; i<=9; i++) { // default: i=5 (guru) itemInfo.totalKanji += itemsBySrs[i].kanji; itemInfo.totalVocab += itemsBySrs[i].vocab; itemInfo.totalRadicals += itemsBySrs[i].radicals; } if (i >= srsStageLearned) { itemInfo.kanjiLearned += itemsBySrs[i].kanji; itemInfo.vocabLearned += itemsBySrs[i].vocab; itemInfo.radicalsLearned += itemsBySrs[i].radicals; } return addKanjiAndVocabSection(itemInfo); }*/ function addNumberOfLearnedItemsSection(itemsBySrs) { if (learnedPrefix != "") { learnedPrefix += " "; } let kanjiLearnedDescription = "Kanji"; let vocabLearnedDescription = "Vocab items"; let radicalsLearnedDescription = "Radicals"; let kanjiLearnedHoverText = `${itemsBySrs.kanjiLearned}/${itemsBySrs.totalKanji} WK-Kanji learned`; let vocabLearnedHoverText = `${itemsBySrs.vocabLearned}/${itemsBySrs.totalVocab} WK-Vocab learned`; let radicalsLearnedHoverText = `${itemsBySrs.radicalsLearned}/${itemsBySrs.totalRadicals} WK-Radicals learned`; // Add elements var section = document.createElement('section'); section.className = 'number-of-learned-items'; // number of learned items var list = document.createElement('div'); list.insertAdjacentHTML( 'beforeend', '<div class="noli-item" id="">'+ '<span class="noli-item noli-first-item">'+learnedPrefix+'</span></div>'); if (showKanjiLearned) { const separatorSuffix = (showVocabLearned || showRadicalsLearned) ? ", " : ""; list.insertAdjacentHTML( 'beforeend', '<div class="noli-item" id="" title="'+kanjiLearnedHoverText+'">'+ '<span class="items-learned-number">'+itemsBySrs.kanjiLearned+'</span>'+ '<span class="item-description">'+' '+kanjiLearnedDescription+separatorSuffix+'</span></div>'); } if (showVocabLearned) { const separatorSuffix = (showRadicalsLearned) ? ", " : ""; list.insertAdjacentHTML( 'beforeend', '<div class="noli-item" id="" title="'+vocabLearnedHoverText+'">'+ '<span class="items-learned-number">'+itemsBySrs.vocabLearned+'</span>'+ '<span class="item-description">'+' '+vocabLearnedDescription+separatorSuffix+'</span></div>'); } if (showRadicalsLearned) { list.insertAdjacentHTML( 'beforeend', '<div class="noli-item" id="" title="'+radicalsLearnedHoverText+'">'+ '<span class="items-learned-number">'+itemsBySrs.radicalsLearned+'</span>'+ '<span class="item-description">'+' '+radicalsLearnedDescription+'</span></div>'); } if (srsStageLearnedDescription != "" && srsStageLearnedDescription != null) { list.insertAdjacentHTML( 'beforeend', '<div class="noli-item" id="">'+ '<span class="noli-item">'+' '+srsStageLearnedDescription+'</span></div>'); } section.appendChild(list); const sectionToPlaceAroundElement = document.getElementsByClassName(sectionToPlaceAround)[0]; if (placeBeforeOrAfterSection == placements.before) { sectionToPlaceAroundElement.before(section); } else { sectionToPlaceAroundElement.after(section); } return section; } // Handy little function that rfindley wrote. Checks whether the theme is dark. Fixed to not use jquery. function is_dark_theme() { // Grab the <html> background color, average the RGB. If less than 50% bright, it's dark theme. const style = window.getComputedStyle(document.body); return style.getPropertyValue('background-color').match(/\((.*)\)/)[1].split(',').slice(0,3).map(str => Number(str)).reduce((a, i) => a+i)/(255*3) < 0.5; } })();