您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Better visualization of upcoming Bunpro Reviews
当前为
// ==UserScript== // @name bunpro-planner // @namespace http://patrickayoup.com/ // @version 1.0.6 // @description Better visualization of upcoming Bunpro Reviews // @author Patrick Ayoup // @include *bunpro.jp/* // @exclude *community.bunpro.jp* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js // @require http://greasyfork.icu/scripts/5392-waitforkeyelements/code/WaitForKeyElements.js?version=115012 // ==/UserScript== const BunproPlanner = function () { this.interval = 1; this.chart = null; this.toAbsoluteTime = function (relativeTime) { let absoluteTime = null; relativeTime = relativeTime.toLowerCase(); if (relativeTime === 'now') { absoluteTime = moment(); } else { let tokens = relativeTime.split(' '); // When bunpro "burns" items, it uses the string // "almost 20 years", we want to get the last 2 tokens // which will cover both cases. let value = tokens[tokens.length - 2]; let unit = tokens[tokens.length - 1]; absoluteTime = moment().add(parseInt(value), unit); } return absoluteTime.format(); }; this.getReviews = function () { const self = this; let cards = jQuery('div .srs-info .row'); const reviews = cards.map(function () { let kids = jQuery(this).children(); let rv = { timesCorrect: parseInt(jQuery(kids[0]).text().trim().replace("Times Correct: ", "")), nextReview: self.toAbsoluteTime( jQuery(kids[1]).text().trim().replace("Next Review: ", "").replace("about ", "") ) }; return rv; }); this.reviews = reviews.get(); }; this.insertChartNode = function () { const self = this; // Delete the existing chart if there is one. const oldContainer = $('#bunpro-planner-chart-container').first().remove(); let showUpcomingGrammarButton = jQuery('div.show-upcoming-grammar').first(); let container = jQuery('<div id="bunpro-planner-chart-container"></div>'); let intervalSelector = jQuery(` <select id="bunpro-planner-interval"> <option value="8">8 Hours</option> <option value="4">4 Hours</option> <option value="2">2 Hours</option> <option value="1">1 Hour</option> <option value="0.5">30 Minutes</option> <option value="0.25">15 Minutes</option> </select> `); intervalSelector.change(function () { self.interval = Number($('#bunpro-planner-interval').children("option:selected").val()); self.insertChartNode(); self.buildChart(); }); let chart = jQuery('<canvas id="bunpro-planner-chart"></canvas>'); container.append(intervalSelector); container.append(chart); container.insertBefore(showUpcomingGrammarButton); $('#bunpro-planner-interval').val(this.interval); this.chart = chart; }; this.generateIntervals = function (startAt) { startAt = startAt.startOf('hour'); let intervals = []; let totalIntervals = 24; for (i = 0; i < totalIntervals; ++i) { intervals.push(startAt.clone().add(i * this.interval, 'hours')); } return intervals; }; this.getReviewsPerInterval = function (intervals) { let now = moment().startOf('hour'); let maxTimestamp = now.clone().add(24 * this.interval, 'hours'); let maxDiff = maxTimestamp.diff(now, 'hours'); // Filter for cards scheduled outside the displayed range. let filteredReviews = this.reviews.filter(function (card) { return moment(card.nextReview).diff(now, 'hours') < maxDiff; }); let groups = _.groupBy(filteredReviews, (card) => { const reviewTime = moment(card.nextReview); for (i = 0; i < intervals.length; ++i) { if (intervals[i] > reviewTime) { if (i > 0) { return intervals[i - 1]; } else { return intervals[0]; } } } return intervals[intervals.length - 1]; }); return intervals.map(function(interval) { if (groups[interval]) { return groups[interval].length; } else { return 0; } }); }; this.getAccumulatedReviewsPerInterval = function (reviewsPerInterval) { const accumulatedReviews = []; for (i = 0; i < reviewsPerInterval.length; ++i) { let sum = 0; for (j = 0; j <= i; ++j) { sum += reviewsPerInterval[j]; } accumulatedReviews.push(sum); } return accumulatedReviews; }; this.buildChart = function () { const now = moment(); var ctx = this.chart.get(0).getContext('2d'); let intervals = this.generateIntervals(now); const reviewsPerInterval = this.getReviewsPerInterval(intervals); const accumulatedReviewsPerInterval = this.getAccumulatedReviewsPerInterval(reviewsPerInterval); const labels = intervals.map((interval) => { return interval.format('MM/DD HH:mm'); }); new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [ { label: 'Reviews', data: reviewsPerInterval, backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgba(255,99,132,1)', borderWidth: 1 }, { label: 'Accumulated Reviews', data: accumulatedReviewsPerInterval, type: 'line' } ] }, options: { scales: { yAxes: [{ ticks: { beginAtZero:true } }] }, legend: { display: false } } }); }; this.getReviews(); }; waitForKeyElements('div.show-upcoming-grammar', function() { const bunproPlanner = new BunproPlanner(); bunproPlanner.insertChartNode(); bunproPlanner.buildChart(); });