您需要先安装一个扩展,例如 篡改猴、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|preview)\.wanikani\.com\/((?!review|lesson|login).)*$/ // @version 2.1.3 // @run-at document-end // @grant none // ==/UserScript== /* global $ */ (function() { // Require 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,Settings'); window.wkof.ready('document,ItemData,Settings') .then(load_settings) .then(initCSS) .then(sortData) .then(finishUI) .catch(loadError); /** * Loads the saved settings for this script */ function load_settings () { return window.wkof.Settings.load('levelOverviewPlus', { showNextReview: true }) } /** * Parse the data once the document and WKOF data is ready */ function sortData () { if (window.wkof.ItemData == null) { return Promise.reject(); } const config = { wk_items: { options: {assignments: true} } }; return window.wkof.ItemData.get_items(config).then(function (processItems) { if (processItems == null) { return Promise.reject(); } for (let i = 0, itemsLen = processItems.length; i < itemsLen; i++) { const item = processItems[i]; if (item != null) { const srsLevel = item.assignments && item.assignments.srs_stage; const level = item.data && item.data.level; const levelStageCounts = levelCounts[level]; if (levelStageCounts == null) { // skip this item... continue; } // Increment 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++; } // Check if this is the next item for review for its level let nextReviewDate = 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, } if (item.data.characters == null) { levelStageCounts.nextReviewItem.imgUrl = item.data.character_images.filter((img) => { return (img.content_type === 'image/svg+xml' && !img.metadata.inline_styles); })[0].url; levelStageCounts.nextReviewItem.slug = item.data.slug; } } } } return Promise.resolve(); }); } /** * 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) { overwriteLevelBlock(levelBlock); } createSettings(); updateLevelheader(); /** * Overwrite the level block with the custom elements and tooltip */ function overwriteLevelBlock (levelBlock) { 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; let loadImgData; if (nextReview != null) { // Get the time amount until review 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" : ''); } // Get the review item characters let itemClass = "guru-tooltip"; if (nextReview.type === "radical") { itemClass = "enlightened-tooltip"; } else if (nextReview.type === "kanji") { itemClass = "apprentice-tooltip"; } if (nextReview.characters) { nextReviewChars = `<span class="${itemClass} next-review-chars">${nextReview.characters}</span>`; } else if (nextReview.imgUrl && nextReview.slug) { nextReviewChars = `<span class="${itemClass} next-review-chars" id="svg_${nextReview.slug}"></span>`; loadImgData = nextReview; } } const nextReviewText = `<p class="tooltip-extra-info next-review-text">Next: ${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}`); if (loadImgData != null) { wkof.load_file(loadImgData.imgUrl).then((svgData) => { const destSpan = $(`#svg_${loadImgData.slug}`); if (destSpan.length > 0) { destSpan.html(svgData); } }); } } /** * Create the settings button and panel with contents */ function createSettings () { // Create the settings popop const showNextToggle = $(`<input type="checkbox">`); const showNextSetting = $(`<div class="settings-row"><span>Show Next Review</span></div>`); showNextSetting.prepend(showNextToggle); const settingsButton = $(`<div id="settings-button"></div>`); const settingsPanel = $(`<div id="settings-panel"><div id='settings-title'>Levels Overview Plus Settings</div></div>`); settingsPanel.append(showNextSetting); $('.sitemap__grouped-pages').append(settingsButton, settingsPanel); // Handling hiding/showing the settings panel let settingsPanelActive = false; function handleBodyClick (e) { if ($(e.target).closest("#settings-panel").length === 0 && e.target.id !== 'settings-button') { $("#settings-panel").hide(); settingsButton.css("background-color", ""); $("body").off('mouseup', handleBodyClick); settingsPanelActive = false; } } settingsButton.click(() => { settingsPanelActive = !settingsPanelActive; if (settingsPanelActive) { settingsPanel.show(); settingsButton.css("background-color", "#b0b0b0"); $("body").on('mouseup', handleBodyClick); } else { settingsPanel.hide(); settingsButton.css("background-color", ""); $("body").off('mouseup', handleBodyClick); } }); // Handle the "toggle next review" checkbox const nextReviewTexts = $('.next-review-text'); showNextToggle.change(function() { if(this.checked) { nextReviewTexts.show(); } else { nextReviewTexts.hide(); } window.wkof.settings.levelOverviewPlus.showNextReview = this.checked; window.wkof.Settings.save('levelOverviewPlus'); }); showNextToggle.prop("checked", window.wkof.settings.levelOverviewPlus.showNextReview).change(); showNextSetting.click(() => { showNextToggle.prop("checked", !window.wkof.settings.levelOverviewPlus.showNextReview).change(); }); } /** * Append a "+" to the level nav text to indicate script success */ function updateLevelheader () { $('.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 !important; } .level-block-text { width: 100%; position: absolute; top: 0; left: 0; text-align: center; line-height: 46px; } #settings-button { width: 28px; height: 28px; position: absolute; top: 8px; right: 8px; cursor: pointer; border-radius: 5px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABaklEQVR4Xu2aTUoDQRCFv2yNP9cInsCtbkWCl9EraE4jwWXIVo8gXiIkxL0URNBON3Yyq6p6s00z9Hv1VVdPpUYkf0bJ9SMDREByB5QCyQHQIagUUAokd0Ap0ABgAjwBN8CZc0i+gCXwAHyUWmoEmPh34MK58HL7a+CqNKFmwAswDSb+R8681FYzYBMA+1b8tqW2mgG2aByUgC4DDJO7oAZYet//1pbpEFztDsHP/wyw360SPO/K4KlzGgz7BfAI/BFvunQRch7dwdsXAYMtdP4CEeA8gIO3LwIGW+j8BS0CLoEZcA2cONd4cD/AxL8B586FH90PeAVug4k/qB9gyHjHXv2AhgPqB6gfUHwSt8qg+gFBq8CeLF2Fs0S6pVMEiIDkDigFkgPQ/F9A/QD1A2LmRtd8gPoBmg/QfEDIISnNB2g+oFLZdBWOWe77VYmAfq9irhQBMePar0oE9HsVc6UIiBnXflXpCfgGC8dCQbbkoGgAAAAASUVORK5CYII='); background-position: center !important; background-repeat: no-repeat !important; background-size: 18px !important; } #settings-panel { display: none; position: absolute; padding: 10px 15px; background-color: #2a2a2a; border-radius: 4px; top: 40px; right: 8px; user-select: none; } #settings-title { font-weight: bold; font-size: 14px; } .settings-row { padding: 8px 0 0 10px; font-size: 12px; line-height: 20px; cursor: pointer; } .settings-row input { margin: 0 3px 0 0; width: 20px; height: 20px; } .level-tooltip { color: #eeeeee; visibility: hidden; background-color: rgba(0,0,0,0.8); text-align: center; padding: 0 10px 2px 10px; margin-left: 52px; margin-top: -5px; border-radius: 6px; position: absolute; z-index: 2; width: 150px; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; pointer-events: none; } .tooltip-level-text { font-size: 16px; 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; text-indent: -20px; padding-left: 20px; padding-bottom: 6px; } .next-review-chars { text-shadow: none; padding: 0 3px; font-size: 14px; } svg.radical { fill:none; stroke:#fff; stroke-linecap:square; stroke-miterlimit:2; stroke-width:68px; height: 15px; vertical-align: top; margin-top: 1.5px; } .tooltip-section { clear: both; overflow: auto; padding: 3px 10px; } .tooltip-section-title { float: left; margin: 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; font-size: 11px; 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: 11px; 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>`); return Promise.resolve(); } /** * 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或关注我们的公众号极客氢云获取最新地址