您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a native-looking, text-input to the Azure DevOps branches page to filter by author
// ==UserScript== // @name Azure DevOps In-Page Branch Author Filter // @namespace https://github.com/Rubenisme/Azure-DevOps-Branch-Author-Filter // @version 2.4 // @description Adds a native-looking, text-input to the Azure DevOps branches page to filter by author // @author Rubenisme // @match https://dev.azure.com/*/_git/*/branches* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; let activeFilterCleanup = null; /** * Manages the visibility of virtual list spacers. * @param {boolean} shouldBeVisible True to show spacers, false to hide them. */ function setSpacerVisibility(shouldBeVisible) { const table = document.querySelector('table[aria-label="Branches table"]'); if (table) { const spacers = table.querySelectorAll('tbody tr.bolt-list-row-spacer'); spacers.forEach(spacer => { spacer.style.display = shouldBeVisible ? '' : 'none'; }); } } /** * The core filtering logic. * @param {string} targetAuthor The text to filter by. */ function filterByAuthor(targetAuthor) { if (activeFilterCleanup) { activeFilterCleanup(); } // --- FILTER IS BEING CLEARED --- if (!targetAuthor || typeof targetAuthor !== 'string' || targetAuthor.trim() === '') { setSpacerVisibility(true); // Show the spacers again return; } const table = document.querySelector('table[aria-label="Branches table"]'); if (!table) return; const tbody = table.querySelector('tbody'); if (!tbody) return; // --- FILTER IS BEING APPLIED --- setSpacerVisibility(false); // Hide the spacers to prevent empty gaps const headers = table.querySelectorAll('thead th[data-column-index]'); let authorColIndex = -1; headers.forEach(th => { if (th.innerText.trim() === 'Author') { authorColIndex = th.getAttribute('data-column-index'); } }); if (authorColIndex === -1) return; const applyFilterToRow = (row) => { if (!row.matches('tr.bolt-table-row')) return; const authorCell = row.querySelector(`td[data-column-index="${authorColIndex}"]`); if (!authorCell) return; const authorNameSpan = authorCell.querySelector('span.text-ellipsis'); const currentAuthor = authorNameSpan ? authorNameSpan.innerText.trim() : ''; if (currentAuthor.toLowerCase().includes(targetAuthor.toLowerCase())) { row.style.display = ''; } else { row.style.display = 'none'; } }; const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TR') { applyFilterToRow(node); } }); } }); tbody.querySelectorAll('tr').forEach(applyFilterToRow); observer.observe(tbody, { childList: true }); activeFilterCleanup = () => { observer.disconnect(); if (tbody) { tbody.querySelectorAll('tr').forEach(row => row.style.display = ''); } activeFilterCleanup = null; }; } /** * Creates and injects the filter UI. */ function injectFilterUI(filterBar) { const wrapperDiv = document.createElement("div"); wrapperDiv.className = "flex-column flex-grow bolt-textfield-inline-tabbar-width"; wrapperDiv.id = "userscript-author-filter-wrapper"; wrapperDiv.style.marginLeft = "8px"; const filterContainer = document.createElement("div"); filterContainer.className = "bolt-text-filterbaritem flex-grow bolt-textfield flex-row flex-center focus-keyboard-only bolt-textfield-inline"; const iconSpan = document.createElement("span"); iconSpan.className = "fluent-icons-enabled"; iconSpan.innerHTML = '<span aria-hidden="true" class="keyword-filter-icon prefix bolt-textfield-icon bolt-textfield-no-text flex-noshrink fabric-icon ms-Icon--Filter medium"></span>'; const input = document.createElement("input"); input.className = "bolt-text-filterbaritem-input bolt-textfield-input flex-grow bolt-textfield-input-with-prefix"; input.placeholder = "Filter by author..."; input.setAttribute('aria-label', 'Filter by author'); input.type = "text"; let debounceTimer; input.addEventListener('input', (event) => { clearTimeout(debounceTimer); const searchText = event.target.value; debounceTimer = setTimeout(() => { filterByAuthor(searchText); }, 500); // 500ms delay }); filterContainer.appendChild(iconSpan); filterContainer.appendChild(input); wrapperDiv.appendChild(filterContainer); filterBar.appendChild(wrapperDiv); } /** * Main execution block. */ const injectionInterval = setInterval(() => { const filterBar = document.querySelector(".bolt-inline-keyword-filter-bar"); const alreadyInjected = document.getElementById("userscript-author-filter-wrapper"); if (filterBar && !alreadyInjected) { clearInterval(injectionInterval); injectFilterUI(filterBar); } }, 500); })();