您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds markup & tooltips for item types, critical reviews, burn reviews to the forecast and review buttons
// ==UserScript== // @name Wanikani Forecast Details // @namespace https://www.wanikani.com // @author kernfel // @description Adds markup & tooltips for item types, critical reviews, burn reviews to the forecast and review buttons // @version 1.5.5 // @include /^https://(www|preview).wanikani.com/(dashboard)?$/ // @copyright 2020+, Felix Kern // @license MIT; http://opensource.org/licenses/MIT // @run-at document-end // @grant none // ==/UserScript== (function() { /* global $, wkof */ //=================================================================== // Initialization of the Wanikani Open Framework. //------------------------------------------------------------------- var script_name = 'Forecast Details'; if (!window.wkof) { if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?')) { window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'; } return; } wkof.include('ItemData,Menu,Settings'); wkof.ready('document,ItemData,Settings') .then(install_menu) .then(load_settings) .then(startup); //======================================================================== // Settings //------------------------------------------------------------------- var settings; function load_settings() { var defaults = { crit_highlight: true, crit_highlight_now: true, add_crit_icon: true, add_crit_icon_now: true, crit_icon: '🔺', burn_highlight: true, burn_highlight_now: true, add_burn_icon: true, add_burn_icon_now: true, burn_icon: '🔥', bar_colours: 'type' }; wkof.Settings.load('forecast_details', defaults).then(function(){ settings = wkof.settings.forecast_details }); } //======================================================================== // Startup //------------------------------------------------------------------- function startup() { install_css(); wkof.ItemData.get_items({ wk_items:{ options:{ assignments:true }, filters:{ srs: '1,2,3,4,5,6,7,8', level: '1..+0' } } }) .then(process_items); wkof.ItemData.get_items({ wk_items:{ options:{ assignments:true }, filters:{ srs: '1,2,3,4', level: '+0', item_type: 'rad,kan' } } }) .then(process_crits); } //======================================================================== // CSS Styling //------------------------------------------------------------------- var fcr_css = // Bar highlights '.fcr_apprentice { background: #59c27450; }'+ '.fcr_burn { background: #fbc04250; }'+ '.fcr_burn.fcr_apprentice { background: #aac15b50; }'+ // Review buttons '.lessons-and-reviews__button span.fcr_apprentice, .navigation-shortcut a.fcr_apprentice { background: #59c274; text-shadow: none; color: white; }'+ '.lessons-and-reviews__button span.fcr_burn, .navigation-shortcut a.fcr_burn { background: #fbc042; text-shadow: none; color: white; }'+ '.lessons-and-reviews__button span.fcr_apprentice.fcr_burn, .navigation-shortcut a.fcr_apprentice.fcr_burn { background: #aac15b; }'+ // Item type bar split '.fcr_radical { background: #00AAFF; }'+ '.fcr_kanji { background: #FF00AA; }'+ '.fcr_vocab { background: #AA00FF; }'+ // SRS level bar split '.fcr_bar_apprentice { background: #dd0093; }'+ '.fcr_bar_guru { background: #882d9e; }'+ '.fcr_bar_master { background: #294ddb; }'+ '.fcr_bar_enlightened { background: #0093dd; }'+ // Crit/burn icons '.fcr_icon { float: right; padding-right: 1px; }'+ // Layout details '.fcr_split_bar { display: inline-block; }'+ '.review-forecast__bar { min-width: 6px; }'+ '.fcr_lineheight_fix { line-height: 0; }'+ '.review-forecast__day.mb-3 { padding-bottom: 12px; }'+ '.review-forecast__day.mb-3.is-collapsed { padding-bottom: 0; }'+ '.review-forecast__hour.pb-2 { padding-bottom: 0 !important; }'+ '.review-forecast__hour:last-child>* { padding-bottom: 0 !important; }' ; //======================================================================== // Install stylesheet. //------------------------------------------------------------------- function install_css() { $('head').append('<style>'+fcr_css+'</style>'); } //======================================================================== // Add detailed information and burns to forecast bars & review buttons //------------------------------------------------------------------- function process_items(data) { var now = new Date(), rtime, counts = {}, stage; // Count typed reviews in each hour for ( var idx in data ) { if ( Date.parse(data[idx].assignments.available_at) - 7*24*3600*1000 < now ) { rtime = data[idx].assignments.available_at.split('.')[0]; if ( Date.parse(data[idx].assignments.available_at) < now ) { rtime = 'now'; } if ( ! (rtime in counts) ) { counts[rtime] = { 'radical':0, 'kanji':0, 'vocabulary':0, 'burn': { 'radical':0, 'kanji':0, 'vocabulary':0 }, 'hasBurn': false, 4: 0, // Apprentice 5: 0, // Guru 7: 0, // Master 8: 0 // Enlightened }; } counts[rtime][data[idx].object]++; // counts by type stage = data[idx].assignments.srs_stage; if ( stage < 5 ) stage = 4; if ( stage == 6 ) stage = 5; counts[rtime][stage]++; // counts by srs stage if ( data[idx].assignments.srs_stage == 8 ) { counts[rtime].burn[data[idx].object]++; counts[rtime].hasBurn = true; } } } // Split forecast bars into types, add informative titles, and add burn markup var bar, tr, review_btn, w0, n, title; for ( rtime in counts ) { // Locate the review bar bar = $('section.forecast tr.review-forecast__hour time[datetime="' + rtime + 'Z"]') .parents('th').first().siblings('td').first().children('span').first(); if ( !bar.length ) { continue; } tr = bar.parents('tr').first(); // Split the bar into typed segments if ( settings.bar_colours == 'type' ) { split_bar(bar, counts[rtime], barsplit_type); } else if ( settings.bar_colours == 'srs' ) { split_bar(bar, counts[rtime], barsplit_srs); } // Add tooltips and burn highlights tr.attr('title', setTitle(counts[rtime], 'Total reviews')) .attr('title', setTitleByLevel(counts[rtime])); if ( counts[rtime].hasBurn ) { tr.attr('title', setTitle(counts[rtime].burn, 'Burn')); if ( settings.burn_highlight ) { tr.addClass('fcr_burn'); } if ( settings.add_burn_icon ) { tr.children('th').append('<span class="fcr_icon fcr_icon_burn">' + settings.burn_icon + '</span>'); } } } // Add currently available info & burns the review buttons if ( 'now' in counts ) { review_btn = $('a.lessons-and-reviews__reviews-button, li.navigation-shortcut--reviews') .attr('title', setTitle(counts.now, 'Total reviews')) .attr('title', setTitleByLevel(counts.now)); if ( counts.now.hasBurn ) { review_btn.attr('title', setTitle(counts.now.burn, 'Burn')); if ( settings.burn_highlight_now ) { review_btn.children().addClass('fcr_burn'); } if ( settings.add_burn_icon_now ) { review_btn.children().append(settings.burn_icon); } } } } //======================================================================== // Add level-critical information to forecast bars & review buttons //------------------------------------------------------------------- function process_crits(data) { var now = new Date(), rtime, counts = {}; // Count typed reviews in each hour for ( var idx in data ) { if ( data[idx].assignments.passed_at === null && Date.parse(data[idx].assignments.available_at) - 7*24*3600*1000 < now ) { rtime = data[idx].assignments.available_at.split('.')[0]; if ( Date.parse(data[idx].assignments.available_at) < now ) { rtime = 'now'; } if ( ! (rtime in counts) ) { counts[rtime] = { 'radical':0, 'kanji':0 }; } counts[rtime][data[idx].object]++; // crit counts by type } } // Add crit markup to forecast var tr, review_btn, w0, n, title; for ( rtime in counts ) { // Locate the review bar tr = $('section.forecast tr.review-forecast__hour time[datetime="' + rtime + 'Z"]') .parents('tr').first(); if ( !tr.length ) { continue; } // Add tooltips and markup tr.attr('title', setTitle(counts[rtime], 'Critical')); if ( settings.crit_highlight ) { tr.addClass('fcr_apprentice'); } if ( settings.add_crit_icon ) { tr.children('th').append('<span class="fcr_icon fcr_icon_critical">' + settings.crit_icon + '</span>'); } } // Add currently available crits to review buttons if ( 'now' in counts ) { review_btn = $('a.lessons-and-reviews__reviews-button, li.navigation-shortcut--reviews') .attr('title', setTitle(counts.now, 'Critical')).children(); if ( settings.crit_highlight_now ) { review_btn.addClass('fcr_apprentice'); } if ( settings.add_crit_icon_now ) { review_btn.append(settings.crit_icon); } } } //======================================================================== // Helper functions //------------------------------------------------------------------- var setTitle = function(countObj, name){ return function(idx, title){ title = (title ? title + '\n' : '') + name + ': ' + countObj.radical + ' radicals, ' + countObj.kanji + ' kanji'; if ( countObj.vocabulary != undefined ) { title += ', ' + countObj.vocabulary + ' vocabulary'; } return title; } } var setTitleByLevel = function(countObj){ return function(idx, title){ title = (title ? title + '\n' : '') + countObj[4] + ' apprentice, ' + countObj[5] + ' guru, ' + countObj[7] + ' master, ' + countObj[8] + ' enlightened'; return title; } } function split_bar(bar, counts, config) { var n = 0, w0, c; for ( c in config ) { n += counts[c] } bar.removeClass('rounded-r-lg').addClass('fcr_split_bar'); w0 = parseFloat(bar.attr('style').match("width: ([0-9.]+)%")[1]); for ( c in config ) { if ( counts[c] ) { bar.clone().addClass(config[c]).css('width', counts[c] * w0 / n + '%').appendTo(bar.parent()); } } bar.siblings().last().addClass('rounded-r-lg').parent().addClass('fcr_lineheight_fix'); if ( w0 > 95 ) { bar.siblings().css('min-width', 0); } bar.remove(); } var barsplit_type = { radical: 'fcr_radical', kanji: 'fcr_kanji', vocabulary: 'fcr_vocab' }; var barsplit_srs = { 4: 'fcr_bar_apprentice', 5: 'fcr_bar_guru', 7: 'fcr_bar_master', 8: 'fcr_bar_enlightened' }; //======================================================================== // Menu //------------------------------------------------------------------- function install_menu() { wkof.Menu.insert_script_link({ name: 'forecast_details', title: 'Forecast Details', submenu: 'Settings', on_click: open_settings }); } function open_settings() { var config = { script_id: 'forecast_details', title: 'Forecast Details settings', content: { tabset: { type: 'tabset', content: { crits: { type: 'page', label: 'Level-critical reviews', content: { crit_icon: { type: 'text', label: 'Icon (Default: 🔺)', }, sec_forecast: { type: 'section', label: 'Forecast' }, crit_highlight: { type: 'checkbox', label: 'Highlight bars', }, add_crit_icon: { type: 'checkbox', label: 'Add icons' }, sec_now: { type: 'section', label: 'Review buttons (currently available items)' }, crit_highlight_now: { type: 'checkbox', label: 'Add highlight' }, add_crit_icon_now: { type: 'checkbox', label: 'Add icon' } } }, burns: { type: 'page', label: 'Burn reviews', content: { burn_icon: { type: 'text', label: 'Icon (Default: 🔥)', }, sec_forecast: { type: 'section', label: 'Forecast' }, burn_highlight: { type: 'checkbox', label: 'Highlight bars', }, add_burn_icon: { type: 'checkbox', label: 'Add icons' }, sec_now: { type: 'section', label: 'Review buttons (currently available items)' }, burn_highlight_now: { type: 'checkbox', label: 'Add highlight' }, add_burn_icon_now: { type: 'checkbox', label: 'Add icon' } } }, misc: { type: 'page', label: 'Other forecast details', content: { bar_colours: { type: 'dropdown', label: 'Colour bars by', content: { none: 'None', type: 'Item type', srs: 'SRS stage', } } } } } }, divider: { type: 'divider' }, note: { type: 'section', label: 'Note, changes come into effect when the page is reloaded.', } } }; var dialog = new wkof.Settings(config); dialog.open(); } })();