WaniKani Example Sentences

Displays additional examples sentences for the given vocabulary.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name WaniKani Example Sentences
// @version 3.2
// @description  Displays additional examples sentences for the given vocabulary.
// @require https://greasyfork.org/scripts/34539-wanikani-api/code/WaniKani%20API.js?version=226222
// @match https://www.wanikani.com/settings/account*
// @match https://www.wanikani.com/vocabulary/*
// @match https://www.wanikani.com/review/session*
// @match https://www.wanikani.com/lesson/session*
// @run-at          document-end
// @copyright 2017 jeshuam
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @grant       GM_addStyle
// @namespace https://greasyfork.org/users/27794
// ==/UserScript==

//////////////////////////
/////// CSS Styles ///////
//////////////////////////
GM_addStyle(`
div#example-sentences {
max-height: 400px;
overflow: auto;
}

.example-sentence-unlearned-vocab {
display: none;
}

#example-sentences-toggle-display {
margin-bottom: 20px;
}`);


/////////////////////////
/////// Constants ///////
/////////////////////////
// The amount of time before the learned item cache is updated.
let CACHE_EXPIRATION_TIMER_MS = 24 * 60 * 60 * 1000; // (1 day)


/////////////////////////////////
/////// Utility Functions ///////
/////////////////////////////////
//
// Determine whether the given Japanese word ends in an 'u' sound (i.e. is a verb).
//
function EndsInUSound(japaneseWord) {
  return japaneseWord.match(/[るゆむふぬつすくう]/) !== null;
}

//
// Function to check whether a Japanese word only contains kana (hira- or katakana).
//
function OnlyContainsKanaOrPunctuation(japaneseWord) {
  return japaneseWord.match(/^[\u3000-\u30FF]+$/) !== null;
}


/////////////////////////////
/////// Main Function ///////
/////////////////////////////
function DisplayExampleSentences(known_vocab) {
  //
  // Extract the Kanji from the current page. This will have a switch for each type of page, and
  // will do something different for each. This will only include vocabulary pages, reviews and
  // lessons.
  //
  function GetVocabularyKanjiFromPage() {
    // Vocabulary information page.
    if (document.URL.indexOf('vocabulary') != -1) {
      return document.querySelector('header span.vocabulary-icon span').innerText.trim();
    }

    // Review page.
    else if (document.URL.indexOf('review/session') != -1) {
      return document.querySelector('div.vocabulary span').innerText.trim();
    }

    // Lesson page.
    else if (document.URL.indexOf('lesson/session') != -1) {
      return document.querySelector('div#main-info div#character').innerText.trim();
    }

    // Not on a valid page.
    else {
      return null;
    }
  }

  //
  // Get the data from the remote URL for the given vocabulary.
  //
  function GetExampleSentencesForVocabulary(vocabulary, complete) {
    WaniKaniAPI.load('https://jeshuam.pythonanywhere.com/wanikani-sentences/' + vocabulary, complete);
  }

  //
  // Generate the DOM required to display the example sentences. This will be consistent over the
  // various pages this script runs on.
  //
  function GetSectionWithExamplesSentences(known_vocab, sentences) {
    // Make the initial section.
    let section = document.createElement('section');
    section.id = 'examples-sentences-section';
    section.innerHTML = `
<h2>More Context Sentences</h2>
<button id="example-sentences-toggle-display">Show All Sentences</button>
<div id="example-sentences"></div>
`;

    // When the button is pressed, show/hide the example sentences with unlearned vocab.
    section.children[1].onclick = function() {
      if (this.innerText === 'Show All Sentences') {
        for (let element of document.querySelectorAll('.example-sentence-unlearned-vocab')) {
          element.style.display = 'block';
        }

        this.innerText = 'Show Only Known Vocab';
      } else {
        for (let element of document.querySelectorAll('.example-sentence-unlearned-vocab')) {
          element.style.display = 'none';
        }

        this.innerText = 'Show All Sentences';
      }
    };

    // Add each sentence to the section.
    for (let sentence of sentences) {
      // Check if this sentence has any words we don't know. If it does, add an extra class to it.
      let extra_class = '';
      let kanji = GetVocabularyKanjiFromPage();
      for (let word of sentence.jpn) {
        if (word != kanji && !OnlyContainsKanaOrPunctuation(word) && known_vocab[word] === undefined) {
          extra_class = 'example-sentence-unlearned-vocab';
          break;
        }
      }

      // Make the HTML for this sentence.
      let japanese_html = '';
      for (let word of sentence.jpn) {
        // Highlight the current word.
        if (word == kanji) {
          japanese_html += '<span class="vocabulary-highlight highlight-vocabulary">' + kanji + '</span>';
        }

        // Insert a link to the WaniKani page for learned vocabulary.
        else if (known_vocab[word] !== undefined) {
          japanese_html += '<span><a href="https://www.wanikani.com/vocabulary/' + known_vocab[word] + '">' + word + '</a></span>';
        }

        // Otherwise, just put the word into the text.
        else {
          japanese_html += '<span>' + word + '</span>';
        }
      }

      let sentence_html = document.createElement('div');
      sentence_html.className = `context-sentence-group ${extra_class}`;
      sentence_html.innerHTML = `<p lang="ja">${japanese_html}</p><p>${sentence.eng}</p>`;

      section.children[2].appendChild(sentence_html);
    }

    return section;
  }

  // Process the vocabulary page.
  if (document.URL.indexOf('vocabulary') >= 0) {
    let vocab = GetVocabularyKanjiFromPage();
    GetExampleSentencesForVocabulary(vocab, function(data) {
      if (data.length === 0) {
        return;
      }

      let section = GetSectionWithExamplesSentences(known_vocab, data);
      let insertion_section = document.querySelector('section.context-sentence');
      insertion_section.parentNode.insertBefore(section, insertion_section.nextSibling);
    });
  }

  // Process the review page. TODO(jeshua): test this.
  else if (document.URL.indexOf('review/session') >= 0) {
    // If the 'all-info' button is pressed, then display it.
    document.querySelector('div#all-info').onclick = function() {
      let vocab = GetVocabularyKanjiFromPage();
      GetExampleSentencesForVocabulary(vocab, function(data) {
        if (data.length === 0) {
          return;
        }

        // Remove the old section.
        let section_to_remove = document.querySelector('#examples-sentences-section');
        if (section_to_remove !== null) {
          section_to_remove.parentNode.removeChild(section_to_remove);
        }

        let section = GetSectionWithExamplesSentences(known_vocab, data);
        let insertion_section = document.querySelector('section#item-info-context-sentences');
        insertion_section.parentNode.appendChild(section);
      });
    };
  }

  // Process the lesson page.
  else if (document.URL.indexOf('lesson/session') >= 0) {
    let observer = new MutationObserver(function() {
      let vocab = GetVocabularyKanjiFromPage();
      GetExampleSentencesForVocabulary(vocab, function(data) {
        if (data.length === 0) {
          return;
        }

        // Remove the old section.
        let section_to_remove = document.querySelector('#examples-sentences-section');
        if (section_to_remove !== null) {
          section_to_remove.parentNode.removeChild(section_to_remove);
        }

        // Add the new section.
        let section = GetSectionWithExamplesSentences(known_vocab, data);
        let insertion_section = document.querySelector('div#supplement-voc-context-sentence');
        insertion_section.parentNode.insertBefore(section, insertion_section.nextSibling);
      });
    });
    
    observer.observe(document.querySelector('div#main-info'), {subtree: true, childList: true});
  }
}


