Wanikani Double-Check

Adds a delay after wrong answers to prevent double-tapping <enter>

目前為 2017-11-18 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Wanikani Double-Check
  3. // @namespace wkdoublecheck
  4. // @description Adds a delay after wrong answers to prevent double-tapping <enter>
  5. // @include https://www.wanikani.com/review/session*
  6. // @version 2.0.3
  7. // @author Robin Findley
  8. // @copyright 2017+, Robin Findley
  9. // @license MIT; http://opensource.org/licenses/MIT
  10. // @run-at document-end
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. // CREDITS: This is a replacement for an original script by Wanikani user @Ethan.
  15. // Ethan's script stopped working due to some Wanikani changes. The code below is
  16. // 100% my own, but it closely replicates the functionality of Ethan's original script.
  17.  
  18. // HOTKEYS:
  19. // "+" - Marks answer as 'correct'.
  20. // "-" - Marks answer as 'incorrect'.
  21. // "Escape" - Resets question, allowing you to retype.
  22.  
  23. // SEE SETTINGS BELOW.
  24.  
  25. window.wkdoublecheck = {};
  26.  
  27. (function(gobj) {
  28.  
  29. //==[ Settings ]=====================================================
  30. var settings = {
  31.  
  32. // Delay when answer is wrong.
  33. delay_wrong: 1,
  34.  
  35. // Delay when answer has multiple meanings.
  36. delay_multi_meaning: 0,
  37.  
  38. // Delay when answer is slightly off (e.g. minor typo).
  39. delay_slightly_off: 0,
  40.  
  41. // Amount of time to delay (in milliseconds) before allowing the
  42. // user to move on after an accepted typo or rejected answer.
  43. delay_period: 1500,
  44. };
  45.  
  46. // Make the settings accessible from the console via 'wkdoublecheck.settings'
  47. gobj.settings = settings;
  48.  
  49. // For debugging. Blocks ajax requests.
  50. settings.block_ajax = 1;
  51.  
  52. //===================================================================
  53.  
  54. // Theory of operation:
  55. // ====================
  56. // Wanikani's normal process:
  57. // 1) User clicks 'submit'
  58. // a) Wanikani checks answer and updates screen with the result.
  59. // b) If both reading and meaning have been answered, Wanikani immediately sends the result to the server. (<-- BAD!!)
  60. // 2) User clicks 'submit' (or enter) again to move to the next question.
  61. // a) Wanikani updates the screen to show the next question.
  62. //
  63. // Our modified process:
  64. // 1) User clicks 'submit'
  65. // a) We intercept the click, check the answer ourself, and update the screen.
  66. // Wanikani's code is unaware of what we're doing.
  67. // b) User now has the opportunity to modify their answer.
  68. // 2) User clicks 'submit' (or enter) again to move to the next question.
  69. // a) We intercept the click again.
  70. // b) We reset the display back to pre-submitted state, so Wanikani's code won't be confused.
  71. // c) We call Wanikani's normal 'submit' function, but we intercept the answer-checker function,
  72. // so Wanikani will see whatever result the user requested.
  73. // Wanikani's updates the screen with the result.
  74. // d) Keep in mind, the user has clicked the 'submit' button twice, but Wanikani has only
  75. // seen one click. So, we have to send a third hidden click so Wanikani will catch up to
  76. // where the user thinks we are (i.e. 'next question').
  77. // 3) We intercept the hidden click, and forward it to Wanikani's code.
  78. // a) Wanikani updates the screen to show the next question.
  79.  
  80. var old_submit_handler, old_answer_checker, ignore_submit = false, state = 'first_submit', old_audioAutoplay, show_srs;
  81. var item, itype, item_id, item_status, qtype, valid_answers, wrong_cnt, question_cnt, completed_cnt, answer, new_answer;
  82.  
  83. //------------------------------------------------------------------------
  84. // toggle_result() - Toggle an answer from right->wrong or wrong->right.
  85. //------------------------------------------------------------------------
  86. function toggle_result(new_state) {
  87. if ($('#option-double-check').hasClass('disabled')) return false;
  88. if (new_state === 'toggle') new_state = (new_answer.passed ? 'incorrect' : 'correct');
  89. if (new_answer.passed && new_state === 'incorrect') {
  90. new_answer = {passed:false, accurate:false, multipleAnswers:false, exception:false};
  91. set_answer_state(new_answer, false /* show_msgs */);
  92. } else if (!new_answer.passed && new_state === 'correct') {
  93. new_answer = {passed:true, accurate:true, multipleAnswers:false, exception:false};
  94. set_answer_state(new_answer, false /* show_msgs */);
  95. } else if (new_state === 'retype') {
  96. set_answer_state({reset:true}, false /* show_msgs */);
  97. }
  98. }
  99.  
  100. //------------------------------------------------------------------------
  101. // do_delay() - Disable the submit button briefly to prevent clicking past wrong answers.
  102. //------------------------------------------------------------------------
  103. function do_delay() {
  104. ignore_submit = true;
  105. setTimeout(function() {
  106. ignore_submit = false;
  107. }, settings.delay_period);
  108. }
  109.  
  110. //------------------------------------------------------------------------
  111. // return_new_answer() - Alternate answer checker that overrides our results.
  112. //------------------------------------------------------------------------
  113. function return_new_answer() {
  114. return new_answer;
  115. }
  116.  
  117. //------------------------------------------------------------------------
  118. // set_answer_state() - Update the screen to show results of answer-check.
  119. //------------------------------------------------------------------------
  120. function set_answer_state(answer, show_msgs) {
  121. // If user requested to retype answer, reset the question.
  122. if (answer.reset) {
  123. $.jStorage.set('wrongCount', wrong_cnt);
  124. $.jStorage.set('questionCount', question_cnt);
  125. $.jStorage.set('completedCount', completed_cnt);
  126. $.jStorage.set('currentItem', item);
  127. $("#answer-exception").remove();
  128. $('#option-double-check').addClass('disabled').find('span').attr('title','Mark Right').find('i').attr('class','icon-thumbs-up');
  129. $('#option-retype').addClass('disabled');
  130. Srs.remove();
  131. state = 'first_submit';
  132. return;
  133. }
  134.  
  135. // If answer is invalid for some reason, do the shake thing.
  136. if (answer.exception) {
  137. if (!$("#answer-form form").is(":animated")) {
  138. $("#reviews").css("overflow-x", "hidden");
  139. var xlat = {onyomi:"on'yomi", kunyomi:"kun'yomi", nanori:"nanori"};
  140. var emph = xlat[item.emph];
  141. $("#answer-form form").effect("shake", {}, 400, function() {
  142. $("#reviews").css("overflow-x", "visible");
  143. $("#answer-form form").append($('<div id="answer-exception" class="answer-exception-form"><span>WaniKani is looking for the '+emph+" reading</span></div>").addClass("animated fadeInUp"));
  144. }).find("input").focus();
  145. }
  146. return;
  147. }
  148.  
  149. // Draw 'correct' or 'incorrect' results, enable Double-Check button, and calculate updated statistics.
  150. var new_wrong_cnt = wrong_cnt, new_completed_cnt = completed_cnt;
  151. $("#user-response").blur();
  152. $('#option-retype').removeClass('disabled');
  153. var new_status = Object.assign({},item_status);
  154. if (answer.passed) {
  155. $("#answer-form fieldset").removeClass('incorrect').addClass("correct");
  156. $('#option-double-check').removeClass('disabled').find('span').attr('title','Mark Wrong').find('i').attr('class','icon-thumbs-down');
  157. if (qtype === 'meaning')
  158. new_status.mc = (new_status.mc || 0) + 1;
  159. else
  160. new_status.rc = (new_status.rc || 0) + 1;
  161. } else {
  162. $("#answer-form fieldset").removeClass('correct').addClass("incorrect");
  163. $('#option-double-check').removeClass('disabled').find('span').attr('title','Mark Right').find('i').attr('class','icon-thumbs-up');
  164. new_wrong_cnt++;
  165. }
  166. if ((itype === 'r' || ((new_status.rc || 0) >= 1)) && ((new_status.mc || 0) >= 1)) {
  167. new_completed_cnt++;
  168. if (show_srs) Srs.load(new_status,item.srs);
  169. }
  170. $.jStorage.set('wrongCount', new_wrong_cnt);
  171. $.jStorage.set('questionCount', question_cnt + 1);
  172. $.jStorage.set('completedCount', new_completed_cnt);
  173. $("#user-response").prop("disabled", !0);
  174. additionalContent.enableButtons();
  175. lastItems.disableSessionStats();
  176. $("#answer-exception").remove();
  177.  
  178. // When user is submitting an answer, display the on-screen message that Wanikani normally shows.
  179. if (show_msgs) {
  180. var msg;
  181. if (answer.passed) {
  182. if (!answer.accurate) {
  183. msg = 'Your answer was a bit off. Check the '+qtype+' to make sure you are correct';
  184. } else if (answer.multipleAnswers) {
  185. msg = 'Did you know this item has multiple possible '+qtype+'s?';
  186. }
  187. } else {
  188. msg = 'Need help? View the correct '+qtype+' and mnemonic';
  189. }
  190. if (msg)
  191. $("#additional-content").append($('<div id="answer-exception"><span>'+msg+'</span></div>').addClass("animated fadeInUp"));
  192. }
  193. }
  194.  
  195. //------------------------------------------------------------------------
  196. // new_submit_handler() - Intercept handler for 'submit' button. Overrides default behavior as needed.
  197. //------------------------------------------------------------------------
  198. function new_submit_handler(e) {
  199. // Don't process 'submit' if we are ignoring temporarily (to prevent double-tapping past important info)
  200. if (ignore_submit) {
  201. // If the user presses <enter> during delay period,
  202. // WK enables the user input field, which makes Item Info not work.
  203. // Let's make sure the input field is disabled.
  204. setTimeout(function(){
  205. $("#user-response").prop('disabled',!0);
  206. },1);
  207. return false;
  208. }
  209.  
  210. // For more information about the state machine below,
  211. // see the "Theory of operation" info at the top of the script.
  212. switch(state) {
  213. case 'first_submit':
  214. // We intercept the first 'submit' click, and simulate normal Wanikani screen behavior.
  215. state = 'second_submit';
  216.  
  217. // Capture the state of the system before submitting the answer.
  218. item = $.jStorage.get('currentItem');
  219. itype = (item.rad ? 'r' : (item.kan ? 'k' : 'v'));
  220. item_id = itype + item.id;
  221. item_status = $.jStorage.get(item_id) || {};
  222. qtype = $.jStorage.get('questionType');
  223. wrong_cnt = $.jStorage.get('wrongCount');
  224. question_cnt = $.jStorage.get('questionCount');
  225. completed_cnt = $.jStorage.get('completedCount');
  226. show_srs = $.jStorage.get('r/srsIndicator');
  227.  
  228. // Ask Wanikani if the answer is right (but we don't actually submit the answer).
  229. answer = old_answer_checker(qtype, $("#user-response").val());
  230.  
  231. // Update the screen to reflect the results of our checked answer.
  232. $("html, body").animate({scrollTop: 0}, 200);
  233. new_answer = Object.assign({},answer);
  234. set_answer_state(answer, true);
  235. if (answer.exception) return false;
  236.  
  237. // Optionally (according to settings), temporarily ignore any additional clicks on the
  238. // 'submit' button to prevent the user from clicking past important info about the answer.
  239. if ((!answer.passed && settings.delay_wrong) ||
  240. (answer.passed &&
  241. ((!answer.accurate && settings.delay_slightly_off) || (answer.multipleAnswers && settings.delay_multi_meaning))
  242. )
  243. )
  244. {
  245. do_delay();
  246. }
  247.  
  248. return false;
  249.  
  250. case 'second_submit':
  251. // We take the user's second 'submit' click (after they've optionally toggled the answer result),
  252. // and send it to Wanikani's code as if it were the first click.
  253. // Then we send a hidden third 'submit', which Wanikani will see as the second 'submit', which moves us to the next question.
  254. state = 'third_submit';
  255.  
  256. old_audioAutoplay = window.audioAutoplay;
  257. window.audioAutoplay = false;
  258.  
  259. // Reset the screen to pre-submitted state, so Wanikani won't get confused when it tries to process the answer.
  260. // Wanikani code will then update the screen according to our forced answer-check result.
  261. $('#option-double-check').addClass('disabled').find('span').attr('title','Double-Check').find('i').attr('class','icon-thumbs-up');
  262. $('#option-retype').addClass('disabled');
  263. $('#user-response').removeAttr('disabled');
  264. $('#option-audio audio').remove();
  265. $.jStorage.set('wrongCount', wrong_cnt);
  266. $.jStorage.set('questionCount', question_cnt);
  267. $.jStorage.set('completedCount', completed_cnt);
  268.  
  269. // Prepare a hidden third click, which tells Wanikani to move to the next question.
  270. setTimeout(function(){
  271. $("#answer-form button").trigger('click');
  272. }, 1);
  273.  
  274. // We want Wanikani to see our forced answer-check result,
  275. // so we set up to intercept the answer-checker here.
  276. return old_submit_handler.apply(this, arguments);
  277.  
  278. case 'third_submit':
  279. // This is hidden third click from above, which Wanikani thinks is the second click.
  280. // Wanikani will move to the next question.
  281. state = 'first_submit';
  282.  
  283. window.audioAutoplay = old_audioAutoplay;
  284.  
  285. // We need to disable the input field, so Wanikani will see this as the second click.
  286. $('#user-response').attr('disabled','disabled');
  287.  
  288. return old_submit_handler.apply(this, arguments);
  289.  
  290. default:
  291. return false;
  292. }
  293.  
  294. return false;
  295. }
  296.  
  297. //------------------------------------------------------------------------
  298. // startup() - Install our intercept handlers, and add our Double-Check button and hotkey ("!")
  299. //------------------------------------------------------------------------
  300. function startup() {
  301. // Check if we can intercept the submit button handler.
  302. try {
  303. old_submit_handler = $._data( $('#answer-form button')[0], 'events').click[0].handler;
  304. old_answer_checker = answerChecker.evaluate;
  305. } catch(err) {}
  306. if (typeof old_submit_handler !== 'function' || typeof old_answer_checker !== 'function') {
  307. alert('Wanikani Mistake Delay script is not working.');
  308. return;
  309. }
  310.  
  311. // Replace the handler.
  312. $._data( $('#answer-form button')[0], 'events').click[0].handler = new_submit_handler;
  313.  
  314. var btn_count = $('#additional-content ul').children().length + 2;
  315. $('#additional-content ul').css('text-align','center').append(
  316. '<li id="option-double-check" class="disabled"><span title="Double Check"><i class="icon-thumbs-up"></i></span></li>'+
  317. '<li id="option-retype" class="disabled"><span title="Retype"><i class="icon-undo"></i></span></li></ul>'
  318. );
  319. $('#additional-content ul > li').css('width',Math.floor(9950/btn_count)/100 + '%');
  320. $('#option-double-check').on('click', toggle_result.bind(null,'toggle'));
  321. $('#option-retype').on('click', toggle_result.bind(null,'retype'));
  322. $('body').on('keypress', function(event){
  323. if (event.which === 43) toggle_result('correct');
  324. if (event.which === 45) toggle_result('incorrect');
  325. return true;
  326. });
  327. $('body').on('keydown', function(event){
  328. if (event.which === 27) toggle_result('retype');
  329. return true;
  330. });
  331. answerChecker.evaluate = return_new_answer;
  332.  
  333. // For debugging, block progress submissions.
  334. if (settings.block_ajax) {
  335. console.log('======[ "Double-Check" script is in debug mode, and will blocking ajax requests! ]======');
  336. $.ajax = function(){return $.Deferred().resolve();};
  337. }
  338. }
  339.  
  340. // Run startup() after window.onload event.
  341. if (document.readyState === 'complete')
  342. startup();
  343. else
  344. window.addEventListener("load", startup, false);
  345.  
  346. })(window.wkdoublecheck);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址