您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays how many levels have been completed to a given SRS level. A re-implementation of https://community.wanikani.com/t/userscript-levels-by-srs/37711 using modern js
// ==UserScript== // @name Wanikani: Levels completed per SRS // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description Displays how many levels have been completed to a given SRS level. A re-implementation of https://community.wanikani.com/t/userscript-levels-by-srs/37711 using modern js // @author KaHLK // @match https://www.wanikani.com/dashboard // @match https://www.wanikani.com // @include *preview.wanikani.com* // @grant none // ==/UserScript== // @ts-ignore const SCRIPT_ID = "srs_levels"; const SRS_LEVELS_STYLE = ` .srs-progress li{ position: relative; overflow: hidden; } .${SCRIPT_ID}.levels_cleared{ display: flex; justify-content: center; align-items: center; background: #222; color: #fafafa; height: 2em; width: 100%; position: absolute; bottom: 0; left: 0; } `; (function () { let wkof; if (!window.wkof) { let response = confirm("\"WaniKani: Total Progress\" 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; } else { wkof = window.wkof; } let settings; let data; // Create a template element that will be the base for the output of this script. // This is done because it is faster to clone than it is to create a new element each time. // NOTE: This small performance gain is not important in this case, but the small complexity increase // is not enough of a drawback in this case to not use it. const template = document.createElement("div"); template.classList.add(SCRIPT_ID, "levels_cleared"); let stage_divs = []; // Append a clone of the template element to each element in 'stage_dom_items' for (let item of document.querySelectorAll(".srs-progress li")) { // Increase the bottom padding, to make room for the element const padding_bottom = Number(window.getComputedStyle(item).paddingBottom.slice(0, -2)); item.style.setProperty("padding-bottom", `${padding_bottom + 28}px`); // Clone the template and append it to the list element const node = template.cloneNode(); item.append(node); // Push the element to a list to be able to use it later stage_divs.push(node); } setup(); /** * Load the necessary plugins from wkof, create the settings menu and add a link to the menu. * Fetch all assignments from wanikani from the levels up to and including the current and * only save the total number of items per level and the stage of each them. * Each item is also counted towards all previous stages they've cleared. * * NOTE: Should only be called once, on script start-up. */ async function setup() { wkof.include("Menu,Settings,ItemData"); await wkof.ready("Menu,Settings,ItemData"); // Setup the settings menu settings = prepare_srs_levels_settings_dialog(update); await settings.load({ threshold: 90 }); wkof.Menu.insert_script_link({ name: `${SCRIPT_ID}_settings`, submenu: "Settings", title: "Levels by SRS", on_click: settings.open, }); // Get the assignments starting from level one and up to the current level. const items = await wkof.ItemData.get_items({ wk_items: { options: { assignments: true }, filters: { level: "1..+0" }, }, }); const item_by_level = []; // Sort the items into the level they are from and save only the total number of items // as well as the amount per stage for (let i of items) { let level = i.data.level; let stage = srs_map(i.assignments.srs_stage); // If the entry for the current level is undefined, create a new one and fill the 'per_stage' arary with 0's. if (item_by_level[level] === undefined) { item_by_level[level] = { level: level, total_items: 0, per_stage: Array(stage_divs.length) }; item_by_level[level].per_stage.fill(0); } item_by_level[level].total_items++; if (stage === SRSCombinedStage.OTHER) { continue; } // Increment the value for all stages less than or equal to the current. // Ex: if the stage is 'Master' also increment 'Guru' and 'Apprentice'. for (let idx = stage; idx >= 0; idx--) { item_by_level[level].per_stage[idx]++; } } // Reverse the array, so that the last level is first (This step allows to skip a lot of computation later). data = item_by_level.reverse(); // Remove the empty 0th element that is now at the end. This prevents a undefined check later. data.pop(); update(); } /** * Calculate the highest level cleared for each stage and display it in the output divs ('stage_divs' array). * * NOTE: Should be called once, on script start-up, and every time the settings are saved. */ async function update() { const settings = wkof.settings[SCRIPT_ID]; let threshold = settings.threshold; const max_level_on_stage = Array(stage_divs.length).fill(0); // For every item, check if they (given the threshold) have cleared the stage. for (let level of data) { // Count how many stages where skipped (already set by a later level). let continued = 0; for (let i = 0; i < max_level_on_stage.length; i++) { // Skip this stage if the current level is lower than the one already set if (max_level_on_stage[i] > level.level) { continued++; continue; } // Check if the level has cleared the stage. if (level.per_stage[i] / level.total_items * 100 >= threshold) { max_level_on_stage[i] = level.level; } } // If all of the stages are already set to a level higher than this, // break out of the loop, as the order of the levels are descending, // none of the remaining levels will be needed to be checked. // Ex: Current level = 5. max_level_on_stage = [10, 9, 9, 7, 6]. if (continued >= max_level_on_stage.length) { break; } } // Output the highest level cleared for each stage max_level_on_stage.forEach((val, i) => { stage_divs[i].innerText = `Level: ${val}`; }); } const style = document.createElement("style"); style.id = `${SCRIPT_ID}_style`; style.innerHTML = SRS_LEVELS_STYLE; document.head.append(style); })(); /** * Create the settings dialog. * * NOTE: This function should only be called once and the result be saved. * @param update The function to call when the "Save"-button is pressed * @returns The created WKOFDialog */ function prepare_srs_levels_settings_dialog(update) { return new window.wkof.Settings({ script_id: SCRIPT_ID, title: "Levels by srs", on_save: update, content: { threshold: { type: "number", label: "Threshold", hover_tip: "Percentage of level needed completed before SRS stage is considered completed.", default: 90 } } }); } var SRSIndividualStage; (function (SRSIndividualStage) { SRSIndividualStage[SRSIndividualStage["LOCKED"] = -1] = "LOCKED"; SRSIndividualStage[SRSIndividualStage["LESSON"] = 0] = "LESSON"; SRSIndividualStage[SRSIndividualStage["APPRENTICE_1"] = 1] = "APPRENTICE_1"; SRSIndividualStage[SRSIndividualStage["APPRENTICE_2"] = 2] = "APPRENTICE_2"; SRSIndividualStage[SRSIndividualStage["APPRENTICE_3"] = 3] = "APPRENTICE_3"; SRSIndividualStage[SRSIndividualStage["APPRENTICE_4"] = 4] = "APPRENTICE_4"; SRSIndividualStage[SRSIndividualStage["GURU_1"] = 5] = "GURU_1"; SRSIndividualStage[SRSIndividualStage["GURU_2"] = 6] = "GURU_2"; SRSIndividualStage[SRSIndividualStage["MASTER"] = 7] = "MASTER"; SRSIndividualStage[SRSIndividualStage["ENLIGHTENED"] = 8] = "ENLIGHTENED"; SRSIndividualStage[SRSIndividualStage["BURN"] = 9] = "BURN"; })(SRSIndividualStage || (SRSIndividualStage = {})); var SRSCombinedStage; (function (SRSCombinedStage) { SRSCombinedStage[SRSCombinedStage["APPRENTICE"] = 0] = "APPRENTICE"; SRSCombinedStage[SRSCombinedStage["GURU"] = 1] = "GURU"; SRSCombinedStage[SRSCombinedStage["MASTER"] = 2] = "MASTER"; SRSCombinedStage[SRSCombinedStage["ENLIGHTENED"] = 3] = "ENLIGHTENED"; SRSCombinedStage[SRSCombinedStage["BURN"] = 4] = "BURN"; SRSCombinedStage[SRSCombinedStage["OTHER"] = 5] = "OTHER"; })(SRSCombinedStage || (SRSCombinedStage = {})); /** * Maps an individual stage to a combined stage. * Ex: Apprentice 2 -> Apprentice * * @param srs The individual stage to map * @returns The combined stage that the 'srs' individual stage belongs to */ function srs_map(srs) { switch (srs) { case SRSIndividualStage.APPRENTICE_1: case SRSIndividualStage.APPRENTICE_2: case SRSIndividualStage.APPRENTICE_3: case SRSIndividualStage.APPRENTICE_4: return SRSCombinedStage.APPRENTICE; case SRSIndividualStage.GURU_1: case SRSIndividualStage.GURU_2: return SRSCombinedStage.GURU; case SRSIndividualStage.MASTER: return SRSCombinedStage.MASTER; case SRSIndividualStage.ENLIGHTENED: return SRSCombinedStage.ENLIGHTENED; case SRSIndividualStage.BURN: return SRSCombinedStage.BURN; case SRSIndividualStage.LOCKED: case SRSIndividualStage.LESSON: return SRSCombinedStage.OTHER; } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址