您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Select multiple checkboxes with ease by drawing a box around them.
// ==UserScript== // @name CheckBoxMate Modernized // @namespace https://musicbrainz.org/user/chaban // @version 1.0 // @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(); })();