WK Flagger

Add coloured flags to reviews as a memorization aid

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         WK Flagger
// @namespace    http://tampermonkey.net/
// @version      2023-12-29
// @description  Add coloured flags to reviews as a memorization aid
// @author       Gorbit99
// @match        https://www.wanikani.com/*
// @match        https://preview.wanikani.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      MIT
// ==/UserScript==

(function(wkof) {
  'use strict';

  const cacheFilename = "wkFlaggerData";
  let wkFlaggerData = {};
  const availableFlags = {
    red: "#f44",
    green: "#4f4",
    blue: "#44f",
    yellow: "#ff4",
    turqoise: "mediumspringgreen",
  };

  // Stuff I'm sure I'll need to change about 50 times in the future
  const statisticsClass = ".character-header__menu-statistics";
  const characterTextClass = ".character-header__characters";

  if (!window.wkof) {
    alert('WK Flagger requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.');
    window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
    return;
  }

  if (window.location.pathname === "/subjects/review") {
     createFlagUI();
  }

  wkof.file_cache.load(cacheFilename).then((data) => (wkFlaggerData = data))
    .catch(() => wkof.file_cache.save(cacheFilename, {}))
    .then(() => updateShownFlag());

  function updateShownFlag() {

    const itemId = getCurrentItemId();
    const flagElement = document.querySelector(".wk-flagger__flag--button");

    const color = wkFlaggerData[itemId];
    flagElement.classList.remove(...flagElement.classList);
    flagElement.classList.add("fa-flag", "wk-flagger__flag", "wk-flagger__flag--button");
    if (color === undefined) {
      flagElement.classList.add("fa-regular");
    } else {
      flagElement.classList.add("fa-solid", `wk-flagger__flag--${color}`);
    }
  }

  function createFlagUI() {
    if (window.location.pathname !== "/subjects/review") {
      return;
    }

    if (document.querySelector(".wk-flagger__wrapper")) {
      return;
    }

    const statisticsElement = document.querySelector(statisticsClass);

    const flagElementWrapper = document.createElement("div");
    flagElementWrapper.classList.add("wk-flagger__wrapper");

    const createFlag = (type, className) => {
      const flagElement = document.createElement("i");
      flagElement.classList.add(`fa-${type}`, "fa-flag", "wk-flagger__flag");
      if (className) {
        flagElement.classList.add(`wk-flagger__flag--${className}`);
      }
      return flagElement;
    };

    const flagElement = createFlag("regular", "button");
    flagElement.addEventListener("click", () => toggleDropdown());

    flagElementWrapper.append(flagElement);
    statisticsElement.prepend(flagElementWrapper);

    const dropdownElement = document.createElement("div");
    dropdownElement.classList.add("wk-flagger__dropdown");
    flagElementWrapper.append(dropdownElement);

    const dropdownNoFlag = createFlag("regular", "no-flag");
    dropdownNoFlag.addEventListener("click", () => {
        setItemFlag(undefined);
        toggleDropdown(false);
        updateShownFlag();
    });
    const dropdownColoredFlags = Object.keys(availableFlags).map((color) => {
        const flag = createFlag("solid", color)
        flag.addEventListener("click", () => {
          setItemFlag(color);
          toggleDropdown(false);
          updateShownFlag();
        });

        return flag;
      }
    );

    dropdownElement.append(dropdownNoFlag, ...dropdownColoredFlags);

    insertCss();

    const changeObserver = new MutationObserver(updateShownFlag);
    changeObserver.observe(
      document.querySelector(characterTextClass),
      {characterData: true, childList: true},
    );
  }

  function insertCss() {
    const flagColors = Object.entries(availableFlags)
      .map((value) => `${statisticsClass} .wk-flagger__wrapper .wk-flagger__flag--${value[0]} {
        color: ${value[1]};
      }`)
      .join("\n");

    const css = `
    .wk-flagger__wrapper {
      margin-right: 1em;
    }

    .wk-flagger__wrapper .wk-flagger__flag {
      cursor: pointer;
    }

    .wk-flagger__wrapper .wk-flagger__flag:hover {
      opacity: 0.6;
    }

    .wk-flagger__dropdown {
      position: absolute;
      display: none;
      flex-direction: column;
      gap: 0.5em;
      margin-top: 0.5em;
      padding: 0.5em;
      border-radius: 0.25em;
      z-index:9999;

      left: 50%;
      transform: translateX(-50%);

      background: var(--color-menu, #333);
    }

    .wk-flagger__dropdown.wk-flagger__dropdown--shown {
      display: flex;
    }

    ${flagColors}
    `;

    const styleElement = document.createElement("style");
    styleElement.innerHTML = css;
    document.head.append(styleElement);
  }

  function toggleDropdown(state = undefined) {
    const dropdownElement = document.querySelector(".wk-flagger__dropdown");
    if (!dropdownElement) {
      return;
    }

    dropdownElement.classList.toggle("wk-flagger__dropdown--shown", state);
  }

  function setItemFlag(flagColor) {
    const currentId = getCurrentItemId();
    if (flagColor) {
      wkFlaggerData[currentId] = flagColor;
    } else {
      delete wkFlaggerData[currentId];
    }
    wkof.file_cache.save(cacheFilename, wkFlaggerData);

    updateShownFlag();
  }

  function getCurrentItemId() {
    // Stolen straight from Sinyaven's awesome item info injector script - thx
    const subjects = JSON.parse(document.querySelector(`[data-quiz-queue-target="subjects"]`).textContent);
		const currentId = parseInt(document.querySelector(`[data-subject-id]`)?.dataset.subjectId ?? subjects[0]?.id);

    return currentId;
  }

  document.addEventListener("turbo:load", createFlagUI());
})(window.wkof);