Greasy Fork

Greasy Fork is available in English.

Better Lesson Picker

Lesson picker improvements

当前为 2024-06-04 提交的版本,查看 最新版本

// ==UserScript==
// @name         Better Lesson Picker
// @namespace    better-lesson-picker
// @version      1.1.3
// @description  Lesson picker improvements
// @author       Mystery
// @license      MIT
// @match        https://www.wanikani.com/subject-lessons/picker
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Insert CSS
    document.head.insertAdjacentHTML('beforeend',`
        <style name="better_lesson_picker" type="text/css">
            .better-lesson-picker__input::-webkit-outer-spin-button,
            .better-lesson-picker__input::-webkit-inner-spin-button {
                -webkit-appearance: none;
            }
            .better-lesson-picker__input {
                -moz-appearance: textfield;
                width: 3em;
                height: 48px;
                font: inherit;
                font-size: 20px;
                text-align: center;
                border-radius: var(--border-radius-tight);
            }
            .better-lesson-picker__input[disabled] {
                background-color: var(--color-button-lesson-picker-disabled-background);
                color: var(--color-button-lesson-picker-disabled-text);
                user-select: none;
            }
            .better-lesson-picker__input--radical {
                border: var(--color-radical) 2px solid;
            }
            .better-lesson-picker__input--kanji {
                border: var(--color-kanji) 2px solid;
            }
            .better-lesson-picker__input--vocabulary {
                border: var(--color-vocabulary) 2px solid;
            }
            .better-lesson-picker__input--all {
                border: 2px solid transparent;
                background:
                    linear-gradient(var(--color-wk-panel-content-background) 0 0) padding-box,
                    conic-gradient(
                      var(--color-kanji) 0deg,
                      var(--color-kanji) 128deg,
                      var(--color-vocabulary) 128deg,
                      var(--color-vocabulary) 230deg,
                      var(--color-radical) 233deg,
                      var(--color-radical) 360deg
                    ) border-box;
            }

            .lesson-picker__footer-content {
                display: flex;
                gap: 10px;
            }

            .lesson-picker__button {
                width: 210px;
                height: 48px;
            }

            .wk-form__fieldset:not(:last-of-type) {
                margin-bottom: 0px;
            }
        </style>
        `);

    // variables
    let currentEl;
    let firstEl;
    let dragged;

    const items = {
        radical: [],
        kanji: [],
        vocabulary: []
    };

    const inputs = {
        radical: undefined,
        kanji: undefined,
        vocabulary: undefined,
        all: undefined
    };

    const inputFocusValue = {};

    // main functions
    populateItems();
    addInputs();
    addEventListeners();


    function populateItems() {
        document.querySelectorAll('div.lesson-picker__subjects').forEach(el => {
            const type = el.querySelector('span').classList[1].split('--')[1];
            items[type].push(...el.children);
        });
    }

    function addInputs() {
        const form = document.querySelector('form.lesson-picker__footer-content');
        for (const key in inputs) {
            inputs[key] = createNumberInput(form, key);
        }
        reorderDom(form);
    }

    function addEventListeners() {
        document.addEventListener('mousedown', handleMousedown);

        document.addEventListener('click', handleClick);

        // Update inputs when selecting all
        document.querySelector('button.lesson-picker__section-toggle-all').addEventListener('click', () => setTimeout(updateInputs, 10));

        document.querySelectorAll('button.lesson-picker__section-toggle').forEach(el => {
            el.addEventListener('click', () => setTimeout(updateInputs, 10));
        });
    }

    function handleMousedown(e) {
        if (isItem(e)) {
            dragged = false;
            currentEl = undefined;
            firstEl = e.target;
            document.addEventListener('mousemove', handleDrag);
            document.addEventListener('mouseup', handleMouseup);
        }

    }

    function handleDrag(e) {
        dragged = true;
        if (currentEl !== e.target) {
            currentEl = e.target;
            if (isItem(e)) {
                e.target.click();
                updateInputs();
            }
        }
    }

    function handleMouseup(e) {
        if (firstEl === e.target && dragged) {
            // if you drag and release on the same item triggers the click event negating the drag click
            e.target.click();
        }
        if (!dragged) {
            setTimeout(updateInputs, 10);
        }
        document.removeEventListener('mousemove', handleDrag);
        document.removeEventListener('mouseup', handleMouseup);
    }

    function handleInput(type) {
        let list;
        if (type === 'all') {
            list = Object.values(items).reduce((acc, cur) => acc.concat(cur), []);
        } else {
            list = items[type]
        }

        return function(e) {
            const val = Number(e.target.value);
            if (val >= 0) {
                list.forEach((el, i) => {
                    if (i <= val - 1) {
                        if (el.ariaSelected === 'false') {
                            el.click();
                        }
                    } else {
                        if (el.ariaSelected === 'true') {
                            el.click();
                        }
                    }
                });
            }
            if (val > list.length) {
                e.target.value = list.length;
            }
            updateInputs(type);
        }
    }

    function handleClick(e) {
        if (e.shiftKey && isItem(e)) {
            const list = [];
            document.querySelectorAll('div.lesson-picker__subjects').forEach(el => list.push(...el.children));
            const clickedItem = e.target.closest('.lesson-picker__subject');
            for (let i = list.indexOf(clickedItem) - 1; i >= 0; i--) {
                const el = list[i];
                if (el.ariaSelected !== clickedItem.ariaSelected) {
                    el.click();
                } else {
                    break;
                }
            }
        }
    }

    function isItem(e) {
        return e.target.tagName === 'SPAN' && e.target.classList.contains('subject-character__characters');
    }

    function createNumberInput(form, type) {
        const input = document.createElement('input');
        input.type = 'number';
        input.classList.add('better-lesson-picker__input');
        input.classList.add(`better-lesson-picker__input--${type}`);
        input.addEventListener('input', handleInput(type));
        input.addEventListener('focus', e => {
            inputFocusValue[type] = e.target.value;
            e.target.value = '';
        });
        input.addEventListener('blur', e => {
            if (e.target.value === '') {
                e.target.value = inputFocusValue[type];
            }
        });
        input.value = 0;
        if (type !== 'all' && items[type].length === 0) {
            input.disabled = true;
        }
        form.appendChild(input);
        return input;
    }

    function updateInputs(type) {
        let total = 0;
        for (const key in items) {
            let count = 0;
            items[key].forEach(el => {
                if (el.ariaSelected === 'true') {
                    count++;
                }
            });
            if (!type || key !== type) {
                inputs[key].value = count;
            }
            total += count;
        }
        if (type !== 'all') {
            inputs.all.value = total;
        }
    }

    function reorderDom(form) {
        const checkbox = document.querySelector('fieldset div.wk-form__field--checkbox').parentElement;
        checkbox.style.justifyContent = 'center';
        form.appendChild(checkbox);
    }
})();