Last active
June 22, 2023 08:20
-
-
Save 2xAA/e7e91dfe2fb48fb5bdcb5403c3ddce43 to your computer and use it in GitHub Desktop.
A userscript to add a copy branch name button and other useful defaults to Azure DevOps.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Better Azure DevOps | |
// @description Fixes AzureDevOps' bullshit | |
// @match *://dev.azure.com/* | |
// ==/UserScript== | |
const constants = { | |
copyBranchNameButton: "BAD-COPY_BRANCH_NAME_BUTTON", | |
}; | |
const callback = async (eventName, data) => { | |
const { pathname } = window.location; | |
// Branch page | |
if (pathname.match(/^\/.*\/_git\/[^/]*$/)) { | |
if (eventName === "pushState") { | |
// horrible hack, but there you go 🤷🏻 | |
await nothing(); | |
} | |
const dropdown = await waitForSelector(".version-dropdown"); | |
if (document.getElementById(constants.copyBranchNameButton)) { | |
return; | |
} | |
const branchName = dropdown.children[0].textContent; | |
const button = createCopyButton(branchName, constants.copyBranchNameButton); | |
insertAfter(inlineMenuItem(button), dropdown); | |
} | |
// Pull Request | |
if (pathname.match(/^\/.*\/_git\/.*\/pullrequest\/([0-9]*)$/)) { | |
const branchNameEl = await waitForSelector( | |
".pr-secondary-title-row .pr-header-branches > a:first-child" | |
); | |
if (document.getElementById(constants.copyBranchNameButton)) { | |
return; | |
} | |
const branchName = branchNameEl.textContent; | |
const button = createCopyButton(branchName, constants.copyBranchNameButton); | |
insertAfter(inlineMenuItem(button), branchNameEl); | |
// Set active comments as default activity filter | |
const activityFilterDropdownButton = await waitForSelector( | |
".repos-activity-filter-dropdown button" | |
); | |
activityFilterDropdownButton.click(); | |
const activeCommentsDropdownItem = await waitForSelector( | |
"#__bolt-active_comments" | |
); | |
activeCommentsDropdownItem.click(); | |
} | |
}; | |
(() => { | |
callback(); | |
const pushState = history.pushState; | |
history.pushState = function () { | |
pushState.apply(history, arguments); | |
callback("pushState", arguments); | |
}; | |
})(); | |
// https://stackoverflow.com/a/61511955 | |
function waitForSelector(selector) { | |
return new Promise((resolve) => { | |
if (document.querySelector(selector)) { | |
return resolve(document.querySelector(selector)); | |
} | |
const observer = new MutationObserver(() => { | |
if (document.querySelector(selector)) { | |
resolve(document.querySelector(selector)); | |
observer.disconnect(); | |
} | |
}); | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true, | |
}); | |
}); | |
} | |
function insertAfter(newNode, referenceNode) { | |
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); | |
} | |
function applyButtonStyles(node) { | |
const styles = | |
"icon-only bolt-button bolt-icon-button enabled icon-only bolt-focus-treatment".split( | |
" " | |
); | |
styles.forEach((style) => node.classList.add(style)); | |
return node; | |
} | |
function inlineMenuItem(node) { | |
const div = document.createElement("div"); | |
div.className = | |
"bolt-header-command-item-button bolt-expandable-button inline-flex-row"; | |
div.appendChild(node); | |
return div; | |
} | |
function createCopyButton(textToCopy, id) { | |
const button = document.createElement("button"); | |
const span = document.createElement("span"); | |
span.classList.add("left-icon", "flex-noshrink", "fabric-icon", "ms-Icon--Copy"); | |
button.appendChild(span); | |
button.id = id; | |
button.title = `Copy "${textToCopy}" to clipboard`; | |
button.addEventListener("click", async () => { | |
await navigator.clipboard.writeText(textToCopy); | |
button.textContent = "🫡"; | |
setTimeout(() => { | |
button.textContent = ""; | |
button.appendChild(span); | |
}, 1300); | |
}); | |
applyButtonStyles(button); | |
return button; | |
} | |
function sleep(milliseconds) { | |
return new Promise((resolve) => setTimeout(resolve, milliseconds)); | |
} | |
function nothing() { | |
return new Promise((resolve) => resolve()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment