Wanikani Self-Study Hide Info

Hide item info on the Level and Item screens for self-study.

  1. // ==UserScript==
  2. // @name Wanikani Self-Study Hide Info
  3. // @namespace rfindley
  4. // @description Hide item info on the Level and Item screens for self-study.
  5. // @version 1.1.2
  6. // @match https://www.wanikani.com/level/*
  7. // @match https://www.wanikani.com/radicals*
  8. // @match https://www.wanikani.com/kanji*
  9. // @match https://www.wanikani.com/vocabulary*
  10. // @match https://preview.wanikani.com/level/*
  11. // @match https://preview.wanikani.com/radicals*
  12. // @match https://preview.wanikani.com/kanji*
  13. // @match https://preview.wanikani.com/vocabulary*
  14. // @copyright 2018-2023, Robin Findley
  15. // @license MIT; http://opensource.org/licenses/MIT
  16. // @run-at document-end
  17. // @grant none
  18. // ==/UserScript==
  19.  
  20. window.ss_hideinfo = {};
  21.  
  22. (function(gobj) {
  23.  
  24. /* globals wkof, ss_quiz */
  25.  
  26. //===================================================================
  27. // Initialization of the Wanikani Open Framework.
  28. //-------------------------------------------------------------------
  29. var script_name = 'Self-Study Quiz';
  30. var wkof_version_needed = '1.0.17';
  31. if (!window.wkof) {
  32. if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?')) {
  33. window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
  34. }
  35. return;
  36. }
  37. if (wkof.version.compare_to(wkof_version_needed) === 'older') {
  38. if (confirm(script_name+' requires Wanikani Open Framework version '+wkof_version_needed+'.\nDo you want to be forwarded to the update page?')) {
  39. window.location.href = 'https://greasyfork.org/en/scripts/38582-wanikani-open-framework';
  40. }
  41. return;
  42. }
  43.  
  44. wkof.include('Settings');
  45. wkof.ready('document, Settings')
  46. .then(load_settings)
  47. .then(install_interface);
  48.  
  49. //========================================================================
  50. var btnbar, sections;
  51. function install_interface() {
  52. var html =
  53. '<div class="ss_hideinfo">'+
  54. ' <label>Self-study:</label>'+
  55. ' <div class="btn-group">'+
  56. ' <button class="btn enable" title="Enable/Disable self-study plugin"></button>'+
  57. ' <button class="btn ssquiz hidden" title="Open the quiz window">Quiz</button>'+
  58. ' <button class="btn shuffle" title="Shuffle the list of items below">Shuffle</button>'+
  59. ' <select class="btn mode" title="Select a self-study preset">'+
  60. ' <option value="jp2en">Japanese to English</option>'+
  61. ' <option value="en2jp">English to Japanese</option>'+
  62. ' </select>'+
  63. ' <select class="btn lockburn" title="Select a self-study preset">'+
  64. ' <option value="all">Show All Items</option>'+
  65. ' <option value="hideunlocked">Show Locked Only</option>'+
  66. ' <option value="hideunburned">Show Burned Only</option>'+
  67. ' <option value="hidelocked">Hide Locked</option>'+
  68. ' <option value="hideburned">Hide Burned</option>'+
  69. ' <option value="hidelockedburned">Hide Locked and Burned</option>'+
  70. ' </select>'+
  71. ' </div>'+
  72. '</div>';
  73.  
  74. var css = `
  75. .ss_hideinfo {margin-left:20px; margin-bottom:10px; position:relative;}
  76. .ss_hideinfo label {display:inline; vertical-align:middle; padding-right:4px; color:#999; font-size:14px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif; text-shadow:0 1px 0 #fff;}
  77. .ss_hideinfo .btn-group {display:inline; vertical-align:middle; font-size:0px;}
  78. .ss_hideinfo select.btn {width:200px;}
  79. .ss_hideinfo .btn.enable {width:55px;}
  80. .ss_hideinfo .btn {
  81. display:inline-block;
  82. height:30px;
  83. padding: 4px 12px;
  84. margin-bottom: 0;
  85. font-size: 14px;
  86. line-height: 20px;
  87. text-align: center;
  88. vertical-align: middle;
  89. cursor: pointer;
  90. text-shadow: 0 1px 1px rgb(255 255 255 / 75%);
  91. font-family: "Ubuntu", Helvetica, Arial, sans-serif;
  92. background-color: #f0f0f0;
  93. background-image: linear-gradient(to bottom, #fff, #e6e6e6);
  94. background-repeat: repeat-x;
  95. border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);
  96. border: 1px solid #ccc;
  97. border-left: 0;
  98. border-bottom-color: #b3b3b3;
  99. box-shadow: inset 0 1px 0 rgb(255 255 255 / 20%), 0 1px 2px rgb(0 0 0 / 5%);
  100. }
  101. .ss_hideinfo .btn:hover {
  102. color: #333;
  103. text-decoration: none;
  104. background-position: 0 -15px;
  105. transition: background-position 0.1s linear;
  106. }
  107. .ss_hideinfo .btn:first-child {
  108. border-left: 1px solid #ccc;
  109. border-radius: 4px 0 0 4px;
  110. }
  111. .ss_hideinfo .btn:last-child {border-radius: 0 4px 4px 0;}
  112.  
  113. section.ss_active .ss_hideinfo button.enable {background-color:#b3e6b3; background-image:linear-gradient(to bottom, #ecf9ec, #b3e6b3);}
  114. section.ss_active .ss_hideinfo button.enable:after {content:"ON";}
  115. section:not(.ss_active) .ss_hideinfo button.enable:after {content:"OFF";}
  116.  
  117. section.ss_active.ss_hidechar .subject-character .subject-character__characters {opacity:0; transition:opacity ease-in-out 0.15s}
  118. section.ss_active.ss_hideread .subject-character .subject-character__reading {opacity:0; transition:opacity ease-in-out 0.15s}
  119. section.ss_active.ss_hidemean .subject-character .subject-character__meaning {opacity:0; transition:opacity ease-in-out 0.15s}
  120. section.ss_active.ss_hideburned .ss_burned {display:none;}
  121. section.ss_active.ss_hidelocked .ss_locked {display:none;}
  122. section.ss_active.ss_hideunburned .subject-character-grid__item:not(.ss_burned) {display:none;}
  123. section.ss_active.ss_hideunlocked .subject-character-grid__item:not(.ss_locked) {display:none;}
  124.  
  125. section.ss_active .subject-character:hover span.subject-character__characters {opacity: initial !important; transition:opacity ease-in-out 0.05s !important;}
  126. section.ss_active .subject-character:hover .subject-character__info span {opacity: initial !important; transition:opacity ease-in-out 0.05s !important;}
  127. `;
  128.  
  129. let css_tag = document.createElement('style');
  130. css_tag.textContent = css;
  131. document.head.prepend(css_tag);
  132.  
  133. sections = document.querySelectorAll('section.character-grid');
  134.  
  135. sections.forEach((section) => {
  136. section.insertAdjacentHTML('afterbegin',html);
  137. btnbar = section.querySelector('.ss_hideinfo');
  138. btnbar.querySelector('button.enable').addEventListener('click', toggle_enable);
  139. btnbar.querySelector('button.ssquiz').addEventListener('click', open_quiz);
  140. btnbar.querySelector('button.shuffle').addEventListener('click', shuffle);
  141. btnbar.querySelector('select.mode').addEventListener('change', mode_changed);
  142. btnbar.querySelector('select.lockburn').addEventListener('change', lockburn_changed);
  143. });
  144.  
  145. for (let item of document.querySelectorAll('section.character-grid .subject-character--locked')) {
  146. item.closest('.subject-character-grid__item').classList.add('ss_locked');
  147. }
  148. for (let item of document.querySelectorAll('section.character-grid .subject-character--burned')) {
  149. item.closest('.subject-character-grid__item').classList.add('ss_burned');
  150. }
  151.  
  152. wkof.wait_state('ss_quiz', 'ready').then(function(){
  153. if (typeof ss_quiz.open === 'function') {
  154. for (let bar of document.querySelectorAll('.ss_hideinfo button.ssquiz')) {
  155. bar.classList.remove('hidden');
  156. };
  157. }
  158. });
  159.  
  160. toggle_enable(null, true /* no_toggle */);
  161. mode_changed();
  162. lockburn_changed();
  163. if (settings.enabled) shuffle();
  164. }
  165.  
  166. //========================================================================
  167. function deep_merge(...objects) {
  168. let merged = {};
  169. function recursive_merge(dest, src) {
  170. for (let prop in src) {
  171. if (typeof src[prop] === "object" && src[prop] !== null ) {
  172. if (Array.isArray(src[prop])) {
  173. dest[prop] = src[prop].slice();
  174. } else {
  175. dest[prop] = dest[prop] || {};
  176. recursive_merge(dest[prop], src[prop]);
  177. }
  178. } else {
  179. dest[prop] = src[prop];
  180. }
  181. }
  182. return dest;
  183. }
  184. for (let obj in objects) {
  185. recursive_merge(merged, objects[obj]);
  186. }
  187. return merged;
  188. }
  189.  
  190. //========================================================================
  191. var settings;
  192. function load_settings() {
  193. var default_settings = {
  194. enabled: false,
  195. mode: 'jp2en',
  196. lockburn: 'all',
  197. };
  198. return wkof.Settings.load('ss_hideinfo')
  199. .then(function(){
  200. settings = deep_merge(default_settings, wkof.settings.ss_hideinfo);
  201. settings = wkof.settings.ss_hideinfo;
  202. });
  203. }
  204.  
  205. //========================================================================
  206. function save_settings() {
  207. wkof.Settings.save('ss_hideinfo');
  208. }
  209.  
  210. //========================================================================
  211. function toggle_enable(e, no_toggle) {
  212. var enabled = settings.enabled;
  213. if (no_toggle !== true) enabled = !enabled;
  214. if (enabled) {
  215. sections.forEach((section) => section.classList.add('ss_active'));
  216. } else {
  217. sections.forEach((section) => section.classList.remove('ss_active'));
  218. }
  219. if (enabled !== settings.enabled) {
  220. settings.enabled = enabled;
  221. save_settings();
  222. }
  223. }
  224.  
  225. //========================================================================
  226. function fisher_yates_shuffle(arr) {
  227. var i = arr.length, j, temp;
  228. if (i===0) return arr;
  229. while (--i) {
  230. j = Math.floor(Math.random()*(i+1));
  231. temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
  232. }
  233. return arr;
  234. }
  235.  
  236. //========================================================================
  237. function shuffle(e) {
  238. if (e === undefined) {
  239. // Shuffle all
  240. for (let section of document.querySelectorAll('.subject-character-grid')) {
  241. let grid = section.querySelector('.subject-character-grid__items');
  242. let items = fisher_yates_shuffle(Array.from(grid.children));
  243. for (let item of items) grid.append(item);
  244. };
  245. } else {
  246. // Shuffle specific group
  247. let section = e.currentTarget.closest('section.character-grid');
  248. let grid = section.querySelector('.subject-character-grid__items');
  249. let items = fisher_yates_shuffle(Array.from(grid.children));
  250. for (let item of items) grid.append(item);
  251. }
  252. }
  253.  
  254. //========================================================================
  255. function mode_changed(e) {
  256. let value;
  257. if (e !== undefined) {
  258. value = e.target.value;
  259. settings.mode = value;
  260. save_settings();
  261. } else {
  262. value = settings.mode;
  263. }
  264. for (let section of sections) {
  265. section.querySelector('select.mode').value = value;
  266. section.classList.remove('ss_hidechar', 'ss_hideread', 'ss_hidemean');
  267. switch (value) {
  268. case 'jp2en':
  269. section.classList.add('ss_hidemean', 'ss_hideread');
  270. break;
  271.  
  272. case 'en2jp':
  273. section.classList.add('ss_hidechar', 'ss_hideread');
  274. break;
  275. }
  276. }
  277. }
  278.  
  279. //========================================================================
  280. function lockburn_changed(e) {
  281. let value;
  282. if (e !== undefined) {
  283. value = e.target.value;
  284. settings.lockburn = value;
  285. save_settings();
  286. } else {
  287. value = settings.lockburn;
  288. }
  289. for (let section of sections) {
  290. section.querySelector('select.lockburn').value = value;
  291. section.classList.remove('ss_hidelocked', 'ss_hideburned', 'ss_hideunlocked', 'ss_hideunburned');
  292. switch (value) {
  293. case 'all':
  294. break;
  295.  
  296. case 'hidelocked':
  297. section.classList.add('ss_hidelocked');
  298. break;
  299.  
  300. case 'hideburned':
  301. section.classList.add('ss_hideburned');
  302. break;
  303.  
  304. case 'hidelockedburned':
  305. section.classList.add('ss_hidelocked', 'ss_hideburned');
  306. break;
  307.  
  308. case 'hideunlocked':
  309. section.classList.add('ss_hideunlocked');
  310. break;
  311.  
  312. case 'hideunburned':
  313. section.classList.add('ss_hideunburned');
  314. break;
  315. }
  316. }
  317. }
  318.  
  319. //========================================================================
  320. function open_quiz(e) {
  321. var btn = e.currentTarget;
  322. var sec = btn.closest('section.character-grid');
  323. var level, item_type;
  324. var path = window.location.pathname.split('/');
  325. var header = btn.closest('section').querySelector('header').textContent.trim();
  326. if (path[1] === 'level') {
  327. level = path[2];
  328. item_type = header.toLowerCase();
  329. } else {
  330. level = header.split(' ')[1];
  331. item_type = path[1];
  332. }
  333. var item_type_text = item_type;
  334. item_type_text[0] = item_type_text[0].toUpperCase();
  335. var title = 'Level '+level+' '+item_type_text;
  336. item_type = item_type.replace(/s$/g, '');
  337.  
  338. var custom_options = {
  339. ipreset: {name: title, content: {
  340. wk_items: {enabled: true, filters: {
  341. level: {enabled: true, value: level},
  342. item_type: {enabled: true, value: item_type},
  343. }},
  344. }},
  345. };
  346. switch (settings.lockburn) {
  347. case 'all':
  348. break;
  349.  
  350. case 'hidelocked':
  351. custom_options.ipreset.content.wk_items.filters.srs = {enabled:true,value:'1,2,3,4,5,6,7,8,9'};
  352. break;
  353.  
  354. case 'hideburned':
  355. custom_options.ipreset.content.wk_items.filters.srs = {enabled:true,value:'-1,0,1,2,3,4,5,6,7,8'};
  356. break;
  357.  
  358. case 'hidelockedburned':
  359. custom_options.ipreset.content.wk_items.filters.srs = {enabled:true,value:'1,2,3,4,5,6,7,8'};
  360. break;
  361.  
  362. case 'hideunlocked':
  363. custom_options.ipreset.content.wk_items.filters.srs = {enabled:true,value:'-1,0'};
  364. break;
  365.  
  366. case 'hideunburned':
  367. custom_options.ipreset.content.wk_items.filters.srs = {enabled:true,value:'9'};
  368. break;
  369. }
  370.  
  371. if (settings.mode === 'en2jp' && item_type === 'vocabulary') {
  372. custom_options.qpreset = {
  373. name: 'English to Japanese',
  374. content: {
  375. mean2read:true,
  376. }
  377. };
  378. } else {
  379. custom_options.qpreset = {
  380. name: 'Japanese to English',
  381. content: {
  382. char2read:true,
  383. char2mean:true,
  384. }
  385. };
  386. }
  387. ss_quiz.open(custom_options);
  388. }
  389.  
  390. })(window.ss_hideinfo);