您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlights sets of identical barcodes and toggles checkboxes for merging on click
// ==UserScript== // @name MusicBrainz: Highlight identical barcodes and toggle merge checkboxes // @namespace https://musicbrainz.org/user/chaban // @version 1.2 // @tag ai-created // @description Highlights sets of identical barcodes and toggles checkboxes for merging on click // @author chaban // @license MIT // @match *://*.musicbrainz.org/*/*/releases* // @match *://*.musicbrainz.org/release-group/* // @match *://*.musicbrainz.org/label/* // @match *://*.musicbrainz.org/*/*/*edits // @match *://*.musicbrainz.org/edit/* // @match *://*.musicbrainz.org/user/*/edits* // @match *://*.musicbrainz.org/search/edits* // @icon https://musicbrainz.org/static/images/favicons/android-chrome-512x512.png // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const identifierToColor = {}; const identifierToCheckboxes = {}; function getRandomColor() { const letters = '89ABCDEF'; let color = '#'; for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * letters.length)]; } return color; } function removeLeadingZeros(barcode) { return barcode.replace(/^0+/, ''); } /** * Toggles the checkboxes for the entire group associated with the clicked barcode cell. * @param {Event} event The click event. */ function toggleMergeCheckbox(event) { const clickedBarcodeCell = event.currentTarget; const clickedIdentifier = clickedBarcodeCell.dataset.barcodeIdentifier; if (!clickedIdentifier || !identifierToCheckboxes[clickedIdentifier]) { return; } const currentGroupCheckboxes = identifierToCheckboxes[clickedIdentifier]; const shouldCheck = !currentGroupCheckboxes.some(cb => cb.checked); const allCheckboxesOnPage = document.querySelectorAll('input[name="add-to-merge"][type="checkbox"]'); allCheckboxesOnPage.forEach(checkbox => { const row = checkbox.closest('tr'); if (!row) return; const barcodeCellInThisRow = row.querySelector('.barcode-cell[data-barcode-identifier]'); if (barcodeCellInThisRow && barcodeCellInThisRow.dataset.barcodeIdentifier === clickedIdentifier) { checkbox.checked = shouldCheck; } else { checkbox.checked = false; } }); } /** * Processes a given table element to find and highlight identical barcodes. * @param {HTMLElement} table The table element to process. */ function processTable(table) { const barcodeCellsInTable = {}; let barcodeColumnIndex = -1; let formatColumnIndex = -1; let headerRow = table.querySelector('thead tr'); if (!headerRow) { headerRow = table.querySelector('tr:has(th)'); } if (!headerRow) { headerRow = table.querySelector('tbody tr'); } if (headerRow) { const headerCells = Array.from(headerRow.children); headerCells.forEach((th, index) => { const headerText = th.textContent.trim(); if (headerText === 'Barcode') { barcodeColumnIndex = index; } if (headerText === 'Format') { formatColumnIndex = index; } }); } const dataRows = table.querySelectorAll('tbody tr, tr:not(:has(th)):not(:first-child)'); dataRows.forEach(row => { let barcodeCell = null; let formatCell = null; if (barcodeColumnIndex !== -1 && row.children[barcodeColumnIndex]) { barcodeCell = row.children[barcodeColumnIndex]; } if (formatColumnIndex !== -1 && row.children[formatColumnIndex]) { formatCell = row.children[formatColumnIndex]; } if (!barcodeCell || barcodeCell.tagName === 'TH') { const potentialBarcodeCell = row.querySelector('.barcode-cell'); if (potentialBarcodeCell && potentialBarcodeCell.tagName === 'TD') { barcodeCell = potentialBarcodeCell; } } if (barcodeCell && barcodeCell.tagName === 'TD') { const barcode = barcodeCell.textContent.trim(); const format = formatCell ? formatCell.textContent.trim() : ''; const mergeCheckbox = row.querySelector('input[name="add-to-merge"][type="checkbox"]'); if (barcode !== '[none]' && barcode !== '') { const normalizedBarcode = removeLeadingZeros(barcode); const identifier = `${normalizedBarcode}-${format}`; barcodeCell.dataset.barcodeIdentifier = identifier; if (!barcodeCellsInTable[identifier]) { barcodeCellsInTable[identifier] = []; } barcodeCellsInTable[identifier].push(barcodeCell); if (mergeCheckbox) { if (!identifierToCheckboxes[identifier]) { identifierToCheckboxes[identifier] = []; } identifierToCheckboxes[identifier].push(mergeCheckbox); } } } }); for (const identifier in barcodeCellsInTable) { if (barcodeCellsInTable[identifier].length > 1) { let color = identifierToColor[identifier]; if (!color) { color = getRandomColor(); identifierToColor[identifier] = color; } barcodeCellsInTable[identifier].forEach(cell => { cell.style.backgroundColor = color; cell.style.fontWeight = 'bold'; cell.style.padding = '2px 4px'; cell.style.borderRadius = '3px'; if (identifierToCheckboxes[identifier] && identifierToCheckboxes[identifier].length > 0) { cell.style.cursor = 'pointer'; cell.addEventListener('click', toggleMergeCheckbox); } else { cell.style.cursor = 'auto'; cell.removeEventListener('click', toggleMergeCheckbox); } }); } } } function highlightBarcodesOnPage() { document.querySelectorAll('.mergeable-table, table.merge-releases').forEach(table => { processTable(table); }); } highlightBarcodesOnPage(); })();