Skip to content

Instantly share code, notes, and snippets.

@tinder-tannerbennett
Last active May 10, 2025 20:51
Show Gist options
  • Save tinder-tannerbennett/7ab2091184dbad71f5387cca3960e65f to your computer and use it in GitHub Desktop.
Save tinder-tannerbennett/7ab2091184dbad71f5387cca3960e65f to your computer and use it in GitHub Desktop.
Tampermonkey userscripts for github.com that I use for work
// ==UserScript==
// @name pull/:id/files helpers (Approve PR)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Automatically clicks the "Review Changes" and "Approve" buttons on key press (\)
// @author Tanner Bennett
// @match https://github.com/TinderApp/tinder_ios/pull/*/files
// @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
function click(element) {
if (element) element.click();
}
document.addEventListener('keydown', function(event) {
// Skip modifier keys
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
// Abort if a text field is active
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') {
return;
}
// Go to back to conversation
if (event.key === 'b') {
click(document.querySelector('a[href^="/TinderApp/tinder_ios/pull/"]'))
}
// Approve PR
if (event.key === '\\') {
const button = document.getElementById('overlay-show-review-changes-modal');
if (button) {
button.click();
const radio = document.getElementById('pull_request_review[event]_approve');
if (radio) {
radio.click();
const button = document.querySelector('button.Button--primary.Button--small.Button.float-left');
if (button) {
button.click();
}
}
}
}
});
})();
// ==UserScript==
// @name pull/:id helpers
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Keyboard shortcuts for the GitHub PR page (pull/:id)
// @author Tanner Bennett
// @match https://github.com/TinderApp/tinder_ios/pull/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// A little jquery lite kinda API
function matches(el, text) {
return el && el.textContent.trim() === text;
}
function $(elOrSelector, selector) {
let target = elOrSelector;
// Case: only given a selector, use document
if (typeof elOrSelector === 'string') {
target = document;
selector = elOrSelector;
}
// Case: given an object and a selector
else {
selector = selector || '';
}
if (target.querySelector) {
return target.querySelector(selector);
}
return undefined;
}
function find(selector, where) {
const list = Array.from(document.querySelectorAll(selector));
return where ? list.find(where) : list;
}
function findByText(selector, text) {
return find(selector, el => {
const innerSpan = el.querySelector('span[data-component="text"]');
return matches(innerSpan, text);
});
}
function findByTextAll(selector, text) {
return Array.from(document.querySelectorAll(selector)).filter(el => {
const innerSpan = el.querySelector('span[data-component="text"]');
return innerSpan && innerSpan.textContent.trim() === text;
});
}
function gh_buttonByInnerText(text) {
return find('button span[data-component="text"]', btn => matches(btn, text));
}
function click(element) {
if (element) element.click();
}
function scrollToEndOfPage() {
window.scrollTo(0, document.body.scrollHeight);
}
document.addEventListener('keydown', function(event) {
// Skip modifier keys
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
// Abort if a text field is active
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') {
return;
}
// Ready for review / merge when ready
// Press r repeatedly to hit each of the 3 buttons below
if (event.key === 'r') {
scrollToEndOfPage();
// Find the button by the inner text of the innermost span
click(gh_buttonByInnerText('Ready for review'));
click(gh_buttonByInnerText('Merge when ready'));
click(gh_buttonByInnerText('Confirm merge when ready'));
}
// Go to changed files
if (event.key === 'f') {
click($('a[href$="/files"][href*="/pull/"]'))
}
// On shift + x, click "Close pull request"
if (event.key === 'X') {
scrollToEndOfPage();
click($('button[name="comment_and_close"]')); // Close pull request
click(gh_buttonByInnerText('Delete branch'));
}
// Copy some text in this format:
// @codeowner1 @codeowner2
// [PR title](PR URL) `+X -Y`
if (event.key === 'c') {
const prTitle = $('.js-issue-title.markdown-title');
const prUrl = window.location.href;
const prDiffText = $('.diffstat').innerText.trim();
// Codeowners buttons look like this:
/* <button name="button" type="button" id="codeowner-TinderApp/revenue-growth-ios" class="mr-2 btn-link muted-icon" aria-labelledby="tooltip-8cc9d586-5932-4bd5-9b62-4f9081d93b0e"> */
const codeownersButtons = find('button[id^="codeowner-"]');
const codeowners = Array.from(codeownersButtons).map(button => {
const id = button.id;
const codeowner = id.split('TinderApp/')[1];
return `@${codeowner}`;
});
// Copy to clipboard
const msg = `${codeowners.join(' ')}\n[${prTitle.innerText.trim()}](${prUrl}) \`${prDiffText}\``;
navigator.clipboard.writeText(msg);
console.log('Copied to clipboard:\n', msg);
}
});
})();
// ==UserScript==
// @name SSO Helper
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Trigger the SSO 'Continue' button with any keypress
// @author Tanner Bennett
// @match https://github.com/TinderApp/tinder_ios/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=gotinder.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
function $(elOrSelector, selector) {
let target = elOrSelector;
// Case: only given a selector, use document
if (typeof elOrSelector === 'string') {
target = document;
selector = elOrSelector;
}
// Case: given an object and a selector
else {
selector = selector || '';
}
if (target.querySelector) {
return target.querySelector(selector);
}
return undefined;
}
function matches(el, text) {
return el && el.textContent.trim() === text;
}
function find(selector, where) {
const list = Array.from(document.querySelectorAll(selector));
return where ? list.find(where) : list;
}
function gh_buttonByInnerText(text) {
return find('button', btn => matches(btn, text));
}
function click(element) {
if (element) element.click();
}
document.addEventListener('keydown', function(event) {
// Skip modifier keys
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
// Abort if a text field is active
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') {
return;
}
// If we're on the SSO page, click continue on any key press
if ($('h1.sso-title')) {
click(gh_buttonByInnerText('Continue'));
}
});
})();
@tinder-tannerbennett
Copy link
Author

Summary of Features

Continue with SSO Page

Shortcut Action
Any key Presses the "Continue" button

/pull/XXXXX

Shortcut Action
R Click "Ready for review" (1)
R Click "Merge when ready" (2)
R Click "Confirm merge when ready" (3)
F Navigate to the "Changed Files" tab
C Copy a review request for #ios-code-review
shift + X Click "Close pull request" (1)
shift + X Click "Delete branch" (2)

/pull/XXXXX/files

Shortcut Action
B Navigate [B]ack to the "Conversation" tab
\ Approve the pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment