JPDB add context menu in reviews

Adds a new word's context menu from its top deck in JPDB reviews

Stan na 29-07-2023. Zobacz najnowsza wersja.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

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.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         JPDB add context menu in reviews
// @namespace    jpdb.io
// @version      0.1
// @description  Adds a new word's context menu from its top deck in JPDB reviews
// @author       daruko
// @match        https://jpdb.io/review*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jpdb.io
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const debug = false;

    let menuDetails = init();
    const observer = new MutationObserver(handleMutation);
    observer.observe(document.body, { childList: true, subtree: true });

    function handleMutation(mutations) {
        if (!menuDetails || !document.body.contains(menuDetails)) {
            menuDetails = init();
        }
    }

    function init() {
        const wordUri = document.querySelector('.review-reveal .answer-box a[href^="/vocabulary"]')?.href;
        const deckUri = document.querySelector('.review-reveal a[href^="/deck"]')?.href;
        if (!wordUri || !deckUri) {
            debug && console.debug('URI not found');
            return;
        }
        const [,, wordId, word] = new URL(wordUri).pathname.split('/', 4);
        if (!word) {
            debug && console.debug('Word URI not recognized');
            return;
        }
        return createDropdownButton(wordId, word, deckUri);
    }

    function createDropdownButton(wordId, word, deckUri) {
        const sideButton = document.querySelector('.review-button-group .side-button');
        if (!sideButton) {
            debug && console.debug('Side button not found');
            return;
        }
        const buttonWrapper = document.createElement("div");
        buttonWrapper.style = "display: flex; flex-direction: column;";
        sideButton.style = "flex-grow: 1;" + sideButton.style;
        sideButton.parentNode.insertBefore(buttonWrapper, sideButton);
        buttonWrapper.appendChild(sideButton);

        const dropdownButtonLabel = document.createElement("label");
        dropdownButtonLabel.className = "side-button";
        dropdownButtonLabel.style = "margin-top: 0;";
        const dropdownButtonDiv = document.createElement("div");
        dropdownButtonDiv.className = "dropdown right-aligned";
        dropdownButtonLabel.appendChild(dropdownButtonDiv);
        const dropdownButtonDetails = document.createElement("details");
        dropdownButtonDiv.appendChild(dropdownButtonDetails);
        const dropdownButtonSummary = document.createElement("summary");
        dropdownButtonSummary.style = "padding: 0; border: none;";
        dropdownButtonSummary.appendChild(document.createTextNode("⋮"));
        dropdownButtonDetails.appendChild(dropdownButtonSummary);
        buttonWrapper.appendChild(dropdownButtonLabel);

        const onToggle = () => {
            loadDropdown(dropdownButtonDetails, wordId, word, deckUri)
                .then(() => dropdownButtonDetails.removeEventListener("toggle", onToggle));
        };
        dropdownButtonDetails.addEventListener("toggle", onToggle);

        return dropdownButtonDetails;
    }

    function loadDropdown(details, wordId, word, deckUri) {
        const filteredDeckUri = `${deckUri}&q=${word}&show_only=new,locked`;
        return fetch(filteredDeckUri)
            .then((response) => response.text())
            .then((text) => {
                const html = new DOMParser().parseFromString(text, "text/html");
                const dropdownContent = html.querySelector(`.entry .vocabulary-spelling a[href^="/vocabulary/${wordId}"]`)
                    ?.closest('.entry')
                    ?.querySelector('.dropdown details .dropdown-content');
                if (!dropdownContent) {
                    debug && console.debug('Dropdown not found in the deck page');
                    return;
                }
                dropdownContent.style = "bottom: 0;" + dropdownContent.style;
                details.appendChild(dropdownContent);
            })
            .catch((err) => {
                console.error('An error has occurred.', err);
            });
    }
})();