// ==UserScript== // @name GitHub - Latest // @version 2.0.1 // @description Always keep an eye on the latest activity of your favorite projects // @author Journey Over // @license MIT // @match *://github.com/* // @require https://cdn.jsdelivr.net/gh/StylusThemes/Userscripts@0171b6b6f24caea737beafbc2a8dacd220b729d8/libs/utils/utils.min.js // @grant none // @run-at document-end // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com // @homepageURL https://github.com/StylusThemes/Userscripts // @downloadURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/github-latest.user.js // @updateURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/github-latest.user.js // ==/UserScript== (async function() { 'use strict'; const logger = Logger('GH - Latest', { debug: false }); const BUTTON_ID = 'latest-issues-button'; const QUERY_STRING = 'q=sort%3Aupdated-desc'; const NAVIGATION_SELECTOR = 'nav[aria-label="Repository"][data-variant="inset"] > ul[role="list"]'; const findTemplateTab = (navigationBody) => { for (const selector of ['a[data-tab-item="issues"]', 'a[data-tab-item="pulls"]', 'a[href*="/issues"], a[href*="/pulls"]']) { const anchor = navigationBody.querySelector(selector); if (anchor) return anchor.closest('li') || anchor; } return null; }; const createLatestIssuesTab = (templateTab) => { const clonedTab = templateTab.cloneNode(true); const anchorElement = clonedTab.querySelector('a') || clonedTab; if (!anchorElement) return clonedTab; anchorElement.id = BUTTON_ID; try { const urlObject = new URL(anchorElement.href, location.origin); urlObject.search = QUERY_STRING; anchorElement.href = urlObject.href; } catch { const base = (anchorElement.href || '#').split('?')[0]; anchorElement.href = base + '?' + QUERY_STRING; } anchorElement.removeAttribute('aria-current'); anchorElement.removeAttribute('data-discover'); anchorElement.removeAttribute('data-tab-item'); anchorElement.removeAttribute('data-react-nav'); const svgElement = clonedTab.querySelector('svg'); if (svgElement) { svgElement.setAttribute('viewBox', '0 0 16 16'); svgElement.innerHTML = ``; } const textSpan = clonedTab.querySelector('[data-component="text"]'); if (textSpan) { textSpan.textContent = 'Latest issues'; if (textSpan.hasAttribute('data-content')) textSpan.setAttribute('data-content', 'Latest issues'); } const counterElement = clonedTab.querySelector('[data-component="counter"]'); if (counterElement) counterElement.remove(); return clonedTab; }; const addLatestIssuesButton = () => { // Skip non-repo pages (e.g. /settings, /notifications, /dashboard) if (!/^\/[^\/]+\/[^\/]/.test(location.pathname)) return; // Wait for the global nav bar to finish loading before making DOM changes if (!document.querySelector('[partial-name="global-nav-bar"].loaded')) return; const existingButton = document.getElementById(BUTTON_ID); if (existingButton && existingButton.isConnected) return; const navigationBody = document.querySelector(NAVIGATION_SELECTOR); if (!navigationBody) { logger.debug('Navigation selector not found'); return; } const templateTab = findTemplateTab(navigationBody); if (!templateTab) { logger.debug('Template tab not found'); return; } logger.debug('Adding latest issues button'); const newTab = createLatestIssuesTab(templateTab); navigationBody.appendChild(newTab); if (location.pathname.includes('/issues') && location.search === '?' + QUERY_STRING) { const button = document.getElementById(BUTTON_ID); if (button) button.setAttribute('aria-current', 'page'); } }; const initialize = () => { logger('Initializing GitHub Latest Issues script'); addLatestIssuesButton(); // rAF polling: runs every frame (~16ms), pauses when tab is backgrounded. // Catches ALL removal scenarios (React re-render, CSS hide, tree replace). // addLatestIssuesButton has isConnected guard — repeated calls are cheap. const poll = () => { addLatestIssuesButton(); requestAnimationFrame(poll); }; requestAnimationFrame(poll); // Fallback for legacy GitHub pages that still use Turbo/PJAX (Gists, Wiki, etc.) document.addEventListener('turbo:render', addLatestIssuesButton); document.addEventListener('turbo:load', addLatestIssuesButton); document.addEventListener('pjax:end', addLatestIssuesButton); document.addEventListener('github-pjax', addLatestIssuesButton); }; initialize(); })();