// ==UserScript== // @name Github Repo Network Tab // @namespace https://github.com/StaticPH // @match https://github.com/*/* // @exclude-match https://github.com/*/*/search* // @exclude-match https://github.com/about* // @exclude-match https://github.com/contact* // @exclude-match https://github.com/customer-stories* // @exclude-match https://github.com/enterprise* // @exclude-match https://github.com/explore* // @exclude-match https://github.com/features* // @exclude-match https://github.com/github-copilot/* // @exclude-match https://github.com/login/* // @exclude-match https://github.com/marketplace* // @exclude-match https://github.com/new* // @exclude-match https://github.com/notifications* // @exclude-match https://github.com/organizations/* // @exclude-match https://github.com/orgs/* // @exclude-match https://github.com/pricing* // @exclude-match https://github.com/search* // @exclude-match https://github.com/security* // @exclude-match https://github.com/sessions/* // @exclude-match https://github.com/settings/* // @exclude-match https://github.com/site* // @exclude-match https://github.com/team* // @exclude-match https://github.com/topics* // @exclude-match https://github.com/trending* // @exclude-match https://github.com/users/*/projects/* // @version 1.8.0 // @createdAt 4/06/2020 // @author StaticPH // @description Adds a navigation tab for faster access to the 'Network' page of a repository. // @license MIT // @updateURL https://raw.githubusercontent.com/StaticPH/Userscripts/master/github_repo_network_tab.user.js // @downloadURL https://raw.githubusercontent.com/StaticPH/Userscripts/master/github_repo_network_tab.user.js // @homepageURL https://github.com/StaticPH/UserScripts // @supportURL https://github.com/StaticPH/UserScripts/issues // @icon https://github.githubassets.com/pinned-octocat.svg // @grant none // @run-at document-idle // @noframes // ==/UserScript== (function(){ 'use strict'; /* Determine what repository we are looking at */ let here = (function getRepoAddress(){ return location.pathname.split('/', 3).slice(1).join('/'); })(); const networkIconSvgHTML = ''; /* Honestly, I feel like creating the HTML directly is less of a hassle than creating all the elements with JavaScript */ function createBigNetworkTabHTML(){ // Exclude analytical "data-ga-click" and "data-selected-links" attributes return '\n' + '\t' + networkIconSvgHTML + '\n' + ' \n' + ' Network\n' + ' \n' + '\n'; //TODO: Intelligently determine if the link element should have 'style="visibility:hidden;"' to start with? } /* Used when the window size is small enough that the repository header gets combined with the site header */ function createSmallNetworkTabHTML(){ // This function may not be useful following one of the github ui refreshes since it was first implemented; Leaving it here for now. // Exclude analytical "data-ga-click" and "data-selected-links" attributes return '\n' + ' Network\n' + ''; } //TODO: Intelligently determine if the list element should be hidden when first added function createNetworkTabInDropdownHTML(){ // Exclude analytical "data-ga-click" and "data-selected-links" attributes return '
  • \n' + ' \n' + ' Network\n' + ' \n' + '
  • '; } function maybeFixHighlightedTab(){ if (location.pathname.endsWith(here + '/network') || location.pathname.endsWith(here + '/network/')){ let networkTab = document.querySelector('[data-tab-item="i2_1network-tab"]'); let insightsTab = document.querySelector('[data-tab-item="i7insights-tab"]'); if (insightsTab /*&& insightsTab.hasAttribute('aria-current')*/){ insightsTab.removeAttribute('aria-current'); insightsTab.classList.remove('selected'); } if (networkTab){ networkTab.setAttribute('aria-current', 'page'); networkTab.classList.add('selected'); } } } function doesNeedModernTabVariant(){ // Thanks for making it more annoying to find or work with meaningful elements using CSS selectors, GitHub. /s return document.querySelector('nav[class*="prc-components-UnderlineWrapper"]') !== null; } function modernUIAddNetworkTab(){ const prTabLink = document.querySelector(`li.prc-UnderlineNav-UnderlineNavItem-syRjR > a[href="/${here}/pulls"]`); if (!prTabLink){ return false; } // Not ready yet... or GitHub changed shit again. const dataAttrs = 'data-turbo-frame="repo-content-turbo-frame" data-discover="true"'; const isActiveTab = (location.pathname.endsWith('/network') || location.pathname.endsWith('/network/')); const tabHTML = '
  • \n' + `\n` + ` ${networkIconSvgHTML}\n` + // May want to skip adding actual text manually due to css attr magic setting value from data-content ' Network\n' + '\n' + '
  • '; prTabLink.parentElement.insertAdjacentHTML('afterend', tabHTML); const networkTab = document.querySelector('#bigNetworkTab'); if (isActiveTab){ const falseActiveTab = document.querySelector('li.prc-UnderlineNav-UnderlineNavItem-syRjR > a[aria-current]'); if (falseActiveTab){ falseActiveTab.removeAttribute('aria-current'); } networkTab.setAttribute('aria-current', 'page'); } return networkTab; } //TODO: Consider insertion at Nth element position, rather than relative to PR tab. setTimeout(function wait(){ /* Find the 'Pull Requests' tab; inserting the new Network tab immediately after it ensures consistent placement. */ const repoPullsTab = document.querySelectorAll('[data-selected-links*="repo_pulls"]'); const dropdownRetryLimit = 5; let dropdownRetries = 0; // Wait until the page loads in enough to have the 'Pull Requests' tab in the repository header, so that it can be used as a point of reference for element insertion if (repoPullsTab.length !== 0){ repoPullsTab[0].insertAdjacentHTML('afterend', createBigNetworkTabHTML()); /* oxlint-disable no-unused-expressions */ document.querySelector('#bigNetworkTab') && console.debug('Added big Network tab.'); if (repoPullsTab.length > 1){ repoPullsTab[1].insertAdjacentHTML('afterend', createSmallNetworkTabHTML()); /* oxlint-disable no-unused-expressions */ document.querySelector('#smallNetworkTab') && console.debug('Added small Network tab.'); } // setTimeout(function foo(){ // if (document.querySelector('[data-selected-links*="repo_network"]')){ // console.log("Yup, still there"); // } // // I don't know what causes the element to get deleted sometimes, and I don't know why the developer console doesnt update when // // navigating to another (or the current) tab under whatever condition causes the former (but fixes itself if the page is refreshed). // // This is immensely bothersome, but I have no idea how to fix it short of just checking every few seconds and adding it if not found. // }, 400); // TODO: MutationObservers don't exactly lend themselves to watching for the creation of a specific element that doesn't already exist at some point in time. Find an alternative. setTimeout(function waitmore(){ const pullsDropdownItem = document.querySelector('details-menu li[data-menu-item="i2pull-requests-tab"]'); if (pullsDropdownItem){ pullsDropdownItem.insertAdjacentHTML('afterend', createNetworkTabInDropdownHTML()); /* oxlint-disable no-unused-expressions */ document.querySelector('#networkTabDropdown') && console.debug('Added Network tab item to dropdown.'); } else if (dropdownRetries >= dropdownRetryLimit){ console.log(`Number of attempts at adding Network tab to dropdown have exceeded the limit of ${dropdownRetryLimit} attempts. Giving up.`); } else { console.log(`Waiting ${(dropdownRetries * 500) + 500}ms for page to load further before attempting insertion of dropdown-item.`); dropdownRetries++; setTimeout(waitmore, (dropdownRetries * 500) + 500); } }); maybeFixHighlightedTab(); } else if (doesNeedModernTabVariant()){ if (modernUIAddNetworkTab()){ console.log('Added Network tab to modern nagivation menu.'); (async function(){ return await setInterval(function babysit(){ // Compensate for React's DOM fuckery often regenerating the navigation tabs (and who knows what else) shortly after document-idle; // check back every 5 seconds. if (!document.querySelector('#bigNetworkTab')){ console.log('Compensating for React DOM fuckery regenerating the navigation tabs. Re-adding Network tab'); if (modernUIAddNetworkTab()){ console.log('Network tab re-added to modern navigation menu.'); }; } }, 5000); })(); } else { console.log('Modern UI required; waiting 300ms for page to load further.'); return setTimeout(wait, 300); } } else { console.log('Waiting 300ms for page to load further.'); return setTimeout(wait, 300); } }); })();