-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.js
158 lines (138 loc) · 4.82 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// ==UserScript==
// @name WaniKani Exact Review Time
// @namespace goldenchrysus.wanikani.exactreviewtime
// @description Shows actual time of next review
// @author GoldenChrysus
// @website https://github.com/GoldenChrysus
// @version 1.1.3
// @include https://www.wanikani.com/dashboard*
// @include https://www.wanikani.com/
// @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js
// @copyright 2018+, Patrick Golden
// @license MIT; http://opensource.org/licenses/MIT
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
"use strict";
// Establish important variables
let key = "goldenchrysus_wanikani_exactreviewtime";
let $time = $(".review-status .timeago");
let observer = new window.MutationObserver(updateReviewBlock);
let timeout_available = false;
let timeout_countdown = false;
let wkof = window.wkof || {};
let modules = "Menu, Settings";
let settings_modal = false;
// Don't run if time isn't found; probably on home page rather than dashboard
if (!$time.length) {
return;
}
// Only initialize WaniKani Open Framework if the user has it installed and available
if (window.wkof) {
wkof.include(modules);
wkof
.ready(modules)
.then(startup);
}
// Track changes to .timeago in order to override changes created by WaniKani
observer.observe(
$time[0],
{
characterData : true,
childList : true,
attributes : true,
subtree : true
}
);
updateReviewBlock();
/**
* WaniKani Open Framework broad initialization function
*/
function startup() {
// Default settings
let defaults = {
twenty_four_hour : false
};
// Load in default settings, then run the setup function
wkof.Settings
.load(key, defaults)
.then(initializeSetup);
}
/**
* WaniKani Open Framework setup initialization function
*/
function initializeSetup() {
// Create setting menu item
wkof.Menu.insert_script_link({
name : key,
submenu : "Exact Review Time",
title : "Settings",
on_click : openSettingsModal
});
// Establish configurable settings
settings_modal = new wkof.Settings({
script_id : key,
title : "Exact Review Time Settings",
on_save : updateReviewBlock,
content : {
twenty_four_hour : {
type : "checkbox",
label : "24-Hour Time Format"
}
}
});
// Load settings into WaniKani Open Framework, then trigger updateReviewBlock again to account for user's settings
settings_modal
.load()
.then(updateReviewBlock);
}
/**
* Handle clicks on the settings menu item generated by WaniKani Open Framework
*/
function openSettingsModal() {
settings_modal.open();
}
/**
* Updates the time or countdown until the next review
*/
function updateReviewBlock() {
let timestamp = +$time.attr("datetime");
let milliseconds = timestamp * 1000;
let now_milliseconds = +moment().format("x");
let in_future = (milliseconds > now_milliseconds);
let format = (wkof.settings && wkof.settings[key] && wkof.settings[key].twenty_four_hour) ? "HH:mm" : "h:mm a";
let time = (!in_future) ? "Available now" : moment(milliseconds).format(format);
let difference = Math.floor((milliseconds - now_milliseconds) / 1000);
// If time until next review is <= 30 minutes, swap to "# minutes" text
if (in_future && difference <= 1800) {
let minutes = Math.ceil(difference / 60) || 1;
let plural = (minutes === 1) ? "" : "s";
let original_time = time;
let $parent = $time.closest(".next");
time = `${minutes} minute${plural}`;
// Run this function again in 30 seconds to get updated minute count
setTimeout(updateReviewBlock, 30000);
// Maintain exact time feature by injecting a new element adjacent to WaniKani's "Next Review" text
let $small = $parent.find("small.exact");
let small_text = `@ ${original_time}`;
if (!$small.length) {
let $small = $(`<small class="exact">${small_text}</small>`);
$parent.append($small);
} else if ($small.html() !== small_text) {
$small.html(small_text)
}
// otherwise ensure this function runs when 30 minutes remain if there are currently more than 30 minutes left
} else if (in_future && !timeout_countdown) {
timeout_countdown = setTimeout(updateReviewBlock, milliseconds - (30 * 60 * 1000));
}
// Only update the time HTML if it doesn't match this function's output in order to avoid infinite recursion by MutationObserver
if ($time.html() !== time) {
$time.html(time);
}
// Set a timeout for 1 millisecond after the next review time in order to generate the "Available now" text
if (in_future && !timeout_available) {
timeout_available = setTimeout(updateReviewBlock, milliseconds - now_milliseconds + 1);
}
}
}());