您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays all your apprentice items on the dashboard
// ==UserScript== // @name Wanikani: Dashboard Apprentice // @namespace http://tampermonkey.net/ // @version 1.2.4 // @description Displays all your apprentice items on the dashboard // @author Kumirei // @match https://www.wanikani.com // @match https://www.wanikani.com/dashboard* // @match https://preview.wanikani.com // @match https://preview.wanikani.com/dashboard* // @grant none // ==/UserScript== /*jshint esversion: 8 */ ;(function (wkof, $) { // Make sure WKOF is installed let script_id = 'dashboard_apprentice' if (!wkof) { var script_name = 'Wanikani: Dashboard Apprentice' var response = confirm( script_name + ' 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 } // Ready to go else { wkof.include('Menu,Settings,ItemData') wkof.ready('Menu,Settings,ItemData') .then(load_settings) .then(install_menu) .then(add_css) .then(fetch_items) .then(display) } function install_menu() { let config = { name: script_id, submenu: 'Settings', title: 'Dashboard Apprentice', on_click: open_settings, } wkof.Menu.insert_script_link(config) } function open_settings() { var config = { script_id: script_id, title: 'Dashboard Apprentice', content: { theme: { type: 'dropdown', label: 'Theme', default: 0, hover_tip: 'Changes the colors of the items', content: { 0: 'Default', 1: 'Breeze Dark' }, }, srs_start: { type: 'number', label: 'First SRS stage', default: 1, hover_tip: 'First SRS stage to display.\n-1: Locked items\n0: Items in your lessons\n1-4: Apprentice\n5-6: Guru\n7: Master\n8: Enlightened\n9: Burned', }, srs_end: { type: 'number', label: 'Last SRS stage', default: 4, hover_tip: 'Last SRS stage to display.\n-1: Locked items\n0: Items in your lessons\n1-4: Apprentice\n5-6: Guru\n7: Master\n8: Enlightened\n9: Burned', }, types: { type: 'list', label: 'Item types', multi: true, hover_tip: 'Which items you want to display', default: { rad: true, kan: true, voc: true }, content: { rad: 'Radicals', kan: 'Kanji', voc: 'Vocabulary', kana_voc: 'Kana Vocabulary' }, }, }, } let dialog = new wkof.Settings(config) dialog.open() } function load_settings() { let defaults = { theme: 0, srs_start: 1, srs_end: 4, types: { rad: true, kan: true, voc: true, kana_voc: true }, } return wkof.Settings.load(script_id, defaults) } // Fetches the items async function fetch_items() { let types = Object.entries(wkof.settings[script_id].types) .filter((a) => a[1]) .map((a) => a[0]) return wkof.ItemData.get_index( await wkof.ItemData.get_items({ wk_items: { options: { assignments: true }, filters: { item_type: types } }, }), 'srs_stage', ) } // Puts the information on the dashboard async function display(data) { let names = { '-1': 'Locked', 0: 'Lessons', 1: 'Apprentice 1', 2: 'Apprentice 2', 3: 'Apprentice 3', 4: 'Apprentice 4', 5: 'Guru 1', 6: 'Guru 2', 7: 'Master', 8: 'Enlightened', 9: 'Burned', } var elem = $('<section id="wkda_items"></section>')[0] if (is_dark_theme()) elem.className = 'dark' let settings = wkof.settings[script_id] for (var i = settings.srs_start; i <= settings.srs_end; i++) { if (!data[i]) continue var srs_elem = $('<div class="apprentice_' + i + '"></div>')[0] var title = $('<span>' + names[i] + ' </span>')[0] var items = $('<div class="items"></div>')[0] srs_elem.appendChild(title) srs_elem.appendChild(items) for (var j = 0; j < data[i].length; j++) { var item = data[i][j] var info = { type: item.object, characters: item.data.characters !== null ? item.data.characters : await wkof.load_file( item.data.character_images.find((c) => c.content_type === 'image/svg+xml').url, true, ), meanings: [], readings: [], level: item.data.level, url: item.data.document_url, available: i == -1 ? 'Locked' : i == 0 ? 'In lesson queue' : item.assignments.srs_stage == 9 ? 'Burned' : Date.parse(item.assignments.available_at) < Date.now() ? 'Now' : s_to_dhm((Date.parse(item.assignments.available_at) - Date.now()) / 1000), } for (let k = 0; k < item.data.meanings.length; k++) { info.meanings.push(item.data.meanings[k].meaning) } if (item.data.readings) { for (let k = 0; k < item.data.readings.length; k++) { info.readings.push(item.data.readings[k].reading) } } var item_elem = $( '<div class="item ' + info.type + '"' + '>' + '<div class="hover_elem">' + '<div class="left">' + '<a class="' + info.type + '" href="' + info.url + '">' + info.characters + '</a>' + '</div>' + '<div class="right">' + '<table>' + '<tr><td>Meanings</td><td>' + info.meanings.join(', ') + '</td></tr>' + '<tr><td>Readings</td><td>' + info.readings.join('、') + '</td></tr>' + '<tr><td>Level</td><td>' + info.level + '</td></tr>' + '<tr><td>Available</td><td>' + info.available + '</td></tr>' + '</table>' + '</div>' + '</div>' + '<a class="' + info.type + '" href="' + info.url + '">' + info.characters + '</a>' + '</div>', )[0] items.appendChild(item_elem) } elem.appendChild(srs_elem) } let target = document.querySelector('.span12 > .row') target.parentElement.insertBefore(elem, target) } // Adds the CSS to the page function add_css() { let theme = wkof.settings[script_id].theme $('head').append( `<style id="wkda_css"> #wkda_items { background-color: #f4f4f4; border-radius: 5px; padding: 16px 24px 12px; --color-text: ${['rgb(240, 240, 240)', 'black'][theme]} !important; } #wkda_items.dark { background-color: #232629; } #wkda_items > div { margin-bottom: 10px; } #wkda_items { font-size: 16px; } #wkda_items .items { position: relative; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-start; margin-left: -2px; } #wkda_items .items .item { display: inline-block; padding: 0 3px; margin: 1.5px; border-radius: 3px; position: relative; } #wkda_items .items .radical { background: ${['#0096e7', '#3daee9'][theme]}; order: 0; width: 14px; } #wkda_items .items .kanji { background: ${['#ff00aa', '#fdbc4b'][theme]}; order: 1; } #wkda_items .items .vocabulary { background: ${['#9800e8', '#2ecc71'][theme]}; order: 3; } #wkda_items .items .kana_vocabulary { background: ${['#9800e8', '#2ecc71'][theme]}; order: 2; } #wkda_items .hover_elem { visibility: hidden; position: absolute; background-color: rgba(0, 0, 0, 0.9); z-index: 2; padding: 5px; border-radius: 3px; width: max-content; transform: translate(-50%, calc(0px - 100% - 5px)); left: 50%; } #wkda_items .item:hover .hover_elem { visibility: visible; } #wkda_items .hover_elem::after { visibility: hidden; position: absolute; width: 0; border-top: 5px solid rgba(0, 0, 0, 0.9); border-right: 5px solid transparent; border-left: 5px solid transparent; content: " "; font-size: 0; line-height: 0; left: 50%; bottom: -5px; transform: translateX(-50%); } #wkda_items .item:hover .hover_elem::after { visibility: visible; } #wkda_items .hover_elem > div { display: inline-block; } #wkda_items .item.vocabulary .hover_elem > div { display: block; } #wkda_items .left { vertical-align: top; } #wkda_items .item.vocabulary .hover_elem .left { margin-bottom: 5px; } #wkda_items .left a { font-size: 74px; line-height: 73px; min-width: 73px; display: block; padding: 5px; border-radius: 3px; margin: 3px 10px 0 3px; } #wkda_items .item.vocabulary .left a { margin-right: 3px; text-align: center; } #wkda_items .items .radical svg { height: 14px; stroke: currentColor; fill: none; stroke-linecap: square; stroke-width: 68; } #wkda_items .items .radical svg g { clip-path: none; } #wkda_items .items .radical .hover_elem svg { height: 74px; width: 1em; } #wkda_items .right table td:first-child { padding-right: 10px; font-weight: bold; } #wkda_items .items table td { color: rgb(240, 240, 240); } #wkda_items .items > div a { color: ${['rgb(240, 240, 240)', 'black'][theme]} !important; } #wkda_items .item.vocabulary .hover_elem { max-width: 320px; } </style>`, ) } // Converts seconds to days, hours, and minutes function s_to_dhm(s) { var d = Math.floor(s / 60 / 60 / 24) var h = Math.floor((s % (60 * 60 * 24)) / 60 / 60) var m = Math.ceil(((s % (60 * 60 * 24)) % (60 * 60)) / 60) return (d > 0 ? d + 'd ' : '') + (h > 0 ? h + 'h ' : '') + (m > 0 ? m + 'm' : '1m') } // Returns a promise and a resolve function function new_promise() { var resolve, promise = new Promise((res, rej) => { resolve = res }) return [promise, resolve] } // Handy little function that rfindley wrote. Checks whether the theme is dark. function is_dark_theme() { // Grab the <html> background color, average the RGB. If less than 50% bright, it's dark theme. return ( $('body') .css('background-color') .match(/\((.*)\)/)[1] .split(',') .slice(0, 3) .map((str) => Number(str)) .reduce((a, i) => a + i) / (255 * 3) < 0.5 ) } })(window.wkof, window.$)