Araigoshi's Wanikani Stage Breakdown

Show SRS and leech breakdown on dashboard

当前为 2024-07-17 提交的版本,查看 最新版本

// ==UserScript==
// @name          Araigoshi's Wanikani Stage Breakdown
// @namespace     https://www.wanikani.com
// @description   Show SRS and leech breakdown on dashboard
// @author        araigoshi
// @version       1.0.1
// @include       https://www.wanikani.com/dashboard
// @include       https://www.wanikani.com/
// @license       MIT
// @grant         none
// ==/UserScript==

(async function () {
  'use strict';

  if (!window.wkof) {
    let response = confirm('WaniKani Dashboard SRS and Leech Breakdown script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');

    if (response) {
      window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
    }

    return;
  }

  let style =
    `<style>
        .dashboard section.srs-progress .srs-progress-details-group {
            display: flex;
            margin: 0;
            list-style-type: none;
            gap: 0.3em;
            justify-content: center;
            padding-top: var(--spacing-xtight);
        }

        .dashboard section.srs-progress li.grouped-list {
            padding: var(--spacing-xtight) 0.3em;
            border-radius: 5px;
            background-color: white;
            color: black;
            flex-grow: 1;
            flex-basis: 0;
            display: flex;
            justify-content: space-between;
        }

        .dashboard section.srs-progress li.grouped-list:hover {
            background-color: #f88;
        }

        .dashboard section.srs-progress li.grouped-list .group-item-label {
            flex-grow: 1;
        }

        .dashboard section.srs-progress li.grouped-list:hover .group-item-label {
            content: 'L';
        }

        .dashboard section.srs-progress li.grouped-list .group-item-value {
            font-weight: bold;
        }

        .dashboard section.srs-progress li.grouped-list:hover .group-item-value {
            display: none;
        }

        .dashboard section.srs-progress li.grouped-list .group-item-leeches {
            font-weight: bold;
            display: none;
        }

        .dashboard section.srs-progress li.grouped-list:hover .group-item-leeches {
            display: initial;
        }
    </style>`

  $('head').append(style);

  const leechThreshold = 1;
  const config = {
    wk_items: {
      options: {
        review_statistics: true,
        assignments: true
      }
    }
  };

  wkof.include('ItemData');
  wkof.include('Menu');
  wkof.include('Settings');
  await wkof.ready('Menu,ItemData,Settings');
  wkof.Settings.load('araistages', {
    compactItemTypes: true
  });

  let items = await wkof.ItemData.get_items(config);
  let filteredItems = items.filter(item => item?.assignments?.srs_stage > 0);
  let groupedItems = mapItemsToSrs(filteredItems);
  updatePage(groupedItems);

  wkof.Menu.insert_script_link({
    name: 'araistages_settings_open',
    submenu: 'Settings',
    title: "Araigoshi's Dashboard Stage Breakdown",
    on_click() {
      new wkof.Settings({
        script_id: 'araistages',
        title: "Araigoshi's Dashboard Stage Breakdown",
        content: {
          compactItemTypes: {
            type: 'checkbox',
            label: 'Compact Item Types',
          }
        }
      }).open()
    }
  });

  function mapItemsToSrs(items) {
    let itemsBySrs = [1, 2, 3, 4, 5, 6, 7, 8, 9].reduce((result, srs) => {
      result[srs] = {
        totals: {
          all: 0,
          radical: 0,
          vocabulary: 0,
          kanji: 0,
        },
        leeches: {
          all: 0,
          radical: 0,
          vocabulary: 0,
          kanji: 0,
        }
      };

      return result;
    }, {});

    items.forEach(function (item) {
      let srsStage = item.assignments.srs_stage;
      let subjectType = item.assignments.subject_type;
      if (subjectType === 'kana_vocabulary') {
        subjectType = 'vocabulary'
      }
      itemsBySrs[srsStage].totals[subjectType]++;
      itemsBySrs[srsStage].totals.all++;

      if (isLeech(item)) {
        itemsBySrs[srsStage].leeches[subjectType]++;
        itemsBySrs[srsStage].leeches.all++;
      }
    });

    return itemsBySrs;
  }

  function isLeech(item) {
    if (item.review_statistics === undefined) {
      return false;
    }

    let reviewStats = item.review_statistics;
    let meaningScore = getLeechScore(reviewStats.meaning_incorrect, reviewStats.meaning_current_streak);
    let readingScore = getLeechScore(reviewStats.reading_incorrect, reviewStats.reading_current_streak);

    return meaningScore >= leechThreshold || readingScore >= leechThreshold;
  }

  function getLeechScore(incorrect, currentStreak) {
    return incorrect / Math.pow((currentStreak || 0.5), 1.5);
  }

  function updatePage(itemsBySrs) {
    replaceTypes('apprentice', itemsBySrs, [1, 2, 3, 4]);
    addSubStages('apprentice', itemsBySrs, [1, 2, 3, 4]);
    replaceTypes('guru', itemsBySrs, [5, 6]);
    addSubStages('guru', itemsBySrs, [5, 6]);
    replaceTypes('master', itemsBySrs, [7]);
    replaceTypes('enlightened', itemsBySrs, [8]);
    replaceTypes('burned', itemsBySrs, [9]);
  }

  function makeGroupedListItem(labelText, valueCount, leechesCount, color) {
    let li = document.createElement('li');
    li.classList.add('grouped-list');

    let label = document.createElement('span');
    label.classList.add('group-item-label');
    label.textContent = labelText;
    li.appendChild(label);

    let value = document.createElement('span');
    value.classList.add('group-item-value');
    value.textContent = valueCount;
    value.style.color = color;
    li.appendChild(value);

    let leeches = document.createElement('span');
    leeches.textContent = leechesCount;
    leeches.classList.add('group-item-leeches');
    li.appendChild(leeches);

    return li;
  }

  function addSubStages(srsSectionId, itemsBySrs, srsLevelsArray) {
    let romanNumerals = ['I', 'II', 'III', 'IV', 'V'];
    let items = srsLevelsArray.map((srs, i) => makeGroupedListItem(
      romanNumerals[i],
      itemsBySrs[srs].totals.all,
      itemsBySrs[srs].leeches.all,
      `var(--color-srs-progress-${srsSectionId})`
    ));
    let wrapper = document.createElement('ol');
    wrapper.classList.add('srs-progress-details-group');
    for (let item of items) {
      wrapper.appendChild(item);
    };
    document.querySelector(`.srs-progress__stage--${srsSectionId}`).appendChild(wrapper);
  }

  function sumAll(itemsBySrs, srsLevelsArray, group, field) {
    return srsLevelsArray.reduce((n, stage) => n + itemsBySrs[stage][group][field], 0);
  }

  function makeTypeListItem(itemsBySrs, srsLevelsArray, abbr, itemType, color) {
    return makeGroupedListItem(
      abbr,
      sumAll(itemsBySrs, srsLevelsArray, 'totals', itemType),
      sumAll(itemsBySrs, srsLevelsArray, 'leeches', itemType),
      color
    );
  }

  function replaceTypes(srsSectionId, itemsBySrs, srsLevelsArray) {
    if (!wkof.settings.araistages?.compactItemTypes) {
      return;
    }
    let color = `var(--color-srs-progress-${srsSectionId})`;
    let items = [
      makeTypeListItem(itemsBySrs, srsLevelsArray, 'R', 'radical', color),
      makeTypeListItem(itemsBySrs, srsLevelsArray, 'K', 'kanji', color),
      makeTypeListItem(itemsBySrs, srsLevelsArray, 'V', 'vocabulary', color),
    ];
    let wrapper = document.createElement('ol');
    wrapper.classList.add('srs-progress-details-group');
    for (let item of items) {
      wrapper.appendChild(item);
    };
    document.querySelector(`.srs-progress__stage--${srsSectionId} .srs-progress__subject-types`).style.display = 'none';
    document.querySelector(`.srs-progress__stage--${srsSectionId}`).appendChild(wrapper);
  }
})();

QingJ © 2025

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