abdullahalt / Advanced Context Sentence

"use strict";

// ==UserScript==
// @name         Advanced Context Sentence
// @namespace    https://openuserjs.org/users/abdullahalt
// @version      1.0
// @description  Link the kanji page for the kanji in the context sentence section
// @author       abdullahalt
// @match        https://www.wanikani.com/*
// @grant        none
// @copyright    2019, abdullahalt (https://openuserjs.org//users/abdullahalt)
// @license MIT
// ==/UserScript==
// ==OpenUserJS==
// @author abdullahalt
// ==/OpenUserJS==
(function () {
  //-----------------------------------------------------------------------------------------------------------------------------------------------------//
  //-------------------------------------------------------------------INITIALIZATION--------------------------------------------------------------------//
  //-----------------------------------------------------------------------------------------------------------------------------------------------------//
  var wkof = window.wkof;
  init();

  function init() {
    if (wkof) {
      wkof.include("ItemData");
      wkof.ready("ItemData").then(getGuruedKanji).then(extractKanjiFromResponse).then(start);
    } else {
      console.warn("Advanced Context Sentence: You are not using Wanikani Open Framework which " + "this script utlize to see the kanji you learned and color it with a different color. " + "You can still use Advanced Context Sentence normally though");
      start();
    }
  }

  function start() {
    var guruedKanji = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;

    if (window.location.pathname === "/review/session") {
      observeChanges(function () {
        return evolveContextSentence(guruedKanji);
      }, "#item-info-col2");
    }

    if (window.location.pathname === "/lesson/session") {
      evolveContextSentence(guruedKanji);
      observeChanges(function () {
        return evolveContextSentence(guruedKanji);
      }, "#supplement-voc-context-sentence");
    } else {
      evolveContextSentence(guruedKanji);
    }
  }

  function evolveContextSentence(guruedKanji) {
    var sentences = document.querySelectorAll(".context-sentence-group");
    if (sentences.length === 0) return;
    sentences.forEach(function (sentence) {
      var japaneseSentence = sentence.querySelector('p[lang="ja"]');
      var audioButton = createAudioButton(japaneseSentence.innerHTML);
      var advancedExampleSentence = "";
      var chars = japaneseSentence.innerHTML.split("");
      chars.forEach(function (char) {
        var renderedChar = highlightAndLinkKanji(char, guruedKanji);
        advancedExampleSentence = advancedExampleSentence.concat(renderedChar);
      });
      japaneseSentence.innerHTML = advancedExampleSentence;
      audioButton && japaneseSentence.append(audioButton);
    });
  }

  function createAudioButton(sentence) {
    if (!window.SpeechSynthesisUtterance) {
      console.warn("Advanced Context Sentence: you're browser does not support SpeechSynthesisUtterance " + "which this script utilaize to implement audio feature. update you're broswer or use another one if you want that feature");
      return null;
    }

    var button = document.createElement("button");
    button.setAttribute("class", "audio-btn audio-idle");

    button.onclick = function () {
      var msg = new SpeechSynthesisUtterance(sentence);
      msg.lang = "ja";
      msg.rate = 11;
      window.speechSynthesis.speak(msg);

      msg.onstart = function () {
        button.setAttribute("class", "audio-btn audio-play");
      };

      msg.onend = function () {
        button.setAttribute("class", "audio-btn audio-idle");
      };
    };

    return button;
  }

  function observeChanges(callback, element) {
    if (!window.MutationObserver) {
      console.warn("Advanced Context Sentence: you're browser does not support MutationObserver " + "which this script utilaize to implement its features in /lesson/session and /review/sesson. " + "update you're broswer or use another one if you want Advanced Context Sentence to work on them." + "This script is still useful on /vocabulary page though");
      return;
    }

    var target = document.querySelector(element);
    var config = {
      attributes: false,
      childList: true,
      subtree: true
    };
    var observer = new MutationObserver(function () {
      observer.disconnect();
      callback();
      observer.observe(target, config);
    });
    observer.observe(target, config);
  } //-----------------------------------------------------------------------------------------------------------------------------------------------------//
  //-------------------------------------------------------------------HELPER FUNCTIONS------------------------------------------------------------------//
  //-----------------------------------------------------------------------------------------------------------------------------------------------------//


  function highlightAndLinkKanji(char, guruedKanji) {
    var renderedChar = char;

    if (isKanji(char)) {
      renderedChar = isAtLeastGuru(char, guruedKanji) ? renderKanji(char, "#f100a1") : renderKanji(char, "#a100f1");
    }

    return renderedChar;
  }
  /**
   * Determine if the character is a Kanji, inspired by https://stackoverflow.com/a/15034560
   */


  function isKanji(char) {
    return isCommonOrUncommonKanji(char) || isRareKanji(char);
  }

  function isCommonOrUncommonKanji(char) {
    return char >= "\u4E00" && char <= "\u9FAF";
  }

  function isRareKanji(char) {
    char >= "\u3400" && char <= "\u4DBF";
  }
  /**
   * Renders the link for a kanji you've gurued
   * Knji pages always use https://www.wanikani.com/kanji/{kanji} where {kanji} is the kanji character
   */


  function renderKanji(kanji, color) {
    return "<a href=\"https://www.wanikani.com/kanji/".concat(kanji, "\" target=\"_blank\" style=\"color: ").concat(color, "\" title=\"go to kanji page\">").concat(kanji, "</a>");
  }

  function isAtLeastGuru(char, guruedKanji) {
    if (!guruedKanji) return false;
    return guruedKanji.includes(char);
  }

  function getGuruedKanji() {
    return wkof.ItemData.get_items({
      wk_items: {
        filters: {
          item_type: ["kan"],
          srs: ["guru1", "guru2", "mast", "enli", "burn"]
        }
      }
    });
  }

  function extractKanjiFromResponse(items) {
    var kanjis = [];
    items.forEach(function (item) {
      kanjis.push(item.data.characters);
    });
    return kanjis;
  }
})();