Wanikani: Skip to Quiz

Enables the quiz button without having to go through any of the lessons.

目前為 2021-11-16 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Wanikani: Skip to Quiz
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @description  Enables the quiz button without having to go through any of the lessons.
// @author       Kumirei
// @match        https://www.wanikani.com/lesson/session
// @match        https://preview.wanikani.com/lesson/session
// @grant        none
// ==/UserScript==

;(function ($) {
    // Make quiz button look clickable
    add_css(
        '#lesson #batch-items ul li[data-index="quiz"] span {background-color: #004fdd; cursor: pointer !important;}',
        'skip-to-quiz-css',
    )

    // Enable clicking quiz button by adding class that the existing
    // click handler / keyup handler checks for before it fires
    onEachElementReady('li[data-index="quiz"] > span', null, function (e) {
        e = $(e)
        const p = e.parent()
        bindFirst(e, 'click', function () {
            console.log('click', e, p)
            p.addClass('active-quiz')
        })
        bindFirst($('body'), 'keyup', function (evt) {
            if (evt.key == 'q') p.addClass('active-quiz')
        })
    })

    // Library Functions

    // Adds CSS to document
    // Does not escape its input
    function add_css(css, id = '') {
        document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', `<style id="${id}">${css}</style>`)
    }

    // Calls callback once on each element matching selector that is added
    // to the DOM, including existing elements at the time the function is first called.
    // options is options for the MutationObserver (optional, is merged with default options  {childList: true, subtree: true})
    function onEachElementReady(selector, options, callback) {
        if (!options) options = {}

        let els_found = new Set()
        function check_available() {
            for (const el of document.querySelectorAll(selector)) {
                if (!els_found.has(el)) {
                    els_found.add(el)
                    callback(el)
                }
            }
        }

        queueMicrotask(check_available)

        let mo = new MutationObserver(check_available)
        mo.observe(document.documentElement, {
            childList: true,
            subtree: true,
            ...options,
        })
    }

    // Adds a jQuery event listener that runs before all ones added so far
    // Based on https://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t
    // [name] is the name of the event "click", "mouseover", ..
    // same as you'd pass it to bind()
    // [fn] is the handler function
    let bindFirst = function (jqel, name, fn) {
        // bind as you normally would
        // don't want to miss out on any jQuery magic
        jqel.on(name, fn)

        // Thanks to a comment by @Martin, adding support for
        // namespaced events too.
        jqel.each(function () {
            var handlers = $._data(this, 'events')[name.split('.')[0]]
            // take out the handler we just inserted from the end
            var handler = handlers.pop()
            // move it at the beginning
            handlers.splice(0, 0, handler)
        })
    }
})(window.jQuery)