WaniKani Open Framework Statistics Filters

Additional statistics filters for the WaniKani Open Framework

  1. // ==UserScript==
  2. // @name WaniKani Open Framework Statistics Filters
  3. // @namespace https://www.wanikani.com
  4. // @description Additional statistics filters for the WaniKani Open Framework
  5. // @author prouleau
  6. // @version 1.5.0
  7. // @match https://www.wanikani.com/*
  8. // @license MIT; http://opensource.org/licenses/MIT
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function(wkof) {
  13. 'use strict';
  14.  
  15. var wkofMinimumVersion = '1.0.52';
  16.  
  17. if (!wkof) {
  18. var response = confirm('WaniKani Open Framework Date Filters requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');
  19.  
  20. if (response) {
  21. window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
  22. }
  23.  
  24. return;
  25. }
  26. var settingsDialog;
  27. var settingsScriptId = 'statsFilters';
  28. var settingsTitle = 'Statistics Filters';
  29.  
  30. var needToRegisterFilters = true;
  31.  
  32. var filterNamePrefix = 'statsFilters_';
  33. var leechLteqFilterName = filterNamePrefix + 'LeechLteq';
  34. var meaningCorAnsLteqFilterName = filterNamePrefix + 'meaningCorAnsLteq';
  35. var meaningCorAnsGteqFilterName = filterNamePrefix + 'meaningCorAnsGteq';
  36. var readingCorAnsLteqFilterName = filterNamePrefix + 'readingCorAnsLteq';
  37. var readingCorAnsGteqFilterName = filterNamePrefix + 'readingCorAnsGteq';
  38. var meaningIncorAnsLteqFilterName = filterNamePrefix + 'meaningIncorAnsLteq';
  39. var meaningIncorAnsGteqFilterName = filterNamePrefix + 'meaningIncorAnsGteq';
  40. var readingIncorAnsLteqFilterName = filterNamePrefix + 'readingIncorAnsLteq';
  41. var readingIncorAnsGteqFilterName = filterNamePrefix + 'readingIncorAnsGteq';
  42. var totalCorAnsLteqFilterName = filterNamePrefix + 'totalCorAnsLteq';
  43. var totalCorAnsGteqFilterName = filterNamePrefix + 'totalCorAnsGteq';
  44. var totalIncorAnsLteqFilterName = filterNamePrefix + 'totalIncorAnsLteq';
  45. var totalIncorAnsGteqFilterName = filterNamePrefix + 'totalIncorAnsGteq';
  46. var percentageTotalCorLteqFilterName = filterNamePrefix + 'percentageTotalCorLteq';
  47. var percentageTotalCorGteqFilterName = filterNamePrefix + 'percentageTotalCorGteq';
  48. var percentageMeaningCorLteqFilterName = filterNamePrefix + 'pecentagerMeaningCorLteq';
  49. var percentageMeaningCorGteqFilterName = filterNamePrefix + 'percentageMeaningCorGteq';
  50. var percentageReadingCorLteqFilterName = filterNamePrefix + 'percentageReadingCorLteq';
  51. var percentageReadingCorGteqFilterName = filterNamePrefix + 'percentageReadingCorGteq';
  52. var meaningCurStrLteqFilterName = filterNamePrefix + 'meaningCurStrLteq';
  53. var meaningCurStrGteqFilterName = filterNamePrefix + 'meaningCurStrGteq';
  54. var meaningMaxStrLteqFilterName = filterNamePrefix + 'meaningMaxStrLteq';
  55. var meaningMaxStrGteqFilterName = filterNamePrefix + 'meaningMaxStrGteq';
  56. var readingCurStrLteqFilterName = filterNamePrefix + 'readingCurStrLteq';
  57. var readingCurStrGteqFilterName = filterNamePrefix + 'readingCurStrGteq';
  58. var readingMaxStrLteqFilterName = filterNamePrefix + 'readingMaxStrLteq';
  59. var readingMaxStrGteqFilterName = filterNamePrefix + 'readingMaxStrGteq';
  60. var bothCurStrLteqFilterName = filterNamePrefix + 'bothCurStrLteq';
  61. var bothCurStrGteqFilterName = filterNamePrefix + 'bothCurStrGteq';
  62. var minCurStrLteqFilterName = filterNamePrefix + 'minCurStrLteq';
  63. var minCurStrGteqFilterName = filterNamePrefix + 'minCurStrGteq';
  64. var numReviewsLteqFilterName = filterNamePrefix + 'numReviewsLteq';
  65. var numReviewGteqFilterName = filterNamePrefix + 'numReviewsGteq';
  66.  
  67. var supportedFilters = [leechLteqFilterName, meaningCorAnsLteqFilterName, meaningCorAnsGteqFilterName, readingCorAnsLteqFilterName, readingCorAnsGteqFilterName,
  68. meaningIncorAnsLteqFilterName, meaningIncorAnsGteqFilterName, readingIncorAnsLteqFilterName, readingIncorAnsGteqFilterName,
  69. totalCorAnsLteqFilterName, totalCorAnsGteqFilterName, totalIncorAnsLteqFilterName, totalIncorAnsGteqFilterName,
  70. percentageTotalCorLteqFilterName, percentageTotalCorGteqFilterName, percentageMeaningCorLteqFilterName, percentageMeaningCorGteqFilterName,
  71. percentageReadingCorLteqFilterName, percentageReadingCorGteqFilterName, meaningCurStrLteqFilterName, meaningCurStrGteqFilterName,
  72. meaningMaxStrLteqFilterName, meaningMaxStrGteqFilterName, readingCurStrLteqFilterName, readingCurStrGteqFilterName,
  73. readingMaxStrLteqFilterName, readingMaxStrGteqFilterName, bothCurStrLteqFilterName, bothCurStrGteqFilterName,
  74. minCurStrLteqFilterName, minCurStrGteqFilterName, numReviewsLteqFilterName, numReviewGteqFilterName];
  75.  
  76. function updateFiltersWhenReady() {
  77. wkof.set_state(settingsScriptId, 'loading');
  78. needToRegisterFilters = true;
  79. waitForItemDataRegistry().then(registerFilters);
  80. }
  81.  
  82. function waitForItemDataRegistry() {
  83. return wkof.wait_state('wkof.ItemData.registry', 'ready');
  84. }
  85.  
  86. function registerFilters() {
  87. if (!needToRegisterFilters) {
  88. return;
  89. }
  90.  
  91. supportedFilters.forEach(function(filterName) {
  92. delete wkof.ItemData.registry.sources.wk_items.filters[filterName];
  93. });
  94.  
  95. registerLeechLteqFilter();
  96. registerMeaningCorAnsGteqFilter();
  97. registerMeaningCorAnsLteqFilter();
  98. registerReadingCorAnsGteqFilter();
  99. registerReadingCorAnsLteqFilter();
  100. registerMeaningIncorAnsGteqFilter();
  101. registerMeaningIncorAnsLteqFilter();
  102. registerReadingIncorAnsGteqFilter();
  103. registerReadingIncorAnsLteqFilter();
  104. registerTotalCorAnsGteqFilter();
  105. registerTotalCorAnsLteqFilter();
  106. registerTotalIncorAnsGteqFilter();
  107. registerTotalIncorAnsLteqFilter();
  108. registerPercentageTotalCorGteqFilter();
  109. registerPercentageTotalCorLteqFilter();
  110. registerPercentageMeaningCorGteqFilter();
  111. registerPercentageMeaningCorLteqFilter();
  112. registerPercentageReadingCorGteqFilter();
  113. registerPercentageReadingCorLteqFilter();
  114. registerMeaningCurStrGteqFilter();
  115. registerMeaningCurStrLteqFilter();
  116. registerMeaningMaxStrGteqFilter();
  117. registerMeaningMaxStrLteqFilter();
  118. registerReadingCurStrGteqFilter();
  119. registerReadingCurStrLteqFilter();
  120. registerReadingMaxStrGteqFilter();
  121. registerReadingMaxStrLteqFilter();
  122. registerBothCurStrGteqFilter();
  123. registerBothCurStrLteqFilter();
  124. registerMinCurStrGteqFilter();
  125. registerMinCurStrLteqFilter();
  126. registerNumberReviewsGteqFilter();
  127. registerNumberReviewsLteqFilter();
  128.  
  129. needToRegisterFilters = false;
  130. wkof.set_state(settingsScriptId, 'ready');
  131. }
  132.  
  133. // BEGIN Leech
  134. let leechLteqHover_tip = 'Select items that have a leech value less than or equal to the stated value.';
  135.  
  136. function registerLeechLteqFilter() {
  137. wkof.ItemData.registry.sources.wk_items.filters[leechLteqFilterName] = {
  138. type: 'number',
  139. label: 'Leech value <=',
  140. default: 10,
  141. filter_func: leechLteqFilter,
  142. set_options: function(options) { options.review_statistics = true; },
  143. hover_tip: leechLteqHover_tip,
  144. };
  145. }
  146.  
  147. function leechLteqFilter(filterValue, item) {
  148. if (item.review_statistics === undefined) {
  149. return false;
  150. }
  151.  
  152. var reviewStats = item.review_statistics;
  153. var meaningScore = getLeechScore(reviewStats.meaning_incorrect, reviewStats.meaning_current_streak);
  154. var readingScore = getLeechScore(reviewStats.reading_incorrect, reviewStats.reading_current_streak);
  155.  
  156. return meaningScore <= filterValue && readingScore <= filterValue;
  157. }
  158.  
  159. // Leech scores a rounded because otherwise the behavior of the filter is not consistent with rounded values presented to users in scripts.
  160. // In other words, data would be not filtered when based on user facing data it appears that it should be.
  161. function getLeechScore(incorrect, currentStreak) {
  162. return Math.round((incorrect / Math.pow((currentStreak || 0.5), 1.5)) * 100) / 100;
  163. }
  164. // END Leech
  165.  
  166.  
  167. // BEGIN Meaning correct answers
  168. let meaningCorAnsGteqFilterHover_tip = 'Selects items where the number of correct\nanswers for Meaning is &gt;= this value.';
  169. let meaningCorAnsLteqFilterHover_tip = 'Selects items where the number of correct\nanswers for Meaning is &lt;= this value.';
  170.  
  171. function registerMeaningCorAnsGteqFilter() {
  172. wkof.ItemData.registry.sources.wk_items.filters[meaningCorAnsGteqFilterName] = {
  173. type: 'number',
  174. label: 'Meaning Cor. Ans. &gt;=',
  175. default: 0,
  176. filter_func: meaningCorAnsGteqFilter,
  177. set_options: function(options) { options.review_statistics = true; },
  178. hover_tip: meaningCorAnsGteqFilterHover_tip,
  179. };
  180. }
  181.  
  182. function meaningCorAnsGteqFilter(filterValue, item) {
  183. let review_statistics = item.review_statistics;
  184. if (review_statistics === undefined) {
  185. return false;
  186. }
  187. return review_statistics.meaning_correct >= filterValue;
  188. }
  189.  
  190. function registerMeaningCorAnsLteqFilter() {
  191. wkof.ItemData.registry.sources.wk_items.filters[meaningCorAnsLteqFilterName] = {
  192. type: 'number',
  193. label: 'Meaning Cor. Ans. &lt;=',
  194. default: 100,
  195. filter_func: meaningCorAnsLteqFilter,
  196. set_options: function(options) { options.review_statistics = true; },
  197. hover_tip: meaningCorAnsLteqFilterHover_tip,
  198. };
  199. }
  200.  
  201. function meaningCorAnsLteqFilter(filterValue, item) {
  202. let review_statistics = item.review_statistics;
  203. if (review_statistics === undefined) {
  204. return false;
  205. };
  206. return review_statistics.meaning_correct <= filterValue;
  207. };
  208. // END Meaning correct answers
  209.  
  210. // BEGIN Reading correct answers
  211. let readingCorAnsGteqFilterHover_tip = 'Selects items where the number of correct\nanswers for Reading is &gt;= this value.';
  212. let readingCorAnsLteqFilterHover_tip = 'Selects items where the number of correct\nanswers for Reading is &lt;= this value.';
  213.  
  214. function registerReadingCorAnsGteqFilter() {
  215. wkof.ItemData.registry.sources.wk_items.filters[readingCorAnsGteqFilterName] = {
  216. type: 'number',
  217. label: 'Reading Cor. Ans. &gt;=',
  218. default: 0,
  219. filter_func: readingCorAnsGteqFilter,
  220. set_options: function(options) { options.review_statistics = true; },
  221. hover_tip: readingCorAnsGteqFilterHover_tip,
  222. };
  223. };
  224.  
  225. function readingCorAnsGteqFilter(filterValue, item) {
  226. let review_statistics = item.review_statistics;
  227. if (review_statistics === undefined) {
  228. return false;
  229. };
  230. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  231. return true;
  232. };
  233. return review_statistics.reading_correct >= filterValue;
  234. };
  235.  
  236. function registerReadingCorAnsLteqFilter() {
  237. wkof.ItemData.registry.sources.wk_items.filters[readingCorAnsLteqFilterName] = {
  238. type: 'number',
  239. label: 'Reading Cor. Ans. &lt;=',
  240. default: 100,
  241. filter_func: readingCorAnsLteqFilter,
  242. set_options: function(options) { options.review_statistics = true; },
  243. hover_tip: readingCorAnsLteqFilterHover_tip,
  244. };
  245. }
  246.  
  247. function readingCorAnsLteqFilter(filterValue, item) {
  248. let review_statistics = item.review_statistics;
  249. if (review_statistics === undefined) {
  250. return false;
  251. }
  252. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  253. return true;
  254. }
  255. return review_statistics.reading_correct <= filterValue;
  256. }
  257. // END Reading correct answers
  258.  
  259. // BEGIN Meaning incorrect answers
  260. let meaningIncorAnsGteqFilterHover_tip = 'Selects items where the number of incorrect\nanswers for Meaning is &gt;= this value.';
  261. let meaningIncorAnsLteqFilterHover_tip = 'Selects items where the number of incorrect\nanswers for Meaning is &lt;= this value.';
  262.  
  263. function registerMeaningIncorAnsGteqFilter() {
  264. wkof.ItemData.registry.sources.wk_items.filters[meaningIncorAnsGteqFilterName] = {
  265. type: 'number',
  266. label: 'Meaning Incor. Ans. &gt;=',
  267. default: 0,
  268. filter_func: meaningIncorAnsGteqFilter,
  269. set_options: function(options) { options.review_statistics = true; },
  270. hover_tip: meaningIncorAnsGteqFilterHover_tip,
  271. };
  272. }
  273.  
  274. function meaningIncorAnsGteqFilter(filterValue, item) {
  275. let review_statistics = item.review_statistics;
  276. if (review_statistics === undefined) {
  277. return false;
  278. }
  279. return review_statistics.meaning_incorrect >= filterValue;
  280. }
  281.  
  282. function registerMeaningIncorAnsLteqFilter() {
  283. wkof.ItemData.registry.sources.wk_items.filters[meaningIncorAnsLteqFilterName] = {
  284. type: 'number',
  285. label: 'Meaning Incor. Ans. &lt;=',
  286. default: 100,
  287. filter_func: meaningIncorAnsLteqFilter,
  288. set_options: function(options) { options.review_statistics = true; },
  289. hover_tip: meaningIncorAnsLteqFilterHover_tip,
  290. };
  291. }
  292.  
  293. function meaningIncorAnsLteqFilter(filterValue, item) {
  294. let review_statistics = item.review_statistics;
  295. if (review_statistics === undefined) {
  296. return false;
  297. }
  298. return review_statistics.meaning_incorrect <= filterValue;
  299. }
  300. // END Meaning incorrect answers
  301.  
  302. // BEGIN Reading incorrect answers
  303. let readingIncorAnsGteqFilterHover_tip = 'Selects items where the number of incorrect\nanswers for Reading is &gt;= this value.';
  304. let readingIncorAnsLteqFilterHover_tip = 'Selects items where the number of incorrect\nanswers for Reading is &lt;= this value.';
  305.  
  306. function registerReadingIncorAnsGteqFilter() {
  307. wkof.ItemData.registry.sources.wk_items.filters[readingIncorAnsGteqFilterName] = {
  308. type: 'number',
  309. label: 'Reading Incor. Ans. &gt;=',
  310. default: 0,
  311. filter_func: readingIncorAnsGteqFilter,
  312. set_options: function(options) { options.review_statistics = true; },
  313. hover_tip: readingIncorAnsGteqFilterHover_tip,
  314. };
  315. }
  316.  
  317. function readingIncorAnsGteqFilter(filterValue, item) {
  318. let review_statistics = item.review_statistics;
  319. if (review_statistics === undefined) {
  320. return false;
  321. }
  322. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  323. return true;
  324. }
  325. return review_statistics.reading_incorrect >= filterValue;
  326. }
  327.  
  328. function registerReadingIncorAnsLteqFilter() {
  329. wkof.ItemData.registry.sources.wk_items.filters[readingIncorAnsLteqFilterName] = {
  330. type: 'number',
  331. label: 'Reading Incor. Ans. &lt;=',
  332. default: 100,
  333. filter_func: readingIncorAnsLteqFilter,
  334. set_options: function(options) { options.review_statistics = true; },
  335. hover_tip: readingIncorAnsLteqFilterHover_tip,
  336. };
  337. }
  338.  
  339. function readingIncorAnsLteqFilter(filterValue, item) {
  340. let review_statistics = item.review_statistics;
  341. if (review_statistics === undefined) {
  342. return false;
  343. }
  344. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  345. return true;
  346. }
  347. return review_statistics.reading_incorrect <= filterValue;
  348. }
  349. // END Reading incorrect answers
  350.  
  351. // BEGIN Total correct answers
  352. let totalCorAnsGteqFilterHover_tip = 'Selects items where the total number\nof correct answers is &gt;= this value.';
  353. let totalCorAnsLteqFilterHover_tip = 'Selects items where the total number\nof correct answers is &lt;= this value.';
  354.  
  355. function registerTotalCorAnsGteqFilter() {
  356. wkof.ItemData.registry.sources.wk_items.filters[totalCorAnsGteqFilterName] = {
  357. type: 'number',
  358. label: 'Total Cor. Ans. &gt;=',
  359. default: 0,
  360. filter_func: totalCorAnsGteqFilter,
  361. set_options: function(options) { options.review_statistics = true; },
  362. hover_tip: totalCorAnsGteqFilterHover_tip,
  363. };
  364. }
  365.  
  366. function totalCorAnsGteqFilter(filterValue, item) {
  367. let review_statistics = item.review_statistics;
  368. if (review_statistics === undefined) {
  369. return false;
  370. }
  371. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  372. return review_statistics.meaning_correct >= filterValue;
  373. }
  374. return review_statistics.meaning_correct + review_statistics.reading_correct >= filterValue;
  375. }
  376.  
  377. function registerTotalCorAnsLteqFilter() {
  378. wkof.ItemData.registry.sources.wk_items.filters[totalCorAnsLteqFilterName] = {
  379. type: 'number',
  380. label: 'Total Cor. Ans. &lt;=',
  381. default: 100,
  382. filter_func: totalCorAnsLteqFilter,
  383. set_options: function(options) { options.review_statistics = true; },
  384. hover_tip: totalCorAnsLteqFilterHover_tip,
  385. };
  386. }
  387.  
  388. function totalCorAnsLteqFilter(filterValue, item) {
  389. let review_statistics = item.review_statistics;
  390. if (review_statistics === undefined) {
  391. return false;
  392. }
  393. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  394. return review_statistics.meaning_correct <= filterValue;
  395. }
  396. return review_statistics.meaning_correct + review_statistics.reading_correct <= filterValue;
  397. }
  398. // END Total correct answers
  399.  
  400. // BEGIN Number of reviews
  401. // The number of reviews is always equal to the total correct answers because
  402. // you need to answer correctly exactly once to terminate a review
  403. // Therefore this is the same filters as Total correct answer with a different user
  404. // interface to inform the user of what they are doing
  405.  
  406. let numberReviewsGteqFilterHover_tip = 'Selects items where the total number\nof reviews is &gt;= this value.';
  407. let numberReviewsLteqFilterHover_tip = 'Selects items where the total number\nof reviews is &lt;= this value.';
  408.  
  409. function registerNumberReviewsGteqFilter() {
  410. wkof.ItemData.registry.sources.wk_items.filters[numReviewGteqFilterName] = {
  411. type: 'number',
  412. label: 'Number of Reviews &gt;=',
  413. default: 0,
  414. filter_func: totalCorAnsGteqFilter,
  415. set_options: function(options) { options.review_statistics = true; },
  416. hover_tip: numberReviewsGteqFilterHover_tip,
  417. };
  418. }
  419.  
  420. function registerNumberReviewsLteqFilter() {
  421. wkof.ItemData.registry.sources.wk_items.filters[numReviewsLteqFilterName] = {
  422. type: 'number',
  423. label: 'Number of Reviews &lt;=',
  424. default: 100,
  425. filter_func: totalCorAnsLteqFilter,
  426. set_options: function(options) { options.review_statistics = true; },
  427. hover_tip: numberReviewsLteqFilterHover_tip,
  428. };
  429. }
  430.  
  431. // END Number of reviews
  432.  
  433. // BEGIN Total incorrect answers
  434. let totalIncorAnsGteqFilterHover_tip = 'Selects items where the total number\nof incorrect answers is &gt;= this value.';
  435. let totalIncorAnsLteqFilterHover_tip = 'Selects items where the total number\nof incorrect answers is &lt;= this value.';
  436.  
  437. function registerTotalIncorAnsGteqFilter() {
  438. wkof.ItemData.registry.sources.wk_items.filters[totalIncorAnsGteqFilterName] = {
  439. type: 'number',
  440. label: 'Total Incor. Ans. &gt;=',
  441. default: 0,
  442. filter_func: totalIncorAnsGteqFilter,
  443. set_options: function(options) { options.review_statistics = true; },
  444. hover_tip: totalIncorAnsGteqFilterHover_tip,
  445. };
  446. }
  447.  
  448. function totalIncorAnsGteqFilter(filterValue, item) {
  449. let review_statistics = item.review_statistics;
  450. if (review_statistics === undefined) {
  451. return false;
  452. }
  453. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  454. return review_statistics.meaning_incorrect >= filterValue;
  455. }
  456. return review_statistics.meaning_incorrect + review_statistics.reading_incorrect >= filterValue;
  457. }
  458.  
  459. function registerTotalIncorAnsLteqFilter() {
  460. wkof.ItemData.registry.sources.wk_items.filters[totalIncorAnsLteqFilterName] = {
  461. type: 'number',
  462. label: 'Total Incor. Ans. &lt;=',
  463. default: 100,
  464. filter_func: totalIncorAnsLteqFilter,
  465. set_options: function(options) { options.review_statistics = true; },
  466. hover_tip: totalIncorAnsLteqFilterHover_tip,
  467. };
  468. }
  469.  
  470. function totalIncorAnsLteqFilter(filterValue, item) {
  471. let review_statistics = item.review_statistics;
  472. if (review_statistics === undefined) {
  473. return false;
  474. }
  475. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  476. return review_statistics.meaning_incorrect <= filterValue;
  477. }
  478. return review_statistics.meaning_incorrect + review_statistics.reading_incorrect <= filterValue;
  479. }
  480. // END Total incorrect answers
  481.  
  482. //==============================================
  483. // Percentages filters
  484. //
  485. // Needs rounding because otherwise user facing data is usually floored and inconsistencies in displayed data will occur
  486. // Flooring is used instead of rounding because otherwise we could have 100% correct while missing some answers due to rounding
  487.  
  488. function percentageRounding(value){return Math.floor(value * 100)};
  489.  
  490. // BEGIN Percentage Total Correct
  491. let percentageTotalCorGteqFilterHover_tip = 'Selects items where % of correct answers\non Total is &gt;= this value.';
  492. let percentageTotalCorLteqFilterHover_tip = 'Selects items where % of correct answers\non Total is &lt;= this value.';
  493.  
  494. function registerPercentageTotalCorGteqFilter() {
  495. wkof.ItemData.registry.sources.wk_items.filters[percentageTotalCorGteqFilterName] = {
  496. type: 'number',
  497. label: '%Total Correct &gt;=',
  498. default: 0,
  499. filter_func: percentageTotalCorGteqFilter,
  500. set_options: function(options) { options.review_statistics = true; },
  501. hover_tip: percentageTotalCorGteqFilterHover_tip,
  502. };
  503. }
  504.  
  505. function percentageTotalCorGteqFilter(filterValue, item) {
  506. let review_statistics = item.review_statistics;
  507. if (review_statistics === undefined) {
  508. return false;
  509. }
  510. return review_statistics.percentage_correct >= filterValue;
  511. }
  512.  
  513. function registerPercentageTotalCorLteqFilter() {
  514. wkof.ItemData.registry.sources.wk_items.filters[percentageTotalCorLteqFilterName] = {
  515. type: 'number',
  516. label: '%Total Correct &lt;=',
  517. default: 100,
  518. filter_func: percentageTotalCorLteqFilter,
  519. set_options: function(options) { options.review_statistics = true; },
  520. hover_tip: percentageTotalCorLteqFilterHover_tip,
  521. };
  522. }
  523.  
  524. function percentageTotalCorLteqFilter(filterValue, item) {
  525. let review_statistics = item.review_statistics;
  526. if (review_statistics === undefined) {
  527. return false;
  528. }
  529. return review_statistics.percentage_correct <= filterValue;
  530. }
  531. // END Percentage Total Correct
  532.  
  533. // BEGIN Percentage Meaning Correct
  534. let percentageMeaningCorGteqFilterHover_tip = 'Selects items where % of correct answers\non Meaning is &gt;= this value.';
  535. let percentageMeaningCorLteqFilterHover_tip = 'Selects items where % of correct answers\non Meaning is &lt;= this value.';
  536.  
  537. function registerPercentageMeaningCorGteqFilter() {
  538. wkof.ItemData.registry.sources.wk_items.filters[percentageMeaningCorGteqFilterName] = {
  539. type: 'number',
  540. label: '%Meaning Correct &gt;=',
  541. default: 0,
  542. filter_func: percentageMeaningCorGteqFilter,
  543. set_options: function(options) { options.review_statistics = true; },
  544. hover_tip: percentageMeaningCorGteqFilterHover_tip,
  545. };
  546. }
  547.  
  548. function percentageMeaningCorGteqFilter(filterValue, item) {
  549. let review_statistics = item.review_statistics;
  550. if (review_statistics === undefined) {
  551. return false;
  552. }
  553. return percentageRounding(review_statistics.meaning_correct / (review_statistics.meaning_correct + review_statistics.meaning_incorrect)) >= filterValue;
  554. }
  555.  
  556. function registerPercentageMeaningCorLteqFilter() {
  557. wkof.ItemData.registry.sources.wk_items.filters[percentageMeaningCorLteqFilterName] = {
  558. type: 'number',
  559. label: '%Meaning Correct &lt;=',
  560. default: 100,
  561. filter_func: percentageMeaningCorLteqFilter,
  562. set_options: function(options) { options.review_statistics = true; },
  563. hover_tip: percentageMeaningCorLteqFilterHover_tip,
  564. };
  565. }
  566.  
  567. function percentageMeaningCorLteqFilter(filterValue, item) {
  568. let review_statistics = item.review_statistics;
  569. if (review_statistics === undefined) {
  570. return false;
  571. }
  572. return percentageRounding(review_statistics.meaning_correct / (review_statistics.meaning_correct + review_statistics.meaning_incorrect)) <= filterValue;
  573. }
  574. // END Percentage Meaning Correct
  575.  
  576. // BEGIN Percentage Reading Correct
  577. let percentageReadingCorGteqFilterHover_tip = 'Selects items where % of correct answers\non Reading is &gt;= this value.';
  578. let percentageReadingCorLteqFilterHover_tip = 'Selects items where % of correct answers\non Reading is &lt;= this value.';
  579.  
  580. function registerPercentageReadingCorGteqFilter() {
  581. wkof.ItemData.registry.sources.wk_items.filters[percentageReadingCorGteqFilterName] = {
  582. type: 'number',
  583. label: '%Reading Correct &gt;=',
  584. default: 0,
  585. filter_func: percentageReadingCorGteqFilter,
  586. set_options: function(options) { options.review_statistics = true; },
  587. hover_tip: percentageReadingCorGteqFilterHover_tip,
  588. };
  589. }
  590.  
  591. function percentageReadingCorGteqFilter(filterValue, item) {
  592. let review_statistics = item.review_statistics;
  593. if (review_statistics === undefined) {
  594. return false;
  595. }
  596. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  597. return true;
  598. }
  599. return percentageRounding(review_statistics.reading_correct / (review_statistics.reading_correct + review_statistics.reading_incorrect)) >= filterValue;
  600. }
  601.  
  602. function registerPercentageReadingCorLteqFilter() {
  603. wkof.ItemData.registry.sources.wk_items.filters[percentageReadingCorLteqFilterName] = {
  604. type: 'number',
  605. label: '%Reading Correct &lt;=',
  606. default: 100,
  607. filter_func: percentageReadingCorLteqFilter,
  608. set_options: function(options) { options.review_statistics = true; },
  609. hover_tip: percentageReadingCorLteqFilterHover_tip,
  610. };
  611. }
  612.  
  613. function percentageReadingCorLteqFilter(filterValue, item) {
  614. let review_statistics = item.review_statistics;
  615. if (review_statistics === undefined) {
  616. return false;
  617. }
  618. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  619. return true;
  620. }
  621. return percentageRounding(review_statistics.reading_correct / (review_statistics.reading_correct + review_statistics.reading_incorrect)) <= filterValue;
  622. }
  623. // END Percentage Reading Correct
  624.  
  625. //======================================================
  626. // Streak Filters
  627. //
  628. // Wanikani starts the streak count at 1, that is when the last review is failed, the streak is 1, not 0.
  629. // User facing data is adjusted in Item Inspector to start the streak count at 0 because starting at 1 is not intuitive.
  630. // The filters do so too because the filter value in the settings panel is also user facing data that may be correlated with Item Inspector data.
  631. // This is done by adjusting the filter value through passing streakValueAdjustment to the filter_value_map: registering option.
  632. // This adjustment is not made in leech calculations because a- this use of streaks is not user facing data - and b- there is an history of
  633. // calculating leech values this way and it would be disruptive to change it.
  634.  
  635. function streakValueAdjustment(param){return param + 1};
  636.  
  637.  
  638. // BEGIN Meaning Current Streak
  639. let meaningCurStrGteqFilterHover_tip = 'Selects items where current Meaning streak is &gt;= this value.';
  640. let meaningCurStrLteqFilterHover_tip = 'Selects items where current Meaning streak is &lt;= this value.';
  641.  
  642. function registerMeaningCurStrGteqFilter() {
  643. wkof.ItemData.registry.sources.wk_items.filters[meaningCurStrGteqFilterName] = {
  644. type: 'number',
  645. label: 'Meaning Cur. Str. &gt;=',
  646. default: 0,
  647. filter_value_map: streakValueAdjustment,
  648. filter_func: meaningCurStrGteqFilter,
  649. set_options: function(options) { options.review_statistics = true; },
  650. hover_tip: meaningCurStrGteqFilterHover_tip,
  651. };
  652. }
  653.  
  654. function meaningCurStrGteqFilter(filterValue, item) {
  655. let review_statistics = item.review_statistics;
  656. if (review_statistics === undefined) {
  657. return false;
  658. }
  659. return review_statistics.meaning_current_streak >= filterValue;
  660. }
  661.  
  662. function registerMeaningCurStrLteqFilter() {
  663. wkof.ItemData.registry.sources.wk_items.filters[meaningCurStrLteqFilterName] = {
  664. type: 'number',
  665. label: 'Meaning Cur. Str. &lt;=',
  666. default: 100,
  667. filter_value_map: streakValueAdjustment,
  668. filter_func: meaningCurStrLteqFilter,
  669. set_options: function(options) { options.review_statistics = true; },
  670. hover_tip: meaningCurStrLteqFilterHover_tip,
  671. };
  672. }
  673.  
  674. function meaningCurStrLteqFilter(filterValue, item) {
  675. let review_statistics = item.review_statistics;
  676. if (review_statistics === undefined) {
  677. return false;
  678. }
  679. return review_statistics.meaning_current_streak <= filterValue;
  680. }
  681. // END Meaning Current Streak
  682.  
  683. // BEGIN Meaning Maximum Streak
  684. let meaningMaxStrGteqFilterHover_tip = 'Selects items where maximum Meaning streak is &gt;= this value.';
  685. let meaningMaxStrLteqFilterHover_tip = 'Selects items where maximum Meaning streak is &lt;= this value.';
  686.  
  687. function registerMeaningMaxStrGteqFilter() {
  688. wkof.ItemData.registry.sources.wk_items.filters[meaningMaxStrGteqFilterName] = {
  689. type: 'number',
  690. label: 'Meaning Max. Str. &gt;=',
  691. default: 0,
  692. filter_value_map: streakValueAdjustment,
  693. filter_func: meaningMaxStrGteqFilter,
  694. set_options: function(options) { options.review_statistics = true; },
  695. hover_tip: meaningMaxStrGteqFilterHover_tip,
  696. };
  697. }
  698.  
  699. function meaningMaxStrGteqFilter(filterValue, item) {
  700. let review_statistics = item.review_statistics;
  701. if (review_statistics === undefined) {
  702. return false;
  703. }
  704. return review_statistics.meaning_max_streak >= filterValue;
  705. }
  706.  
  707. function registerMeaningMaxStrLteqFilter() {
  708. wkof.ItemData.registry.sources.wk_items.filters[meaningMaxStrLteqFilterName] = {
  709. type: 'number',
  710. label: 'Meaning Max. Str. &lt;=',
  711. default: 100,
  712. filter_value_map: streakValueAdjustment,
  713. filter_func: meaningMaxStrLteqFilter,
  714. set_options: function(options) { options.review_statistics = true; },
  715. hover_tip: meaningMaxStrLteqFilterHover_tip,
  716. };
  717. }
  718.  
  719. function meaningMaxStrLteqFilter(filterValue, item) {
  720. let review_statistics = item.review_statistics;
  721. if (review_statistics === undefined) {
  722. return false;
  723. }
  724. return review_statistics.meaning_max_streak <= filterValue;
  725. }
  726. // END Meaning Maximum Streak
  727.  
  728. // BEGIN Reading Current Streak
  729. let readingCurStrGteqFilterHover_tip = 'Selects items where current Reading streak is &gt;= this value.';
  730. let readingCurStrLteqFilterHover_tip = 'Selects items where current Reading streak is &lt;= this value.';
  731.  
  732. function registerReadingCurStrGteqFilter() {
  733. wkof.ItemData.registry.sources.wk_items.filters[readingCurStrGteqFilterName] = {
  734. type: 'number',
  735. label: 'Reading Cur. Str. &gt;=',
  736. default: 0,
  737. filter_value_map: streakValueAdjustment,
  738. filter_func: readingCurStrGteqFilter,
  739. set_options: function(options) { options.review_statistics = true; },
  740. hover_tip: readingCurStrGteqFilterHover_tip,
  741. };
  742. }
  743.  
  744. function readingCurStrGteqFilter(filterValue, item) {
  745. let review_statistics = item.review_statistics;
  746. if (review_statistics === undefined) {
  747. return false;
  748. }
  749. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  750. return true;
  751. }
  752. return review_statistics.reading_current_streak >= filterValue;
  753. }
  754.  
  755. function registerReadingCurStrLteqFilter() {
  756. wkof.ItemData.registry.sources.wk_items.filters[readingCurStrLteqFilterName] = {
  757. type: 'number',
  758. label: 'Reading Cur. Str. &lt;=',
  759. default: 100,
  760. filter_value_map: streakValueAdjustment,
  761. filter_func: readingCurStrLteqFilter,
  762. set_options: function(options) { options.review_statistics = true; },
  763. hover_tip: readingCurStrLteqFilterHover_tip,
  764. };
  765. }
  766.  
  767. function readingCurStrLteqFilter(filterValue, item) {
  768. let review_statistics = item.review_statistics;
  769. if (review_statistics === undefined) {
  770. return false;
  771. }
  772. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  773. return true;
  774. }
  775. return review_statistics.reading_current_streak <= filterValue;
  776. }
  777. // END Reading Current Streak
  778.  
  779. // BEGIN Reading Maximum Streak
  780. let readingMaxStrGteqFilterHover_tip = 'Selects items where maximum Reading streak is &gt;= this value.';
  781. let readingMaxStrLteqFilterHover_tip = 'Selects items where maximum Reading streak is &lt;= this value.';
  782.  
  783. function registerReadingMaxStrGteqFilter() {
  784. wkof.ItemData.registry.sources.wk_items.filters[readingMaxStrGteqFilterName] = {
  785. type: 'number',
  786. label: 'Reading Max. Str. &gt;=',
  787. default: 0,
  788. filter_value_map: streakValueAdjustment,
  789. filter_func: readingMaxStrGteqFilter,
  790. set_options: function(options) { options.review_statistics = true; },
  791. hover_tip: readingMaxStrGteqFilterHover_tip,
  792. };
  793. }
  794.  
  795. function readingMaxStrGteqFilter(filterValue, item) {
  796. let review_statistics = item.review_statistics;
  797. if (review_statistics === undefined) {
  798. return false;
  799. }
  800. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  801. return true;
  802. }
  803. return review_statistics.reading_max_streak >= filterValue;
  804. }
  805.  
  806. function registerReadingMaxStrLteqFilter() {
  807. wkof.ItemData.registry.sources.wk_items.filters[readingMaxStrLteqFilterName] = {
  808. type: 'number',
  809. label: 'Reading Max. Str. &lt;=',
  810. default: 100,
  811. filter_value_map: streakValueAdjustment,
  812. filter_func: readingMaxStrLteqFilter,
  813. set_options: function(options) { options.review_statistics = true; },
  814. hover_tip: readingMaxStrLteqFilterHover_tip,
  815. };
  816. }
  817.  
  818. function readingMaxStrLteqFilter(filterValue, item) {
  819. let review_statistics = item.review_statistics;
  820. if (review_statistics === undefined) {
  821. return false;
  822. }
  823. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  824. return true;
  825. }
  826. return review_statistics.reading_max_streak <= filterValue;
  827. }
  828. // END Reading Maximum Streak
  829.  
  830. // BEGIN Both Current Streaks
  831. let bothCurStrGteqFilterHover_tip = 'Selects items where both current Meaning streak\nand current Reading streak are &gt;= this value.';
  832. let bothCurStrLteqFilterHover_tip = 'Selects items where both current Meaning streak\nand current Reading streak are &lt;= this value.';
  833.  
  834. function registerBothCurStrGteqFilter() {
  835. wkof.ItemData.registry.sources.wk_items.filters[bothCurStrGteqFilterName] = {
  836. type: 'number',
  837. label: 'Both Cur. Str. &gt;=',
  838. default: 0,
  839. filter_value_map: streakValueAdjustment,
  840. filter_func: bothCurStrGteqFilter,
  841. set_options: function(options) { options.review_statistics = true; },
  842. hover_tip: bothCurStrGteqFilterHover_tip,
  843. };
  844. }
  845.  
  846. function bothCurStrGteqFilter(filterValue, item) {
  847. let review_statistics = item.review_statistics;
  848. if (review_statistics === undefined) {
  849. return false;
  850. }
  851. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  852. return review_statistics.meaning_current_streak >= filterValue;
  853. } else {
  854. return review_statistics.meaning_current_streak >= filterValue && review_statistics.reading_current_streak >= filterValue;
  855. }
  856. }
  857.  
  858. function registerBothCurStrLteqFilter() {
  859. wkof.ItemData.registry.sources.wk_items.filters[bothCurStrLteqFilterName] = {
  860. type: 'number',
  861. label: 'Both Cur. Str. &lt;=',
  862. default: 100,
  863. filter_value_map: streakValueAdjustment,
  864. filter_func: bothCurStrLteqFilter,
  865. set_options: function(options) { options.review_statistics = true; },
  866. hover_tip: bothCurStrLteqFilterHover_tip,
  867. };
  868. }
  869.  
  870. function bothCurStrLteqFilter(filterValue, item) {
  871. let review_statistics = item.review_statistics;
  872. if (review_statistics === undefined) {
  873. return false;
  874. }
  875. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  876. return review_statistics.meaning_current_streak <= filterValue;
  877. } else {
  878. return review_statistics.meaning_current_streak <= filterValue && review_statistics.reading_current_streak <= filterValue;
  879. }
  880. }
  881. // END Both Current Streaks
  882.  
  883. // BEGIN Minimum Current Streaks
  884. let minCurStrGteqFilterHover_tip = 'Selects items where the minimum of current Meaning streak\nand current Reading streak is &gt;= this value.';
  885. let minCurStrLteqFilterHover_tip = 'Selects items where the minimum of current Meaning streak\nand current Reading streak is &lt;= this value.';
  886.  
  887. function registerMinCurStrGteqFilter() {
  888. wkof.ItemData.registry.sources.wk_items.filters[minCurStrGteqFilterName] = {
  889. type: 'number',
  890. label: 'Minimum Cur. Str. &gt;=',
  891. default: 0,
  892. filter_value_map: streakValueAdjustment,
  893. filter_func: minCurStrGteqFilter,
  894. set_options: function(options) { options.review_statistics = true; },
  895. hover_tip: minCurStrGteqFilterHover_tip,
  896. };
  897. }
  898.  
  899. function minCurStrGteqFilter(filterValue, item) {
  900. let review_statistics = item.review_statistics;
  901. if (review_statistics === undefined) {
  902. return false;
  903. }
  904. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  905. return review_statistics.meaning_current_streak >= filterValue;
  906. } else {
  907. return review_statistics.meaning_current_streak >= filterValue && review_statistics.reading_current_streak >= filterValue;
  908. };
  909. }
  910.  
  911. function registerMinCurStrLteqFilter() {
  912. wkof.ItemData.registry.sources.wk_items.filters[minCurStrLteqFilterName] = {
  913. type: 'number',
  914. label: 'Minimum Cur. Str. &lt;=',
  915. default: 100,
  916. filter_value_map: streakValueAdjustment,
  917. filter_func: minCurStrLteqFilter,
  918. set_options: function(options) { options.review_statistics = true; },
  919. hover_tip: minCurStrLteqFilterHover_tip,
  920. };
  921. }
  922.  
  923. function minCurStrLteqFilter(filterValue, item) {
  924. let review_statistics = item.review_statistics;
  925. if (review_statistics === undefined) {
  926. return false;
  927. }
  928. if (item.object === 'radical' || item.object === 'kana_vocabulary'){
  929. return review_statistics.meaning_current_streak <= filterValue;
  930. } else {
  931. return review_statistics.meaning_current_streak <= filterValue || review_statistics.reading_current_streak <= filterValue;
  932. }
  933. }
  934. // END Minimum Current Streaks
  935.  
  936. updateFiltersWhenReady();
  937. })(window.wkof);