WK Read that if you can

Change font and font sizing for wanikani reviews

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         WK Read that if you can
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Change font and font sizing for wanikani reviews
// @author       Gorbit99
// @include      /^https:\/\/(preview\.|www\.)wanikani.com\/(review|extra_study|lesson)\/session.*$/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wanikani.com
// @grant        none
// @require      https://greasyfork.org/scripts/441792-cidwwa/code/CIDWWA.js?version=1031511
// @license      MIT
// @run-at       document-body
// ==/UserScript==
"use strict";

(function() {
  const googleApiLink = document.createElement("link");
  googleApiLink.rel = "preconnect";
  googleApiLink.href = "https://fonts.googleapis.com";
  const gstaticLink = document.createElement("link");
  gstaticLink.rel = "preconnect";
  gstaticLink.href = "https://fonts.gstatic.com";
  gstaticLink.crossOrigin = true;
  document.head.append(googleApiLink);
  document.head.append(gstaticLink);

  function addFontToSite(font) {
    const link = document.createElement("link");
    link.href =
      "https://fonts.googleapis.com/css?family=" +
      font.family.replace(/ /g, "+") +
      "&subset=japanese";
    link.rel = "stylesheet";
    document.head.append(link);
  }

  let settings = {
    googleFonts: {},
    fontMin: 15,
    fontMax: 60,
  };

  const loadedSettings = window.localStorage.getItem("read-that.settings");

  const fontCache = JSON.parse(
    window.localStorage.getItem("read-that.cache") ?? "{}"
  );

  for (let key in fontCache) {
    addFontToSite(fontCache[key]);
  }

  if (loadedSettings) {
    settings = JSON.parse(loadedSettings);
  }

  function saveSettings() {
    window.localStorage.setItem("read-that.settings", JSON.stringify(settings));
  }

  const modal = window.createModal({
    title: "Read That Settings",
  });

  function populateModal() {
    const containerStyle = `
      max-height:60vh;
      overflow-y:auto;
      width:1000px;
    `;

    const googleFontsStyle = `
      overflow-y:auto;
      border:1px solid grey;
      max-height:20em;
      min-height:10em;
      padding:0.5rem;
    `;

    const fontSizeStyle = `
      display:flex;
      justify-content:center;
      gap:2em;
      align-items:end;
    `;

    const onlineFontsStyle = `
      overflow-y:auto;
      border:1px solid grey;
      max-height:20em;
      min-height:10em;
      padding:0.5rem;
    `;

    modal.setContent(`
      <div class="read-that-settings" style="${containerStyle}">
        <h3>Font Size</h3>
        <div style="${fontSizeStyle}">
          <input type="number" class="read-that-font-min" 
            value="${settings.fontMin}" style="width:5ch"/>
          <span class="read-that-font-min-example" 
            style="font-size:${settings.fontMin}px">小</span>
          <span class="read-that-font-max-example" 
            style="font-size:${settings.fontMax}px">大</span>
          <input type="number" class="read-that-font-max" 
            value="${settings.fontMax}" style="width:5ch"/>
        </div>
        <h3>Google Fonts</h3>
        <div class="read-that-google-fonts" style="${googleFontsStyle}"></div>
      </div>
    `);

    loadGoogleFonts().then();

    const fontMin = document.querySelector(".read-that-font-min");
    const fontMax = document.querySelector(".read-that-font-max");

    modal.onClose(() => {
      [...document.querySelectorAll(".read-that-google-selector")]
        .forEach((selector) => {
          settings.googleFonts[selector.name] = selector.checked;
        });
      settings.fontMin = parseInt(fontMin.value);
      settings.fontMax = parseInt(fontMax.value);
      saveSettings();
      modal.close();
    });

    const fontMinExample =
      document.querySelector(".read-that-font-min-example");
    const fontMaxExample =
      document.querySelector(".read-that-font-max-example");

    fontMin.addEventListener("keydown", (e) => {
      e.stopPropagation();
    });

    fontMax.addEventListener("keydown", (e) => {
      e.stopPropagation();
    });

    fontMin.addEventListener("mouseup", (e) => {
      e.stopPropagation();
    });

    fontMax.addEventListener("mouseup", (e) => {
      e.stopPropagation();
    });

    fontMin.addEventListener("change", () => {
      let minVal = parseInt(fontMin.value);
      let maxVal = parseInt(fontMax.value);

      if (minVal > 300) {
        fontMin.value = 300;
        minVal = 300;
      }
      if (minVal < 10) {
        fontMin.value = 10;
        minVal = 10;
      }
      if (maxVal < minVal) {
        fontMax.value = minVal;
        fontMaxExample.style.fontSize = `${fontMax.value}px`;
      }
      fontMinExample.style.fontSize = `${fontMin.value}px`;
    });

    fontMax.addEventListener("change", () => {
      let minVal = parseInt(fontMin.value);
      let maxVal = parseInt(fontMax.value);

      if (maxVal > 300) {
        fontMax.value = 300;
        maxVal = 300;
      }
      if (maxVal < 10) {
        fontMax.value = 10;
        maxVal = 10;
      }
      if (maxVal < minVal) {
        fontMin.value = maxVal;
        fontMinExample.style.fontSize = `${fontMin.value}px`;
      }
      fontMaxExample.style.fontSize = `${fontMax.value}px`;
    });
  }

  async function loadGoogleFonts() {
    const fonts = (await (
      await fetch(
        window.atob("aHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy"
          + "5jb20vd2ViZm9udHMvdjEvd2ViZm9udHM/a2V5PQ==") + stylingString)
    ).json())
      .items
      .filter((font) => font.subsets.includes("japanese"));

    const googleFontsContainer =
      document.querySelector(".read-that-google-fonts");

    fonts.forEach((font) => {
      if (fontCache[font.family] === undefined) {
        fontCache[font.family] = font;
        addFontToSite(font);
      }
    });

    window.localStorage.setItem("read-that.cache", JSON.stringify(fontCache));

    const formContainerStyle = `
      display:flex;
      flex-direction:row;
      align-items:center;
    `;

    const fontExampleStyle = `
      text-overflow:ellipsis;
      white-space:nowrap;
      overflow:hidden;
    `;

    googleFontsContainer.innerHTML = fonts.map((font, i) => `
        <div class="control-group flag"
          style="
            ${formContainerStyle}background:${i % 2 == 0 ? " #fff" : "#eee"};
          ">
        <div style="${fontExampleStyle}">
          <label class="flag__item" style="display:block;">
            ${font.family}
          </label>
          <span style="font-family:'${font.family}'; font-size:2em;">
            ${exampleSentence}
          </span>
        </div>
        <div
          style="
        flex-grow:1; display:flex; justify-content:center; width:5em;
      ">
          <input
            type="checkbox"
            ${settings.googleFonts[font.family] ? "checked" : ""}
            class="read-that-google-selector"
            name="${font.family}"
            style="width:20px; height:20px;" />
        </div>
      </div>
    `).join("");
  }

  const exampleSentence =
    "政治家のキャリア40年にしてようやく総理大臣にまで登りつめました。";

  function addSettingsButton() {
    const buttonContainer = document.querySelector("#summary-button");
    const newButton = document.createElement("a");
    buttonContainer.append(newButton);
    newButton.innerHTML = `
      <i class="fa fa-font"></i>
    `;
    newButton.style.cursor = "pointer";
    newButton.title = "Open font settings";
    newButton.addEventListener("click", () => modal.toggle());
  }

  const stylingString = "AIzaSyAhd-3ke-7i5MBSJGF4HdLffjqhGvggJvo&sort=alpha";
  function setUpChanger() {
    const styleElem = document.createElement("style");
    document.head.append(styleElem);

    const callback = () => {
      const loadedFonts = Object.entries(settings.googleFonts)
        .filter((font) => font[1]).map((font) => font[0]);
      const randomFont =
        loadedFonts[Math.floor(Math.random() * loadedFonts.length)];

      const deltaSize = settings.fontMax - settings.fontMin + 1;
      const offset = Math.floor(Math.random() * deltaSize);
      const fontSize = settings.fontMin + offset;
      styleElem.innerHTML = `
        #character {
          display: flex;
          justify-content: center;
          align-items: center;
          height: ${Math.max(settings.fontMax * 1.5, 330)}px;
        }
        
        #character:not(:hover) span:not(.shown) {
          font-family:${randomFont};
          font-size:${fontSize}px;
        }

        #character span {
          white-space: nowrap;
        }
      `;

      const textElem = document.querySelector("#character span");
      const scaleRatio =
        Math.min(document.body.clientWidth / textElem.scrollWidth, 0.95);
      textElem.style.transform = `scale(${scaleRatio})`;
    };

    callback();

    window.$.jStorage.listenKeyChange("currentItem", callback);

    window.addEventListener("keydown", (e) => {
      if (e.key !== "Alt") {
        return;
      }
      document.querySelector("#character span").classList.add("shown");
    });

    window.addEventListener("keyup", (e) => {
      if (e.key !== "Alt") {
        return;
      }
      document.querySelector("#character span").classList.remove("shown");
    });
  }

  function wireUpOnlineFonts() {
    const onlineFontsContainer =
      document.querySelector(".read-that-online-fonts");

    const onlineFontsStyleElem = document.createElement("style");
    onlineFontsStyleElem.innerHTML = `
      .read-that-of-container.read-that-of-state {
      padding:1rem;
      border-radius:50 %;
      position:relative;
      height:0;
    }

    .read-that-of-container.read-that-of-state:before {
      font-weight:900;
      font-size:1em;
      font-family:FontAwesome;
      position:absolute;
      top:50 %;
      left:50 %;
      transform:translate(-50 %, -50 %);
    }

    .read-that-of-container.empty.read-that-of-state {
      background:lightgrey;
    }

    .read-that-of-container.empty.read-that-of-state:before {
      content:"\\3f";
    }

    @keyframes rotation {
      from {
        transform:rotate(0deg) translate(-50 %, -50 %);
      }
      to {
        transform:rotate(360deg) translate(-50 %, -50 %);
      }
    }

    .read-that-of-container.loading.read-that-of-state {
      background:yellow;
    }

    .read-that-of-container.loading.read-that-of-state:before {
      content:"\\f110";
      animation:rotation 1s steps(8) infinite;
      transform-origin:0 % 0 %;
    }

    .read-that-of-container.correct.read-that-of-state {
      background:darkgreen;
    }

    .read-that-of-container.correct.read-that-of-state:before {
      content:"\\f00c";
      color:white;
    }

    .read-that-of-container.incorrect.read-that-of-state {
      background:red;
    }

    .read-that-of-container.incorrect.read-that-of-state:before {
      content:"\\f071";
      color:white;
    }
    `;

    document.head.append(onlineFontsStyleElem);

    let nextElement;

    const addField = () => {
      const fieldContainer = document.createElement("div");

      onlineFontsContainer.append(fieldContainer);

      fieldContainer.innerHTML = `
        <input class="read-that-of-input" style="width:90%; padding:0.5em;">
        <span class="read-that-of-state"></span>
    `;

      fieldContainer.classList.add("read-that-of-container");
      fieldContainer.classList.add("empty");
      fieldContainer.style.display = "flex";
      fieldContainer.style.flexDirection = "row";
      fieldContainer.style.justifyContent = "space-between";
      fieldContainer.style.margin = "0.25em";
      fieldContainer.style.alignItems = "center";

      const inputField = fieldContainer.querySelector(".read-that-of-input");

      const checkFont = async () => {
        fieldContainer.classList.replace("empty", "loading");

        const font = new FontFace("test", `url(${inputField.value}`);
        font.load().then(() => {
          fieldContainer.classList.replace("loading", "correct");
        }).catch((e) => {
          fieldContainer.classList.replace("loading", "incorrect");
        });
      };

      inputField.addEventListener("blur", () => {
        if (inputField.value.length !== 0) {
          checkFont();
          return;
        }
        if (onlineFontsContainer.childElementCount !== 1) {
          fieldContainer.remove();
        }
      });

      inputField.addEventListener("focus", () => {
        if (nextElement.container === fieldContainer) {
          addField();
        }
      });

      inputField.addEventListener("keydown", (e) => {
        e.stopPropagation();
      });

      nextElement = {
        input: inputField,
        container: fieldContainer,
      };
    };

    addField();
  }

  populateModal();
  addSettingsButton();
  setUpChanger();
})();