Duolingo Vocab Search

Search words from the language you are learning on Duolingo.

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Duolingo Vocab Search
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  Search words from the language you are learning on Duolingo.
// @author       Nekosuki
// @match        https://www.duolingo.com/*
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

GM_addStyle("#vocab-search{ position: fixed; top: 25%; left: 50%; z-index: 2000; width: 550px; margin: 0 0 0 -275px; background-color: #fff; border-radius: 10px; box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); transition: all 0.2s ease-in-out; opacity: 0 } #vocab-search input { width:100%; border: none; background: #fff; font: 500 24px/32px 'museo-sans-rounded', sans-serif; padding: 5px 10px; color: #4b4b4b; border-radius: 5px; box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.05); -webkit-font-smoothing: antialiased; text-align: left; line-height: 24px; padding: 12px 16px; } #vocab-search input::placeholder{ padding-top:3px; font-weight: 300}");


(function() {
    "use strict";
    let modal, input, status;
    let searchInProgress = false;
    let drawModal = function() {
        modal = document.createElement("div");
        modal.id = "vocab-search";
        modal.innerHTML = '<input type="text" placeholder="Dictionary Search"><span style="position:absolute;top:16px;right:15px;color:#aaa;"></span>';
        document.body.appendChild(modal);
        input = modal.querySelector("input");
        status = modal.querySelector("span");
        input.focus();
        hideModal();
    };
    let showModal = function() {
        if(document.activeElement != document.body) return;
        modal.style.opacity = "1";
        modal.style.visibility = "visible";
        setTimeout(function() { input.focus(); }, 50);
    };
    let hideModal = function() {
        if(searchInProgress) return;
        modal.style.opacity = "0";
        modal.style.visibility = "hidden";
        setTimeout(function() {
            input.blur();
            input.value = "";
            status.textContent = "";
        }, 250);
    };
    let toggleModal = function() {
        if(modal.style.visibility == "visible") hideModal();
        else showModal();
    };
    let getURLForWord = function(word, vocab) {
        let words = vocab.vocab_overview;
        let found = null;
        for(let i = 0; i < words.length; i++) {
            if(words[i].normalized_string.toLowerCase() == word.toLowerCase() ||
               words[i].word_string.toLowerCase() == word.toLowerCase()) {
                found = words[i];
                break;
            }
        }
        if(found !== null) {
            found = "https://www.duolingo.com/dictionary/" + vocab.language_string + "/" + found.normalized_string + "/" + found.lexeme_id;
        }
        return found;
    };
    let requestVocab = function(word) {
        searchInProgress = true;
        status.textContent = "searching";
        let xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if(this.readyState != 4 || this.status != 200) return;
            var obj = JSON.parse(this.responseText);
            let url = getURLForWord(word, obj);
            searchInProgress = false;
            if(url !== null) wordFound(url);
            else wordNotFound();
        };
        xhr.open("GET", "/vocabulary/overview", true);
        xhr.send();
    };
    let wordFound = function(url) {
        status.textContent = "redirecting";
        setTimeout(function(){ window.location = url; }, 50);
    };
    let wordNotFound = function() {
        status.textContent = "not found";
        input.readOnly = false;
    };
    let submitWord = function() {
        if(searchInProgress || document.activeElement != input) return;
        let word = input.value.trim();
        if(!word || word.length === 0) return;
        input.readOnly = true;
        requestVocab(word);
    };
    let init = function () {
        drawModal();
        document.addEventListener("keypress", function(event) {
            switch(event.keyCode) {
                case 47: // forward slash
                    toggleModal();
                    if(document.activeElement == input) event.preventDefault();
                    break;
                case 27: // escape
                    hideModal();
            }
        });
        input.addEventListener("keydown", function(event) {
            if(!input.readOnly) status.textContent = "";
            if(event.keyCode == 13) { // enter
                submitWord();
                event.stopPropagation();
            }
        });
        document.addEventListener("click", function(event) {
            if(!modal.contains(event.target)) hideModal();
        });
    };
    init();
})();