WaniKani Open Framework Statistics Filters

Additional statistics filters for the WaniKani Open Framework

当前为 2020-11-12 提交的版本,查看 最新版本

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