//////////////////////////////
/////// Start Function ///////
//////////////////////////////
document.addEventListener('DOMContentLoaded', function() {
  // Get their API key. If we are on the account page, go no further.
  if (WaniKaniAPI.getAPIKey() === undefined) {
    console.log('EXAMPLE-SENTENCES: No WaniKani API key found!');
    return;
  }

  // Load the unlocked vocab, initializing to an empty object.
  let unlockedVocab = JSON.parse(GM_getValue('wanikani-sentences-learned-cache', '{"__cache-time": 0}'));

  // If cache expired, update first then run main.
  let currentTime = (new Date().getTime());
  if ((currentTime - unlockedVocab['__cache-time']) > CACHE_EXPIRATION_TIMER_MS) {
    console.log('EXAMPLE-SENTENCES: Cache expired, refreshing known vocabulary.');
    WaniKaniAPI.load(WaniKaniAPI.apiURL('vocabulary'), function(data) {
      for (let vocab of data.requested_information.general) {
        if (vocab.user_specific !== null) {
          // Remove any preceding ~ characters (as they aren't part of the word).
          let kanji = vocab.character.replace(/〜/g, '');
          unlockedVocab[kanji] = kanji;

          // For verbs, remove the U sound at the end. This should do a decent job of showing
          // the verb even if it is conjugated (it won't be perfect, but better than nothing).
          if (EndsInUSound(kanji)) {
            unlockedVocab[kanji.substr(0, kanji.length - 1)] = kanji;
          }
        }
      }

      // Cache time in milliseconds since the epoch.
      unlockedVocab['__cache-time'] = (new Date().getTime());

      // Save the cache, then keep going with the main program.
      GM_setValue('wanikani-sentences-learned-cache', JSON.stringify(unlockedVocab));
      DisplayExampleSentences(unlockedVocab);
    });
  } else {
    DisplayExampleSentences(unlockedVocab);
  }
});