Automatically marks packages.lock.json files as Viewed on GitHub PRs
// ==UserScript==
// @name GitHub PR: Auto-View packages.lock.json
// @namespace https://github.com/annadongg
// @version 1.0.1
// @description Automatically marks packages.lock.json files as Viewed on GitHub PRs
// @author Anna Dong
// @match https://github.com/*/*/pull/*/changes*
// @match https://github.com/*/*/pull/*/changes
// @run-at document-idle
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const FILENAME_MATCHERS = [/packages\.lock\.json/i];
const DEBOUNCE_MS = 200;
const BUTTON = 'button[aria-label*="Viewed"]'
const NOT_VIEWED_BUTTON = 'button[aria-label*="Viewed"][aria-pressed=false]';
const VIEWED_BUTTON = 'button[aria-label*="Viewed"][aria-pressed=true]';
const isLockfile = (text) =>
text && FILENAME_MATCHERS.some((rx) => rx.test(text));
const debounce = (fn, wait) => {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
};
const mode = Object.freeze({
Viewed: 'VIEWED',
Unviewed: 'UNVIEWED'
});
let currentMode = null;
// if the user manually clicks the checkbox, ignore when automatically scanning
document.addEventListener('click', (e) => {
// ignores script-generated clicks
if (!e.isTrusted) return;
const checkbox = e.target.closest(BUTTON);
if (!checkbox) return;
const region = checkbox.closest('[role="region"]');
if (region) {
region.classList.add('monkey-ignore');
}
});
function toggleLockFiles() {
if (currentMode == null) return;
const regions = document.querySelectorAll('[role="region"][aria-labelledby]');
regions.forEach((region) => {
if (region.classList.contains('monkey-ignore')) return;
const headerId = region.getAttribute('aria-labelledby');
const headerEl = document.getElementById(headerId);
if (!headerEl) return;
const filename =
headerEl.textContent.trim();
if (!isLockfile(filename)) return;
const checkbox =
region.querySelector(currentMode === mode.Viewed ? NOT_VIEWED_BUTTON : VIEWED_BUTTON);
if (checkbox) {
checkbox.click();
}
});
}
function injectButtons() {
const bar = document.querySelector('section[class*="use-sticky-header"]');
if (!bar || bar.querySelector('.view-all-btn')) return;
const wrapper = document.createElement('div');
wrapper.appendChild(viewButton());
bar.querySelector('[class*="Stack"]').appendChild(wrapper);
}
function viewButton() {
const btn = document.createElement('button');
btn.textContent = 'Hide lockfiles';
btn.className = 'btn btn-sm view-all-btn';
btn.style.marginLeft = '8px';
btn.addEventListener('click', () => {
currentMode = currentMode == mode.Viewed || null ? mode.Unviewed : mode.Viewed;
const regions = document.querySelectorAll('[role="region"][aria-labelledby]');
regions.forEach((region) => region.classList.remove('monkey-ignore'));
btn.textContent = currentMode == mode.Viewed ? 'Show lockfiles' : 'Hide lockfiles';
toggleLockFiles();
});
return btn;
}
const debouncedFn = debounce(() => {
toggleLockFiles()
}, DEBOUNCE_MS);
// github progressively loads regions
// thia watches for changes and keeps checking for new regions :)
new MutationObserver(() => {
injectButtons();
debouncedFn();
}).observe(document.body, { childList: true, subtree: true });
// initial run
injectButtons();
debouncedFn();
})();