Pixiv QuickSelect

Allow selecting multiple items by holding shift

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Pixiv QuickSelect
// @version      0.2
// @description  Allow selecting multiple items by holding shift
// @author       8uurg
// @match        https://www.pixiv.net/*/users/*/bookmarks/*
// @grant        GM_addStyle
// @namespace https://greasyfork.org/users/368765
// ==/UserScript==

(function() {
    'use strict';

    // Insert css
    GM_addStyle(`
        label {
            user-select: none;
        }
        .betweenSelected {
            mask-image: none !important;
            box-shadow: 0px 0px 5px 2px lightblue;
        }
        section div:nth-child(3) {
            box-shadow: 15px 0px 0px 0px white, -15px 0px 0px 0px white;
        }
    `)

    // Due to this webpage being dynamic now, we may have to inject code later...
    function setupHandlers()
    {
    // Find the checkboxes and add logic.
    let bookmarksOrigin = document.querySelectorAll('input[type=checkbox]');
    if ( bookmarksOrigin.length == 0 ) {
        // If there are none, don't setup for now: wait a bit!
        return false;
    }
    let lastIndex = 0;
    let lastHovered = -1;
    let currentState = false;

    function getLabelGrandparent(checkbox)
    {
        return checkbox.parentElement.parentElement.parentElement;
    }

    function activateHoverEffect(from, to) {
        if(currentState) { return; }
        const tfrom = Math.min(from, to)
        const tto = Math.max(from, to)

        // console.log(`Activating from ${lastIndex} to ${lastHovered}`)
        for (let i=tfrom; i <= tto; i++) {
            getLabelGrandparent(bookmarksOrigin[i]).parentElement.classList.add('betweenSelected')
        }
        currentState = true;
    }
    function deactivateHoverEffect(from, to) {
        if(!currentState) { return; }
        const tfrom = Math.min(from, to)
        const tto = Math.max(from, to)

        // console.log(`Deactivating from ${lastIndex} to ${lastHovered}`)
        for (let i=tfrom; i <= tto; i++) {
            getLabelGrandparent(bookmarksOrigin[i]).parentElement.classList.remove('betweenSelected')
        }
        currentState = false;
    }
    function keyDownHandler(e) {
        if(lastHovered === -1) { return; }

        if(e.keyCode === 16) {
            activateHoverEffect(lastIndex, lastHovered)
        }
    }
    function keyUpHandler(e) {
        if(lastHovered === -1) { return; }

        if(e.keyCode === 16) {
            deactivateHoverEffect(lastIndex, lastHovered)
        }
    }
    document.addEventListener('keydown', keyDownHandler)
    document.addEventListener('keyup', keyUpHandler)

    for(let i=0; i < bookmarksOrigin.length; i++) {

        const checkbox = bookmarksOrigin[i];
        const bookmark = getLabelGrandparent(checkbox);

        const listedIndex = i;

        // We access the variables accessed outside the loop are done on purpose.
        // The actual element where this warning would be correct (i) is fixed in place using
        // a const binding within this scope!
        /* eslint-disable no-loop-func */
        function clickHandler(e) {
            // Avoid duplicating a click. This works because
            // the checkbox itself is hidden and will never be clicked directly!
            if (e.target instanceof HTMLInputElement) {
                // e.stopPropagation();
                return;
            }
            if(currentState && lastHovered !== -1) {
                deactivateHoverEffect(lastIndex, lastHovered);
            }
            // The checkbox itself gets checked afterwards, after this clickHandler has been performed.
            // Hence inverting the state is required.
            const newState = !checkbox.checked;
            const i = listedIndex;
            console.log(`Clicked ${e.target}, set to ${newState} and is index ${i}, is shift being held?: ${e.shiftKey}`);
            if(e.shiftKey) {
                const tfrom = Math.min(i, lastIndex)
                const tto = Math.max(i, lastIndex)
                // handling = tto - tfrom + 1;
                // console.log(`Setting from ${tfrom} to ${tto} to ${newState}`)
                for(let j=tfrom; j<=tto; j++) {
                    // The original approach with simply setting the checkbox state doesn't work anymore...
                    //  bookmarksOrigin[j].checked = newState
                    // Instead click the label when required... That should work.
                    if ( bookmarksOrigin[j].checked != newState ) {
                        const otherCheckbox = bookmarksOrigin[j];
                        const otherLabel = otherCheckbox.parentElement.parentElement.parentElement;
                        // Use a timeout...
                        setTimeout(function() {
                            otherCheckbox.click();
                        }, 0);
                    }
                }
            }
            lastIndex = i;
            if(currentState) {
                activateHoverEffect(lastIndex, lastHovered)
            }
        }
        function mouseEnterHandler(e) {
            const i = listedIndex;
            lastHovered = i;
            if (e.shiftKey) {
                activateHoverEffect(lastIndex, lastHovered)
            }
        }
        function mouseLeaveHandler(e) {
            deactivateHoverEffect(lastIndex, lastHovered)
            lastHovered = -1;
        }
        /* eslint-enable no-loop-func */

        bookmark.addEventListener('click', clickHandler);

        // For fancy hover when holding shift effect.
        // Disable for now.
        bookmark.addEventListener('mouseenter', mouseEnterHandler);
        bookmark.addEventListener('mouseleave', mouseLeaveHandler);
    }

    // Everything has been successfully set up!
    return true;
    // End of setupHandlers.
    }


    let intervalID = -1;

    function initialize()
    {
        // Try running it first (maybe the webpage is already in edit mode)
        const plainAttempt = setupHandlers();
        if (plainAttempt) {
            console.log("Successfully initialized eagerly.")
            return true;
        }

        // Pixiv has been updated and the webpage is now dynamic: we need to wait until checkboxes are available.
        // In order to do so, we place a click listener on the edit button.
        // We here use the oddity that the edit button is not actually a button element to locate its image.
        // And then go up until we find the actual button itself.
        const editButtonSVGElement = document.querySelector("section div>svg");
        if ( editButtonSVGElement === null ) return false;
        const editButtonElement = editButtonSVGElement.parentElement.parentElement.parentElement;
        editButtonElement.addEventListener('click', function(e) {
            window.setTimeout(function() {
                const success = setupHandlers();
                if (success) console.log("Successfully completely initialized!")
            }, 500);
        });
        console.log("Successfully initialized on edit button.")
        return true;
    }

    function intervalInit()
    {
        const success = initialize();
        if ( success )
        {
            window.clearInterval(intervalID);
        }
    }

    intervalID = window.setInterval(intervalInit, 1000);
})();