Wanikani Double-Check

Allows retyping typo'd answers, or marking wrong when WK's typo tolerance is too lax.

目前為 2018-06-07 提交的版本,檢視 最新版本

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

QingJ © 2025

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