Adds a button to clear review history for a word during jpdb reviews, with 'd' as hotkey
// ==UserScript==
// @name jpdb-clear-review-history
// @version 1
// @homepage https://gitlab.com/ioric/jpdb-clear-review-history
// @namespace https://gitlab.com/ioric/jpdb-clear-review-history
// @description Adds a button to clear review history for a word during jpdb reviews, with 'd' as hotkey
// @author ioric
// @license https://opensource.org/licenses/MIT
// @match https://jpdb.io/review*
// @connect self
// ==/UserScript==
'use strict';
function clearReviewHistory() {
// origin url is sent to clear-review-history form, which is
// based on the vocabulary url that the answer word links to
let orig = document.querySelector('.answer-box a')?.href;
// while the 'c' value is sent to the grade review form, the
// 'v' and 's' values in it are sent to clear-review-history
const c = document.querySelector('input[name="c"]')?.value;
if (!(c && orig)) {
console.error('Vocabulary variables not found');
return;
}
const [_, v, s] = c.split(',');
orig = orig.replace('#a', '').concat('/review-history');
fetch('/clear-review-history', {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `v=${v}&s=${s}&origin=${orig}`,
}).then((response) => {
if (response.status == 302 || response.status == 200) {
// now submit the review form to move to the next word
document.querySelector('#grade-clear').parentElement.submit();
} else {
console.error(`Unexpected response from clearing review history: ${response.status}`);
}
}).catch((e) => {
console.error(e);
});
}
function insertClearButton() {
// Add a new 'Clear history' button between Blacklist and Never forget
const blacklistButton = document.querySelector('#grade-blacklist');
if (blacklistButton == null) {
console.error('blacklist button not found');
return;
}
const blacklistForm = blacklistButton.parentElement;
// we want the whole form for "submitting the review" later
// this will have no effect on the word as the history has been cleared
// but will move forward to the next review properly
const clearForm = blacklistForm.cloneNode(true);
const clearButton = clearForm.querySelector('#grade-blacklist');
clearForm.querySelector('input[name="g"]').value = '1';
clearButton.id = 'grade-clear';
clearButton.value = 'Clear history';
clearButton.type = 'button'; // prevent submit, which will be manually triggered later
clearButton.style = 'padding: 0'; // restore style that only applies to 'submit' type
clearButton.onclick = clearReviewHistory;
blacklistForm.insertAdjacentElement('afterend', clearForm);
// Change "I'll never forget" label to save some space
document.querySelector('#grade-permaknown').value = 'Never forget';
document.addEventListener("keyup", (e) => {
if (e.key == 'd') { // 'c' would be nice but it's already used by jpdb
e.preventDefault();
document.querySelector('#grade-clear').click();
}
});
}
(() => {
// Find the div with the 'Show answer' button, add a new button
// after it changes to show the review buttons on the card back
const reviewButtonGroup = document.querySelector('.review-button-group');
if (reviewButtonGroup == null) {
console.error('review-button-group not found');
return;
}
const callback = (mutationList, _) => {
for (const mutation of mutationList) {
if (mutation.type == 'childList'
&& mutation.addedNodes[0].tagName == 'DIV') {
insertClearButton();
}
}
}
const observer = new MutationObserver(callback);
observer.observe(reviewButtonGroup, {childList: true, subtree: true});
})();