Last active
February 6, 2025 20:45
-
-
Save jjspace/df4fc6eaa587f70e60985977205e0787 to your computer and use it in GitHub Desktop.
Colorize repository and user names across various GitHub pages to make it easier to spot them and skim for matching names.
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 Colorcode GH | |
// @description Colorize repository and user names to allow for easier identification and skiming. | |
// | |
// @author jjspace | |
// @namespace https://github.com/jjspace | |
// @downloadURL https://gist.github.com/jjspace/df4fc6eaa587f70e60985977205e0787/raw/colorcode-gh.user.js | |
// | |
// @version 0.5.3 | |
// @updateURL https://gist.github.com/jjspace/df4fc6eaa587f70e60985977205e0787/raw/colorcode-gh.user.js | |
// | |
// @match https://github.com/* | |
// | |
// @require https://raw.githubusercontent.com/bgrins/TinyColor/master/tinycolor.js | |
// ==/UserScript== | |
/** | |
* Changelog: | |
* see full code changes: https://gist.github.com/jjspace/df4fc6eaa587f70e60985977205e0787/revisions | |
* | |
* 0.5.3 | |
* - Adjust repo name detection for shorthand issue links | |
* - Run through Prettier for sanity | |
* | |
* 0.5.2 | |
* - Fix repo coloring in issue links to avoid custom links | |
* | |
* 0.5.1 | |
* - Fix username colors on the issues list page | |
* | |
* 0.5.0 | |
* - Fix user and repo link colors in new issue preview style | |
* - This will likely need many more little adjustments as GitHub messes with things and makes it harder | |
* for tools and styles like this to exist | |
* | |
* 0.4.16 | |
* - Fix broken selector for notification sidebar | |
* | |
* 0.4.15 | |
* - removed an extra leftover console log | |
* | |
* 0.4.14 | |
* - small fix/adjustment for `/` in issue names | |
* | |
* 0.4.13 | |
* - Remove "magic colors" and consolidate into constants | |
* - Always use theme colors (previously used mix of dark AND dark_dimmed) | |
* - Debug option to ignore the new cache, sill WIP | |
* - Format with prettier (finally) | |
* | |
* 0.4.12 | |
* - Add caching in local storage to even further improve performance and load times after | |
* a color has already been calculated | |
* | |
* 0.4.11 | |
* - Add support for repo shorthands in expanded issue links that point to a different repo | |
* | |
* 0.4.10 | |
* - Change global style to affect ALL links that have a color span inside to account for repo links | |
* | |
* 0.4.9 | |
* - Add some global styles to avoid double underline on user links | |
* - Github added underlines to ALL links by default, more work will be needed to make this | |
* look good https://github.com/orgs/community/discussions/68734 | |
* | |
* 0.4.8 | |
* - Modify debounce to call immediately on the first call then debounce the rest. | |
* This is an attempt to reduce the "flash of uncolored page" delay | |
* - Filter the change list to ignore many changes in parts of the page that don't matter. | |
* This will probably be fine tuned more in the future | |
* - Add a check to not create a new page observer if it already exists. This should prevent | |
* SPA behavior from causing multiple observers as a precaution | |
* - Improved logging for when debugging/developing | |
* | |
* 0.4.7 | |
* - Cache calculated color to avoid recomputing on larger pages, vastly increases performance | |
* - Re-add debounce function to avoid many many mutation calls when loading larger pages. | |
* This will add a very slight delay to styling on initial load but it doesn't feel intrusive | |
* | |
* 0.4.6 | |
* - Remove debounce again. Still seeing the slow loading of pages (seems like GH's issue) | |
* AND this made the notification page colors flicker. Will have to revisit this solution. | |
* | |
* 0.4.5 | |
* - Add debounce to color page call to reduce the number of updates on page load for large issues | |
* | |
* 0.4.4 | |
* - highlight team mentions the same as user mentions | |
* | |
* 0.4.3 | |
* - color repo names of links to issues or PRs in issue/pr comments | |
* | |
* 0.4.2 | |
* - don't style the repo name on project boards, focus only on usernames | |
* - adjust spanify to use the .color-gh-repo class | |
* - fix detection on github.com/pulls and github.com/issues | |
* | |
* 0.4.1 | |
* - Switched to a function per page approach | |
* - Adjusted for new SPA stuff github implemented | |
* | |
* 0.3.10 | |
* - add colors to top level /pulls and /issues pages | |
* | |
* 0.3.9 | |
* - fix update url | |
* - correctly color repo name when it matches org/user name | |
* | |
* Previous: | |
* I didn't start tracking before this, see the gist revisions | |
* https://gist.github.com/jjspace/df4fc6eaa587f70e60985977205e0787/revisions | |
*/ | |
/*global tinycolor*/ | |
// check out this color function if I don't want to include tinycolor anymore | |
// https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors | |
(function () { | |
'use strict'; | |
const DEBUG = false; | |
const IGNORE_STORAGE_CACHE = false; | |
const group = (...label) => { | |
if (DEBUG) console.group(...label); | |
}; | |
const groupEnd = () => { | |
if (DEBUG) console.groupEnd(); | |
}; | |
const log = (...args) => { | |
if (DEBUG) console.log(...args); | |
}; | |
const addStylesheet = (rules) => { | |
const styleEl = document.createElement('style'); | |
styleEl.id = 'customSheet'; | |
document.head.appendChild(styleEl); | |
const styleSheet = styleEl.sheet; | |
rules.forEach((rule) => { | |
const [selector, props] = rule; | |
let propStr = Object.entries(props).reduce((acc, [prop, val]) => { | |
return acc + prop.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()) + `:${val};`; | |
}, ''); | |
styleSheet.insertRule(`${selector}{${propStr}}`, styleSheet.cssRules.length); | |
}); | |
return styleSheet; | |
}; | |
// add some global styles for the page | |
function createStylesheet() { | |
addStylesheet([ | |
// github added underlines to ALL links by default and it looks | |
// bad with the script changes | |
['a:has(.color-gh-repo)', { textDecoration: 'none !important' }], | |
]); | |
} | |
// only create styles if they dont exist | |
if (!document.querySelector('style#customSheet')) { | |
createStylesheet(); | |
} | |
// used to create a hex color from a given string | |
// https://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-javascript | |
function hashCode(str) { | |
// java String#hashCode | |
var hash = 0; | |
for (var i = 0; i < str.length; i++) { | |
hash = str.charCodeAt(i) + ((hash << 5) - hash); | |
} | |
return hash; | |
} | |
function intToRGB(i) { | |
var c = (i & 0x00ffffff).toString(16).toUpperCase(); | |
return '00000'.substring(0, 6 - c.length) + c; | |
} | |
function getCurrentTheme() { | |
const { dataset } = document.querySelector('html'); | |
const isDarkMode = | |
dataset.colorMode === 'dark' || | |
(dataset.colormode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches); | |
return isDarkMode ? dataset.darkTheme : dataset.lightTheme; | |
} | |
const colorsPerTheme = { | |
light: { | |
canvasDefault: '#ffffff', | |
canvasSubtle: '#f6f8fa', | |
}, | |
dark: { | |
canvasDefault: '#0d1117', | |
canvasSubtle: '#161b22', | |
}, | |
dark_dimmed: { | |
canvasDefault: '#22272e', | |
canvasSubtle: '#2d333b', | |
}, | |
}; | |
const chosenTheme = colorsPerTheme[getCurrentTheme] ?? colorsPerTheme.dark_dimmed; | |
const ThemeColors = { | |
pageBackground: chosenTheme.canvasDefault, // --color-canvas-default | |
notifReadBg: chosenTheme.canvasDefault, // --color-notificaitons-row-read-bg > --color-canvas-default | |
notifUnreadBg: chosenTheme.canvasSubtle, // --color-notifications-row-bg > --color-canvas-subtle | |
}; | |
// store text to color map to reduce recalculations | |
let textToColorCache = new Map(); | |
// store calculated readable colors in case 2 different inputs end up at the same color | |
const readableColorCache = new Set(); | |
function localStorageKey() { | |
return `colorcode-gh-colors/${getCurrentTheme()}`; | |
} | |
function loadTextCache() { | |
if (IGNORE_STORAGE_CACHE) { | |
return; | |
} | |
textToColorCache = new Map(JSON.parse(localStorage.getItem(localStorageKey()) ?? '[]')); | |
log('load cache from localstorage', textToColorCache); | |
} | |
loadTextCache(); | |
function saveTextCache() { | |
if (IGNORE_STORAGE_CACHE) { | |
return; | |
} | |
log('save cache to localstorage', textToColorCache); | |
localStorage.setItem( | |
localStorageKey(), | |
JSON.stringify(textToColorCache, (key, value) => (value instanceof Map ? [...value] : value)) | |
); | |
} | |
// TODO: these bgColors probably shouldn't be passed in but consistent across ALL calls | |
// so that the same color is generated for every location of the same text regardless | |
function getReadableColorForText(text, bgColors) { | |
log('getReadableColorForText', text); | |
if (textToColorCache.has(text)) { | |
log(' in cache'); | |
return textToColorCache.get(text); | |
} | |
// check if a color is readable on read and unread backgrounds | |
const isGoodColor = (color) => { | |
if (readableColorCache.has(color)) { | |
return true; | |
} | |
const guideline = { level: 'AA', size: 'small' }; | |
const isGood = bgColors.reduce((acc, bgColor) => { | |
return acc && tinycolor.isReadable(color, bgColor, guideline); | |
}, true); | |
//log('isGood', isGood); | |
return isGood; | |
}; | |
const color = intToRGB(hashCode(text)); | |
let readableColor = tinycolor(color); | |
//group('readableText', text); | |
//log(readableColor.toHex()); | |
let times = 0; | |
const maxTimes = 10; | |
const currentTheme = getCurrentTheme(); | |
const isLightMode = currentTheme === 'light'; | |
// lighten the color until it's readable or we've tried to lighten maxTimes | |
while (!isGoodColor(readableColor) && times < maxTimes) { | |
if (isLightMode) { | |
// TODO: I don't know the best way to adjust for light mode to give a pop of color | |
// while not getting too dark that it looks black. may need to also change the size of the line | |
// readableColor = readableColor.darken().saturate(); | |
} else { | |
readableColor = readableColor.lighten().desaturate(); | |
} | |
//log(readableColor.toHex()); | |
times++; | |
} | |
//groupEnd(); | |
const colorHex = readableColor.toHex(); | |
readableColorCache.add(readableColor); | |
textToColorCache.set(text, colorHex); | |
return colorHex; | |
} | |
/** | |
* Add a border to the bottom of the given element | |
* color coded based on the text inside the element | |
* @param {HTMLElement} element the element to color | |
* @param {Array<string>} [bgColors] the background colors to ensure readability | |
* @param {object} [options={}] | |
* @param {boolean} [options.asTextDecoration=false] apply text decoration instead of a border, may look better depending on circumstances | |
*/ | |
function colorElement(element, bgColors = ['#000'], { asTextDecoration = false } = {}) { | |
if (!element) { | |
log('colorElement: element not found', element); | |
return; | |
} | |
let text = element.innerText; | |
const readableColor = getReadableColorForText(text, bgColors); | |
// Some elements aren't sized nicely for the border bottom style so | |
// utilize the text decoration instead | |
if (!asTextDecoration) { | |
element.style.borderBottom = `1px solid #${readableColor}`; | |
} else { | |
element.style.textDecoration = `underline #${readableColor}`; | |
// create a little extra spacing to make it easier to see the color highlight | |
// and match how it would look if it was the border method | |
element.style.textUnderlineOffset = '2px'; | |
} | |
} | |
function updateOnMutate( | |
target, | |
callback, | |
ignoreMutation = (mutationTarget, addedNodes, removedNodes) => false | |
) { | |
if (window.colorizeObserver) { | |
log('%cObserver already set up, skipping creation', 'color: red; font-weight: bold;'); | |
} | |
// call once before waiting for mutations | |
callback(); | |
const mutationCallback = (mutationsList, observer) => { | |
group('mutation triggered'); | |
for (const mutation of mutationsList) { | |
// only react to a change on the whole list | |
if (ignoreMutation(mutation.target, mutation.addedNodes, mutation.removedNodes)) { | |
log('mutation ignored', mutation.target.tagName, mutation.target.className); | |
break; | |
} | |
if (mutation.type === 'childList') { | |
log('childlist mutation', mutation); | |
callback(); | |
// the callback should only be called once per mutation set, | |
// it affects the whole page | |
break; | |
} else if (mutation.type === 'subtree') { | |
log('subtree mutation'); | |
} | |
} | |
groupEnd(); | |
}; | |
const observer = new MutationObserver(mutationCallback); | |
observer.observe(target, { childList: true, subtree: true }); | |
log('observer listening', target.className); | |
window.colorizeObserver = observer; | |
} | |
/** | |
* Wrap the specified term in a span to enable styling later | |
* @param {Element} containerElem | |
* @param {string} searchTerm | |
* @param {string} [type] add an additional type-[type] to this span for easier debug identification | |
*/ | |
function spanify(containerElem, searchTerm, type) { | |
// TODO: create a generic spanify to enable colorizing any elements | |
// For example, more temporary keyword highlighting of notifs | |
if (containerElem.querySelectorAll('span[data-colorize]').length > 0) { | |
// TODO: add better detection as maybe we want different terms highlighted | |
// we've already spanified this elem | |
return; | |
} | |
const typeClass = type ? `type-${type}` : ''; | |
containerElem.innerHTML = containerElem.innerHTML.replace( | |
searchTerm, | |
`<span class="color-gh-repo ${typeClass}" data-colorize>${searchTerm}</span>` | |
); | |
return containerElem.querySelector('span.color-gh-repo'); | |
} | |
function colorizeNotifPage() { | |
log('colorizeNotifPage called'); | |
// access and extract the repo data | |
const sourceSelector = '.js-navigation-item [id^=notification] p.f6'; | |
//const sourcePattern = /(?<user>\w+)\/(?<repo>[\w-]+) #(?<id>\d+)/; // old that includes id number | |
// username regex https://github.com/shinnn/github-username-regex | |
const sourcePattern = /(?<user>[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})\/(?<repo>[\w\-\.]+)/i; | |
document.querySelectorAll(sourceSelector).forEach((notifSource) => { | |
try { | |
const source = notifSource.innerText; | |
const match = source.match(sourcePattern); | |
if (!match) { | |
log('no user/repo combo found', source); | |
return; | |
} | |
const { user, repo, id } = match.groups; | |
// wrap repo name in span if it's not already | |
if (!notifSource.querySelector('span.color-gh-repo')) { | |
//notifSource.innerHTML = notifSource.innerHTML.replace(repo, `<span>${repo}</span>`); | |
// we need to replace the LAST occurance of the repo name in case the org and repo are the same | |
const lastIndex = notifSource.innerHTML.lastIndexOf(repo); | |
const replacement = `<span class="color-gh-repo">${repo}</span>`; | |
notifSource.innerHTML = | |
notifSource.innerHTML.substring(0, lastIndex) + | |
replacement + | |
notifSource.innerHTML.substring(lastIndex + repo.length + 1); | |
} else { | |
// if it was already put in a span, | |
// it should have already been colorized | |
return; | |
} | |
const repoSpan = notifSource.querySelector('span.color-gh-repo'); | |
colorElement(repoSpan, [ThemeColors.notifReadBg, ThemeColors.notifUnreadBg], { | |
asTextDecoration: true, | |
}); | |
} catch (err) { | |
console.error('colorize error', err); | |
} | |
}); | |
// access and extract sidebar repo list for a color key | |
const sidebarSelector = '.js-notification-sidebar-repositories .ActionListWrap a'; | |
document.querySelectorAll(sidebarSelector).forEach((repoLink) => { | |
try { | |
const repoText = repoLink.innerText; | |
if (!repoText) { | |
log('no repotext', repoLink); | |
return; | |
} | |
const match = repoText.match(sourcePattern); | |
if (!match) { | |
log('no user/repo combo found', repoText); | |
return; | |
} | |
const { user, repo } = match.groups; | |
// wrap repo name in span if it's not already - There is already one span in here for the Number of notifs | |
if (!repoLink.querySelector('span.color-gh-repo')) { | |
// we need to replace the LAST occurance of the repo name in case the org and repo are the same | |
const lastIndex = repoLink.innerHTML.lastIndexOf(repo); | |
const replacement = `<span class="color-gh-repo">${repo}</span>`; | |
repoLink.innerHTML = | |
repoLink.innerHTML.substring(0, lastIndex) + | |
replacement + | |
repoLink.innerHTML.substring(lastIndex + repo.length + 1); | |
log(`set span around sidebar repo ${repo}`); | |
} else { | |
// if it was already put in a span, | |
// it should have already been colorized | |
return; | |
} | |
const repoSpan = repoLink.querySelector('span.color-gh-repo'); | |
colorElement(repoSpan, [ThemeColors.notifReadBg, ThemeColors.notifUnreadBg], { | |
asTextDecoration: true, | |
}); | |
} catch (err) { | |
console.error('colorize error', err); | |
} | |
}); | |
const notifTypeSelector = '.AvatarStack + span'; | |
document.querySelectorAll(notifTypeSelector).forEach((notifTypeSpan) => { | |
colorElement(notifTypeSpan, [ThemeColors.notifReadBg, ThemeColors.notifUnreadBg], { | |
asTextDecoration: true, | |
}); | |
}); | |
} | |
function colorizeCards() { | |
log('styling project board'); | |
const cardUserSelector = | |
'article.issue-card .js-project-issue-details-container .js-issue-number ~ a:first-of-type'; | |
document.querySelectorAll(cardUserSelector).forEach((elem) => { | |
colorElement(elem, [ThemeColors.notifUnreadBg]); | |
}); | |
// these are the "Added by ..." wrapper cards | |
document.querySelectorAll('.mr-4 small a').forEach((elem) => { | |
colorElement(elem, [ThemeColors.notifUnreadBg]); | |
}); | |
} | |
function colorizeRepoNames() { | |
log('styling pr/issues page'); | |
const repoSelector = '[data-hovercard-type=repository]'; // <-- this makes it really easy but could break in the future | |
document.querySelectorAll(repoSelector).forEach((repoLink) => { | |
const repoText = repoLink.innerText; | |
const sourcePattern = /(?<user>[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})\/(?<repo>[\w\-\.]+)/i; | |
const { user, repo } = repoText.match(sourcePattern).groups; | |
// wrap repo name in span if it's not already - There is already one span in here for the Number of notifs | |
if (repoLink.querySelectorAll('span').length < 1) { | |
// we need to replace the LAST occurance of the repo name in case the org and repo are the same | |
const lastIndex = repoLink.innerHTML.lastIndexOf(repo); | |
const replacement = `<span class="color-gh-repo">${repo}</span>`; | |
repoLink.innerHTML = | |
repoLink.innerHTML.substring(0, lastIndex) + | |
replacement + | |
repoLink.innerHTML.substring(lastIndex + repo.length + 1); | |
log(`set span around sidebar repo ${repo}`); | |
} else { | |
// if it was already put in a span, | |
// it should have already been colorized | |
return; | |
} | |
const repoSpan = repoLink.querySelector('span.color-gh-repo'); | |
colorElement(repoSpan, [ThemeColors.notifReadBg, ThemeColors.notifUnreadBg]); | |
}); | |
} | |
function colorIssuesOrPulls() { | |
log('colorIssuesOrPulls called'); | |
// recheck inside mutate handler for when page changes | |
const { pathname } = window.location; | |
if (pathname.includes('issues') || pathname.includes('pulls')) { | |
document.querySelectorAll('.opened-by a, [class*=authorCreatedLink]').forEach((elem) => { | |
colorElement(elem, [ThemeColors.notifReadBg]); | |
}); | |
} | |
} | |
// colorize user mentions in issues/PRs | |
function colorIssuePRPage() { | |
// color user mentions in issue/pr comments and messages | |
document | |
.querySelectorAll( | |
':is(.timeline-comment, .review-comment, [id^=issuecomment]+div, .markdown-body) :is(.user-mention, .team-mention)' | |
) | |
.forEach((elem) => { | |
// strip off the `@` symbol | |
const userName = elem.innerText.substr(1); | |
spanify(elem, userName); | |
colorElement(elem.querySelector('span[data-colorize]'), [ThemeColors.pageBackground], { | |
asTextDecoration: true, | |
}); | |
}); | |
// color repo names in links to other issues | |
// TODO: this will unintentionally pick up any issue that's expanded but has a `/` in the name like `remove/change` | |
document | |
.querySelectorAll( | |
// if there is an issue-shorthand the repo name is in there. Helps slightly solve the `/` in issue names | |
':is(.timeline-comment, .review-comment, .markdown-body) .issue-link:not(:has(.issue-shorthand))' | |
) | |
.forEach((elem) => { | |
const sourcePattern = /(?<user>[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})\/(?<repo>[\w\-\.]+)/i; | |
const match = elem.innerText.match(sourcePattern); | |
// TODO: should the matching here be done on the href instead? That's probably more reliable to pull out | |
// the actual repo name then spanify and match against that name. Could help avoid underlining the whole link like sometimes happens | |
if (match) { | |
// some repo links are just the #[number] format | |
const { repo } = match.groups; | |
const newSpan = spanify(elem, repo, 'issue-link'); | |
if (newSpan) { | |
colorElement(newSpan, [ThemeColors.pageBackground], { | |
asTextDecoration: true, | |
}); | |
} | |
} | |
}); | |
// Some issue links point to another repo with an extra muted text + #11 section | |
log('colorize shorthands'); | |
const shorthandSelector = '.issue-shorthand, a[href*="/issues/"]:not(.issue-link)'; | |
document.querySelectorAll(shorthandSelector).forEach((shorthand) => { | |
const shorthandPattern = | |
/(?:(?<user>[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})\/)?(?<repo>[\w\-\.]+)#(?<number>\d+)/i; | |
const match = shorthand.innerText.match(shorthandPattern); | |
if (!match) { | |
return; | |
} | |
const { repo, number } = match.groups; | |
if (!number) { | |
// with the broad selection based on hrefs we can catch custom labeled links | |
// which we don't want to style. this will still probably run into issues when | |
// the link name also contains a # but I'll fix that later. | |
return; | |
} | |
if (shorthand.querySelectorAll('span').length < 1) { | |
spanify(shorthand, repo, 'shorthand'); | |
log(`set span around shorthand ${repo}`); | |
} else { | |
// if it was already put in a span, | |
// it should have already been colorized | |
return; | |
} | |
const repoSpan = shorthand.querySelector('span.color-gh-repo'); | |
colorElement(repoSpan, [ThemeColors.notifReadBg, ThemeColors.notifUnreadBg], { | |
asTextDecoration: true, | |
}); | |
}); | |
} | |
function colorForPage() { | |
const { pathname } = window.location; | |
log('%ccolorForPage', 'color: blue; font-weight: bold;', window.location.href); | |
if (pathname.includes('notifications')) { | |
colorizeNotifPage(); | |
} else if (pathname.includes('projects')) { | |
colorizeCards(); | |
} else if (pathname.startsWith('/pulls') || pathname.startsWith('/issues')) { | |
colorizeRepoNames(); | |
} else { | |
// if we're on any page | |
colorIssuesOrPulls(); | |
if (document.querySelector('.notifications-list-item')) { | |
colorizeNotifPage(); | |
} | |
colorIssuePRPage(); | |
} | |
saveTextCache(); | |
} | |
log('%c===== COLORIZER STARTED =====', 'color: green; font-weight: bold;'); | |
function debounce(func, timeout = 300) { | |
let timer; | |
return (...args) => { | |
log('debounce called, timer:', timer); | |
if (!timer) { | |
log('no timer, call func'); | |
func.apply(this, args); | |
timer = setTimeout(() => { | |
// set the timer so we ignore the first debounce call | |
// but start debouncing after the second one | |
log('timer reset', timer); | |
timer = undefined; | |
}, timeout); | |
return; | |
} | |
clearTimeout(timer); | |
timer = setTimeout(() => { | |
log('debounce done', timer, ', call func'); | |
func.apply(this, args); | |
timer = undefined; | |
}, timeout); | |
}; | |
} | |
// attempt to avoid lagging page on change or after moving between many pages | |
const debouncedColorForPage = debounce(() => colorForPage(), 200); | |
// github does some SPA type history stuff on repo pages, to account | |
// for that this currently watches the whole page for changes which is | |
// much broader than I'd like but it seems the whole `body` element | |
// and everything inside is completely replaced on some page transitions | |
// but there's not a full "navigation" so the userscript isn't run again. | |
const observeTarget = document.querySelector('html'); | |
updateOnMutate(observeTarget, debouncedColorForPage, (target, addedNodes, removedNodes) => { | |
return ( | |
target.tagName === 'HEAD' || | |
target.tagName === 'HTML' || | |
target.tagName === 'TOOL-TIP' || | |
target.tagName === 'PROFILE-TIMEZONE' || | |
target.tagName === 'TEXT-EXPANDER' || | |
target.tagName === 'SLASH-COMMAND-EXPANDER' || | |
target.classList.contains('sr-only') || | |
target.classList.contains('Popover-message') || | |
target.classList.contains('QueryBuilder-Sizer') || | |
!!target.closest( | |
'.AppHeader, #partial-discussion-sidebar, #partial-new-comment-form-actions' | |
) || | |
[...addedNodes].some((elem) => elem.className === 'color-gh-repo') | |
); | |
}); | |
window.addEventListener('pushstate', (event) => { | |
log('%cpushstate', 'font-weight: bold', event); | |
debouncedColorForPage(); | |
}); | |
window.addEventListener('popstate', () => { | |
log('%cpopstate', 'font-weight: bold', event); | |
debouncedColorForPage(); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment