CheckBoxMate Modernized

Select multiple checkboxes with ease by drawing a box around them.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name        CheckBoxMate Modernized
// @namespace   https://musicbrainz.org/user/chaban
// @version     1.1
// @tag         ai-created
// @description Select multiple checkboxes with ease by drawing a box around them.
// @author      scottmweaver, chaban
// @license     MIT
// @match       *://*/*
// @grant       none
// ==/UserScript==

(function () {
    'use strict';

    /*
     * This is a modernized version of the original CheckBoxMate Greasemonkey script by scottmweaver.
     * Original description: "Check multiple checkboxes with ease by drawing a box around them to
     * automatically select them all."
     * Original namespace: http://macdougalmedia.com/2010/04/07/checkboxmate-for-greasemonkey/
     * Original homepageURL: https://userscripts-mirror.org/scripts/show/73700
     */

    const DRAG_THRESHOLD = 5; // Minimum pixels to move before initiating a drag selection

    class CheckBoxMate {
        // --- Private properties ---
        #isDragging = false;
        #dragStarted = false;
        #startPos = { x: 0, y: 0 };
        #selectionBox = null;
        #checkboxes = [];
        #lastSelected = new Set();

        constructor() {
            document.addEventListener('mousedown', this.handleMouseDown, { passive: true });
        }

        /**
         * Caches the positions of all visible checkboxes on the page.
         * This is a performance optimization to avoid querying the DOM on every mouse move.
         */
        #cacheCheckboxPositions() {
            this.#checkboxes = [];
            const checkboxNodes = document.querySelectorAll('input[type="checkbox"]');

            for (const checkbox of checkboxNodes) {
                // Ignore hidden checkboxes
                if (checkbox.offsetParent !== null) {
                    this.#checkboxes.push({
                        element: checkbox,
                        rect: checkbox.getBoundingClientRect(),
                    });
                }
            }

            // Sort by vertical position for faster intersection checking
            this.#checkboxes.sort((a, b) => a.rect.top - b.rect.top);
        }

        /**
         * Creates and styles the visual selection rectangle.
         */
        #createSelectionBox() {
            if (this.#selectionBox) return;

            this.#selectionBox = document.createElement('div');
            this.#selectionBox.style.cssText = `
                position: fixed;
                border: 1px dotted #000;
                background-color: rgba(0, 100, 255, 0.1);
                z-index: 2147483647;
                pointer-events: none;
            `;
            document.body.appendChild(this.#selectionBox);
        }

        /**
         * Updates the geometry of the selection box based on mouse movement.
         * @param {MouseEvent} event - The mouse move event.
         */
        #updateSelectionBox(event) {
            if (!this.#selectionBox) return;

            const currentPos = { x: event.clientX, y: event.clientY };
            const left = Math.min(this.#startPos.x, currentPos.x);
            const top = Math.min(this.#startPos.y, currentPos.y);
            const width = Math.abs(this.#startPos.x - currentPos.x);
            const height = Math.abs(this.#startPos.y - currentPos.y);

            this.#selectionBox.style.left = `${left}px`;
            this.#selectionBox.style.top = `${top}px`;
            this.#selectionBox.style.width = `${width}px`;
            this.#selectionBox.style.height = `${height}px`;
        }

        /**
         * Checks if two rectangles are intersecting.
         * @param {DOMRect} rect1 - The first rectangle.
         * @param {DOMRect} rect2 - The second rectangle.
         * @returns {boolean} - True if they intersect.
         */
        #isIntersecting(rect1, rect2) {
            return !(
                rect1.right < rect2.left ||
                rect1.left > rect2.right ||
                rect1.bottom < rect2.top ||
                rect1.top > rect2.bottom
            );
        }

        /**
         * Updates the selection state of checkboxes based on the current selection box.
         */
        #updateSelection() {
            if (!this.#selectionBox) return;

            const selectionRect = this.#selectionBox.getBoundingClientRect();
            const currentSelected = new Set();

            // Find all checkboxes intersecting with the selection box
            for (const item of this.#checkboxes) {
                // Optimization: stop checking once we're past the selection box vertically
                if (item.rect.top > selectionRect.bottom) {
                    break;
                }
                if (this.#isIntersecting(selectionRect, item.rect)) {
                    currentSelected.add(item.element);
                }
            }

            // Toggle checkboxes that have changed state (entered or left the selection)
            for (const checkbox of this.#lastSelected) {
                if (!currentSelected.has(checkbox)) {
                    checkbox.click();
                }
            }
            for (const checkbox of currentSelected) {
                if (!this.#lastSelected.has(checkbox)) {
                    checkbox.click();
                }
            }

            this.#lastSelected = currentSelected;
        }

        /**
         * Cleans up all resources and resets the state.
         */
        #cleanup = () => {
            document.removeEventListener('mousemove', this.handleMouseMove);
            document.removeEventListener('mouseup', this.handleMouseUp);

            if (this.#selectionBox) {
                this.#selectionBox.remove();
                this.#selectionBox = null;
            }

            this.#isDragging = false;
            this.#dragStarted = false;
            this.#checkboxes = [];
            this.#lastSelected.clear();
        }

        // --- Event Handlers (as arrow functions to preserve `this` context) ---

        handleMouseDown = (event) => {
            // Only activate on left-click on a checkbox
            if (event.button !== 0 || event.target.type !== 'checkbox') {
                return;
            }

            this.#isDragging = true;
            this.#startPos = { x: event.clientX, y: event.clientY };

            document.addEventListener('mousemove', this.handleMouseMove);
            document.addEventListener('mouseup', this.handleMouseUp);
        }

        handleMouseMove = (event) => {
            if (!this.#isDragging) return;

            event.preventDefault();

            if (!this.#dragStarted) {
                const movedDistance = Math.hypot(
                    event.clientX - this.#startPos.x,
                    event.clientY - this.#startPos.y
                );

                if (movedDistance > DRAG_THRESHOLD) {
                    this.#dragStarted = true;
                    this.#cacheCheckboxPositions();
                    this.#createSelectionBox();
                }
            }

            if (this.#dragStarted) {
                this.#updateSelectionBox(event);
                this.#updateSelection();
            }
        }

        handleMouseUp = () => {
            if (this.#dragStarted) {
                this.#updateSelection();
            }
            this.#cleanup();
        }
    }

    // Initialize the script
    new CheckBoxMate();
})();