Wanikani Override

Adds an "Ignore Answer" button during reviews that makes WaniKani ignore the current answer (useful if, for example, you made a stupid typo)

  1. // ==UserScript==
  2. // @name Wanikani Override
  3. // @namespace wkoverride
  4. // @description Adds an "Ignore Answer" button during reviews that makes WaniKani ignore the current answer (useful if, for example, you made a stupid typo)
  5. // @include http://www.wanikani.com/review/session*
  6. // @include https://www.wanikani.com/review/session*
  7. // @version 1.2
  8. // @author Mempo
  9. // @grant GM_addStyle
  10. // @grant unsafeWindow
  11. // @require http://code.jquery.com/jquery-1.11.2.min.js
  12. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  13. // ==/UserScript==
  14.  
  15. //Original author: Rui Pinheiro
  16.  
  17. // ESC shortcut
  18.  
  19.  
  20. /*
  21. * Debug Settings
  22. */
  23. var debugLogEnabled = true;
  24. var scriptShortName = 'WKO';
  25. scriptLog = debugLogEnabled ? function (msg) {
  26. if (typeof msg === 'string') {
  27. console.log(scriptShortName + ': ' + msg);
  28. } else {
  29. console.log(msg);
  30. }
  31. }
  32. : function () {
  33. };
  34. /*
  35. * Other settings
  36. */
  37. var prefAllowUnignore = true;
  38. /*
  39. * "Ignore Answer" Button Click
  40. */
  41. var ActionEnum = Object.freeze({
  42. ignore: 0,
  43. unignore: 1
  44. });
  45. function WKO_ignoreAnswer()
  46. {
  47. try
  48. {
  49. /* Check if the current item was answered incorrectly */
  50. var elmnts = document.getElementsByClassName('incorrect');
  51. var elmnts2 = document.getElementsByClassName('WKO_ignored');
  52. var curAction;
  53. if (!isEmpty(elmnts[0])) // Current answer is wrong
  54. curAction = ActionEnum.ignore;
  55. else if (prefAllowUnignore && !isEmpty(elmnts2[0])) // Current answer is ignored
  56. curAction = ActionEnum.unignore;
  57. else
  58. // Either there is no current answer, or it's correct
  59. {
  60. alert('WKO: Current item wasn\'t answered incorrectly, nor ignored previously!');
  61. return false;
  62. }
  63. /* Grab information about current question */
  64. var curItem = $.jStorage.get('currentItem');
  65. var questionType = $.jStorage.get('questionType');
  66. /* Build item name */
  67. var itemName;
  68. if (curItem.rad)
  69. itemName = 'r';
  70. else if (curItem.kan)
  71. itemName = 'k';
  72. else
  73. itemName = 'v';
  74. itemName += curItem.id;
  75. scriptLog(itemName);
  76. /* Grab item from jStorage.
  77. *
  78. * item.rc and item.mc => Reading/Meaning Completed (if answered the item correctly)
  79. * item.ri and item.mi => Reading/Meaning Invalid (number of mistakes before answering correctly)
  80. */
  81. var item = $.jStorage.get(itemName) || {
  82. };
  83. /* Update the item data */
  84. if (questionType === 'meaning')
  85. {
  86. if (!('mi' in item) || isEmpty(item.mi))
  87. {
  88. throw Error('item.mi undefined');
  89. return false;
  90. }
  91. else if (item.mi < 0 || (item.mi == 0 && curAction == ActionEnum.ignore))
  92. {
  93. throw Error('item.mi too small');
  94. return false;
  95. }
  96. if (curAction == ActionEnum.ignore)
  97. item.mi -= 1;
  98. else
  99. item.mi += 1;
  100. delete item.mc;
  101. }
  102. else
  103. {
  104. if (!('ri' in item) || isEmpty(item.ri))
  105. {
  106. throw Error('item.ri undefined');
  107. return false;
  108. }
  109. else if (item.ri < 0 || (item.ri == 0 && curAction == ActionEnum.ignore))
  110. {
  111. throw Error('i.ri too small');
  112. return false;
  113. }
  114. if (curAction == ActionEnum.ignore)
  115. item.ri -= 1;
  116. else
  117. item.ri += 1;
  118. delete item.rc;
  119. }
  120. /* Save the new state back into jStorage */
  121. $.jStorage.set(itemName, item);
  122. /* Modify the questions counter and wrong counter and change the style of the answer field */
  123. var wrongCount = $.jStorage.get('wrongCount');
  124. var questionCount = $.jStorage.get('questionCount');
  125. if (curAction == ActionEnum.ignore)
  126. {
  127. $.jStorage.set('wrongCount', wrongCount - 1);
  128. $.jStorage.set('questionCount', questionCount - 1);
  129. $('#answer-form fieldset').removeClass('incorrect');
  130. $('#answer-form fieldset').addClass('WKO_ignored');
  131. }
  132. else
  133. {
  134. $.jStorage.set('wrongCount', wrongCount + 1);
  135. $.jStorage.set('questionCount', questionCount + 1);
  136. $('#answer-form fieldset').removeClass('WKO_ignored');
  137. $('#answer-form fieldset').addClass('incorrect');
  138. }
  139. return true;
  140. }
  141. catch (err) {
  142. logError(err);
  143. }
  144. }
  145. /*
  146. * Bind '~' as a hotkey
  147. */
  148. function bindHotkey()
  149. {
  150. jQuery(document).on('keydown.reviewScreen', function (event)
  151. {
  152. if ($('#reviews').is(':visible') && !$('*:focus').is('textarea, input'))
  153. {
  154. //alert('keycode: ' + event.keyCode);
  155. switch (event.keyCode) {
  156. //case 176: //Firefox '~'
  157. //case 192: //Chrome '~'
  158. case 27: // ESC Button
  159. event.stopPropagation();
  160. event.preventDefault();
  161. if ($('#user-response').is(':disabled'))
  162. WKO_ignoreAnswer();
  163. return false;
  164. break;
  165. }
  166. }
  167. });
  168. }
  169. /*
  170. * Inject Ignore Button
  171. */
  172. function addIgnoreAnswerBtn()
  173. {
  174. var footer = document.getElementsByTagName('footer'),
  175. $btn = jQuery('<div id="WKO_button" title="Ignore Answer">Ignore Answer</div>').on('click', WKO_ignoreAnswer);
  176. jQuery(footer[0]).prepend($btn);
  177. }
  178. /*
  179. * Prepares the script
  180. */
  181. function scriptInit()
  182. {
  183. // Add global CSS styles
  184. GM_addStyle('#WKO_button {background-color: #CC0000; color: #FFFFFF; cursor: pointer; display: inline-block; font-size: 0.8125em; padding: 10px; vertical-align: bottom;}');
  185. GM_addStyle('#answer-form fieldset.WKO_ignored input[type="text"]:-moz-placeholder, #answer-form fieldset.WKO_ignored input[type="text"]:-moz-placeholder {color: #FFFFFF; font-family: "Source Sans Pro",sans-serif; font-weight: 300; text-shadow: none; transition: color 0.15s linear 0s; } #answer-form fieldset.WKO_ignored button, #answer-form fieldset.WKO_ignored input[type="text"], #answer-form fieldset.WKO_ignored input[type="text"]:disabled { background-color: #FFCC00 !important; }');
  186. scriptLog('loaded');
  187. // Set up hooks
  188. try
  189. {
  190. addIgnoreAnswerBtn();
  191. bindHotkey();
  192. }
  193. catch (err) {
  194. logError(err);
  195. }
  196. }
  197. /*
  198. * Helper Functions/Variables
  199. */
  200. //use 'jQuery' for greasemonkey's version, $ is WK's jQuery
  201. $ = unsafeWindow.$;
  202. function isEmpty(value) {
  203. return (typeof value === 'undefined' || value === null);
  204. }
  205. /*
  206. * Error handling
  207. * Can use 'error.stack', not cross-browser (though it should work on Firefox and Chrome)
  208. */
  209. function logError(error)
  210. {
  211. var stackMessage = '';
  212. if ('stack' in error)
  213. stackMessage = '\n\tStack: ' + error.stack;
  214. console.error(scriptShortName + ' Error: ' + error.name + '\n\tMessage: ' + error.message + stackMessage);
  215. }
  216. /*
  217. * Start the script
  218. */
  219. scriptInit();
  220.  
  221. // Hook into App Store
  222. try { $('.app-store-menu-item').remove(); $('<li class="app-store-menu-item"><a href="https://community.wanikani.com/t/there-are-so-many-user-scripts-now-that-discovering-them-is-hard/20709">App Store</a></li>').insertBefore($('.navbar .dropdown-menu .nav-header:contains("Account")')); window.appStoreRegistry = window.appStoreRegistry || {}; window.appStoreRegistry[GM_info.script.uuid] = GM_info; localStorage.appStoreRegistry = JSON.stringify(appStoreRegistry); } catch (e) {}

QingJ © 2025

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