您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improves the levels overview popup with progress indications
当前为
// ==UserScript== // @name Wanikani Levels Overview Plus // @namespace Mercieral // @description Improves the levels overview popup with progress indications // @include https://www.wanikani.com/* // @version 2.0.0 // @run-at document-end // @grant none // ==/UserScript== /* global $ */ (function() { // Reqire the WK open resource if (!window.wkof) { alert('"Wanikani Levels Overview Plus" script requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.'); window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'; return; } // Initialize the level stage data array const levelCounts = []; for (let i = 0; i < 61; i++) { levelCounts.push({ locked: 0, apprentice: 0, guru: 0, master: 0, enlighten: 0, burn: 0, nextReviewItem: null }); } window.wkof.include('ItemData'); window.wkof.ready('document,ItemData').then(startup).catch(loadError); /** * Parse the data once the document and WKOF data is ready */ function startup () { initCSS(); if (window.wkof.ItemData != null) { const config = { wk_items: { options: {assignments: true} } }; window.wkof.ItemData.get_items(config).then(function (processItems) { if (processItems != null) { for (let i = 0, itemsLen = processItems.length; i < itemsLen; i++) { const item = processItems[i]; if (item != null) { } const srsLevel = item && item.assignments && item.assignments.srs_stage; const level = item && item.data && item.data.level; const levelStageCounts = levelCounts[level]; if (levelStageCounts == null) { // skip this item... continue; } // Incremement the appropriate level's stage counter for this item if (srsLevel == null || srsLevel === 0) { levelStageCounts.locked++; } else if (srsLevel < 5) { levelStageCounts.apprentice++; } else if (srsLevel < 7) { levelStageCounts.guru++; } else if (srsLevel === 7) { levelStageCounts.master++; } else if (srsLevel === 8) { levelStageCounts.enlighten++; } else if (srsLevel === 9) { levelStageCounts.burn++; } let nextReviewDate = item && item.assignments && item.assignments.available_at && new Date(item.assignments.available_at); if (nextReviewDate != null && (levelStageCounts.nextReviewItem == null || nextReviewDate < levelStageCounts.nextReviewItem.reviewDate)) { levelStageCounts.nextReviewItem = { reviewDate: nextReviewDate, characters: item.data.characters, type: item.object, } } } finishUI(); } else { loadError(); } }).catch(loadError); } else { loadError(); } }; /** * Create the UI once the data has been parsed into the data array */ function finishUI () { // Get the HTML square elements for each level in the popout const levelBlocks = $('.sitemap__expandable-chunk--levels > .sitemap__grouped-pages > ol > li > a'); for (let levelBlock of levelBlocks) { levelBlock = $(levelBlock); levelBlock.addClass('level-block'); const originalLevelText = levelBlock.text(); const levelStageCounts = levelCounts[Number(originalLevelText)]; const levelTotal = levelStageCounts.locked + levelStageCounts.apprentice + levelStageCounts.guru + levelStageCounts.master + levelStageCounts.enlighten + levelStageCounts.burn; // Create the overlay elements const levelText = `<span class="level-block-text">${originalLevelText}</span>`; const lockedDiv = `<div class="level-block-item level-block-locked" style="width:${levelStageCounts.locked/levelTotal*100}%"></div>`; const apprenticeDiv = `<div class="level-block-item level-block-apprentice" style="width:${levelStageCounts.apprentice/levelTotal*100}%"></div>`; const guruDiv = `<div class="level-block-item level-block-guru" style="width:${levelStageCounts.guru/levelTotal*100}%"></div>`; const masterDiv = `<div class="level-block-item level-block-master" style="width:${levelStageCounts.master/levelTotal*100}%"></div>`; const enlightenedDiv = `<div class="level-block-item level-block-enlightened" style="width:${levelStageCounts.enlighten/levelTotal*100}%"></div>`; const burnDiv = `<div class="level-block-item level-block-burn" style="width:${levelStageCounts.burn/levelTotal*100}%"></div>`; // Create the tooltip const lockedText = `<div class="locked-tooltip tooltip-section"><p class="tooltip-section-title">Locked</p><p class="tooltip-section-count">${levelStageCounts.locked}</p></div>`; const apprenticeText = `<div class="apprentice-tooltip tooltip-section"><p class="tooltip-section-title">Apprentice</p><p class="tooltip-section-count">${levelStageCounts.apprentice}</p></div>`; const guruText = `<div class="guru-tooltip tooltip-section"><p class="tooltip-section-title">Guru</p><p class="tooltip-section-count">${levelStageCounts.guru}</p></div>`; const masterText = `<div class="master-tooltip tooltip-section"><p class="tooltip-section-title">Master</p><p class="tooltip-section-count">${levelStageCounts.master}</p></div>`; const enlightenedText = `<div class="enlightened-tooltip tooltip-section"><p class="tooltip-section-title">Enlightened</p><p class="tooltip-section-count">${levelStageCounts.enlighten}</p></div>`; const burnText = `<div class="burn-tooltip tooltip-section"><p class="tooltip-section-title">Burn</p><p class="tooltip-section-count">${levelStageCounts.burn}</p></div>`; const totalText = `<div class="total-tooltip tooltip-section"><p class="tooltip-section-title">Total</p><p class="tooltip-section-count">${levelTotal}</p></div>`; let nextReviewDateText = "N/A" let nextReviewChars = "N/A"; const nextReview = levelStageCounts.nextReviewItem; if (nextReview != null) { const nextReviewMinutes = (new Date(nextReview.reviewDate) - new Date()) / (1000 * 60); const nextReviewHours = nextReviewMinutes / 60; if (nextReviewHours <= 0) { nextReviewDateText = "now"; } else if (nextReviewHours <= 1) { const minutes = Math.floor(nextReviewMinutes); nextReviewDateText = minutes + " minute" + (minutes !== 1 ? "s" : ''); } else if (nextReviewHours >= 24) { const days = Math.floor(nextReviewHours / 24); nextReviewDateText = days + " day" + (days !== 1 ? "s" : ''); } else { const hours = Math.floor(nextReviewHours); nextReviewDateText = hours + " hour" + (hours !== 1 ? "s" : ''); } let itemClass = "guru-tooltip"; if (nextReview.type === "radical") { itemClass = "enlightened-tooltip"; } else if (nextReview.type === "kanji") { itemClass = "apprentice-tooltip"; } nextReviewChars = `<span class="${itemClass} next-review-chars">${nextReview.characters}</span>`; } const nextReviewText = `<p class="tooltip-extra-info">Next Review: ${nextReview ? `${nextReviewChars} (${nextReviewDateText})` : "N/A"}</p>` const tooltip = `<div class="level-tooltip"><p class="tooltip-level-text">Level ${originalLevelText}</p>${lockedText}${apprenticeText}${guruText}${masterText}${enlightenedText}${burnText}${totalText}${nextReviewText}</div>`; if (levelStageCounts.burn === levelTotal) { // Fully burned level, add the checkbox to the div levelBlock.addClass('level-block-complete'); } if (levelStageCounts.locked === levelTotal) { // Fully locked level, add the padlock to the div levelBlock.addClass('level-block-full-locked'); } // Rewrite the level block's HTML with the custom elements levelBlock.html(`<div class="level-block-container">${apprenticeDiv}${guruDiv}${masterDiv}${enlightenedDiv}${burnDiv}${lockedDiv}</div>${levelText}${tooltip}`); } // Append a "+" to the level nav text to indicate script success $('.navigation > .sitemap > li:first-child > .sitemap__section-header').children().append('+'); }; /** * Create the CSS style sheet and append it to the document */ function initCSS() { $('head').append(` <style> .level-block { position: relative; height: 46px; border: 1px solid black; } .level-block-text { width: 100%; position: absolute; top: 0; left: 0; text-align: center; line-height: 46px; } .level-tooltip { color: #eeeeee; visibility: hidden; background-color: rgba(0,0,0,0.8); text-align: center; padding: 0 14px 14px 14px; margin-left: 48px; margin-top: -5px; border-radius: 6px; position: absolute; z-index: 2; width: 190px; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; } .tooltip-level-text { font-size: 20px; margin: 5px 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; } .tooltip-extra-info { font-size: 12px; text-align: left; margin: 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; white-space: normal; text-shadow: none; } .next-review-chars { text-shadow: none; padding: 0 3px; font-size: 14px; } .tooltip-section { clear: both; overflow: auto; padding: 5px 15px; } .tooltip-section-title { float: left; margin: 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; font-size: 13px; font-weight: bold; text-shadow: none; } .tooltip-section-count { float: right; margin: 0; font-weight: bold; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; font-size: 13px; font-weight: bold; text-shadow: none; } .locked-tooltip { background-color: #666; background-image: linear-gradient(to bottom, #666, #444); } .apprentice-tooltip { background-color: #dd0093; background-image: linear-gradient(to bottom, #ff00aa, #b30077); } .guru-tooltip { background-color: #882d9e; background-image: linear-gradient(to bottom, #aa38c7, #662277); } .master-tooltip { background-color: #294ddb; background-image: linear-gradient(to bottom, #516ee1, #2142c4); } .enlightened-tooltip { background-color: #0093dd; background-image: linear-gradient(to bottom, #00aaff, #0077b3); } .burn-tooltip { background-color: #fbc042; background-image: linear-gradient(to bottom, #fbc550, #c88a04); color: #ffffff; } .total-tooltip { background-color: #efefef; background-image: linear-gradient(to bottom, #efefef, #cfcfcf); color: #000000; margin-bottom: 10px; } .level-block:hover .level-tooltip { visibility: visible; } .sitemap__page--current-level > a > span{ line-height: 42px !important; } .sitemap__pages--levels .sitemap__page--current-level a { border: 2px solid black !important; } .sitemap__pages--levels .sitemap__page a:hover { background-color: rgba(255,255,255,0.5); } .sitemap__grouped-pages { overflow: visible !important; } .level-block-container { height: 100%; width: 100%; position: absolute; top: 0; left: 0; overflow: hidden; border-radius: 4px; } .level-block-complete { background-position: center !important; background-repeat: no-repeat !important; background-size: 34px !important; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjM1Mi42MnB4IiBoZWlnaHQ9IjM1Mi42MnB4IiB2aWV3Qm94PSIwIDAgMzUyLjYyIDM1Mi42MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzUyLjYyIDM1Mi42MjsiDQoJIHhtbDpzcGFjZT0icHJlc2VydmUiIGZpbGw9IiMwMDY2MDAiIHN0cm9rZT0iIzAwNjYwMCI+DQo8Zz4NCgk8cGF0aCBkPSJNMzM3LjIyMiwyMi45NTJjLTE1LjkxMi04LjU2OC0zMy42Niw3Ljk1Ni00NC4wNjQsMTcuNzQ4Yy0yMy44NjcsMjMuMjU2LTQ0LjA2Myw1MC4xODQtNjYuNzA4LDc0LjY2NA0KCQljLTI1LjA5MiwyNi45MjgtNDguMzQ4LDUzLjg1Ni03NC4wNTIsODAuMTczYy0xNC42ODgsMTQuNjg4LTMwLjYsMzAuNi00MC4zOTIsNDguOTZjLTIyLjAzMi0yMS40MjEtNDEuMDA0LTQ0LjY3Ny02NS40ODQtNjMuNjQ4DQoJCWMtMTcuNzQ4LTEzLjQ2NC00Ny4xMjQtMjMuMjU2LTQ2LjUxMiw5LjE4YzEuMjI0LDQyLjIyOSwzOC41NTYsODcuNTE3LDY2LjA5NiwxMTYuMjhjMTEuNjI4LDEyLjI0LDI2LjkyOCwyNS4wOTIsNDQuNjc2LDI1LjcwNA0KCQljMjEuNDIsMS4yMjQsNDMuNDUyLTI0LjQ4LDU2LjMwNC0zOC41NTZjMjIuNjQ1LTI0LjQ4LDQxLjAwNS01Mi4wMjEsNjEuODEyLTc3LjExMmMyNi45MjgtMzMuMDQ4LDU0LjQ2OC02NS40ODUsODAuNzg0LTk5LjE0NQ0KCQlDMzI2LjIwNiw5Ni4zOTIsMzc4LjIyNiw0NC45ODMsMzM3LjIyMiwyMi45NTJ6IE0yNi45MzcsMTg3LjU4MWMtMC42MTIsMC0xLjIyNCwwLTIuNDQ4LDAuNjExDQoJCWMtMi40NDgtMC42MTEtNC4yODQtMS4yMjQtNi43MzItMi40NDhsMCwwQzE5LjU5MywxODQuNTIsMjIuNjUzLDE4NS4xMzIsMjYuOTM3LDE4Ny41ODF6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==') !important; } .level-block-full-locked { background-position: center !important; background-repeat: no-repeat !important; background-size: 36px !important; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjM0cHgiIGhlaWdodD0iMzRweCIgdmlld0JveD0iMCAwIDM0IDM0IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAzNCAzNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0cm9rZT0iIzY2NjY2NiIgZmlsbD0iIzY2NjY2NiI+DQo8Zz4NCgk8cGF0aCBkPSJNMjYuODMzLDEzLjMzM1Y5YzAtNC45NjItNC4wMzgtOS05LTloLTEuNjY2Yy00Ljk2MiwwLTksNC4wMzgtOSw5djQuMzMzSDQuMzMzVjM0aDI1LjMzNFYxMy4zMzNIMjYuODMzeiBNMTEuMTY3LDkNCgkJYzAtMi43NTcsMi4yNDMtNSw1LTVoMS42NjZjMi43NTcsMCw1LDIuMjQzLDUsNXY0LjMzM0gxMS4xNjdWOXoiLz4NCjwvZz4NCjwvc3ZnPg0K') !important; } .level-block-item { height: 100%; display: inline-block; } .level-block-locked { background-color: rgba(0, 0, 0, 0.3); } .level-block-apprentice { background-color: rgba(221, 0, 147, 0.4); } .level-block-guru { background-color: rgba(136, 45, 158, 0.4); } .level-block-master { background-color: rgba(41, 77, 219, 0.4); } .level-block-enlightened { background-color: rgba(0, 147, 221, 0.4); } .level-block-burn { background-color: rgba(251, 192, 66, 0.4); } </style>`); } /** * log an error if any part of the wkof data request failed * @param {*} [e] - The error to log if it exists */ function loadError (e) { console.error('Failed to load data from WKOF for "Wanikani Levels Overview Plus"', e); }; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址