GitHub Enhancer (Ultra Edition)

GitHub UI enhancer w/ drag menu, fast downloads, dark mode override, and more. Author: Eliminater74 | MIT License

  1. // ==UserScript==
  2. // @name GitHub Enhancer (Ultra Edition)
  3. // @namespace https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts
  4. // @version 3.0
  5. // @description GitHub UI enhancer w/ drag menu, fast downloads, dark mode override, and more. Author: Eliminater74 | MIT License
  6. // @author Eliminater74
  7. // @license MIT
  8. // @match https://github.com/*
  9. // @icon https://github.githubassets.com/favicons/favicon.svg
  10. // @homepage https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts
  11. // @supportURL https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts/issues
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const defaultSettings = {
  19. highlightStalePRs: true,
  20. autoExpandDiffs: true,
  21. addFileSizeLabels: true,
  22. autoFocusSearch: true,
  23. showFastDownload: true,
  24. showFloatingMenu: true,
  25. themeMode: 'auto',
  26. pinnedRepos: []
  27. };
  28.  
  29. const settingsKey = 'githubEnhancerSettings';
  30. const settings = JSON.parse(localStorage.getItem(settingsKey)) || defaultSettings;
  31. const saveSettings = () => localStorage.setItem(settingsKey, JSON.stringify(settings));
  32. const getCurrentRepo = () => location.pathname.split('/').slice(1, 3).join('/');
  33.  
  34. const debounce = (fn, delay = 300) => {
  35. let timeout;
  36. return (...args) => {
  37. clearTimeout(timeout);
  38. timeout = setTimeout(() => fn.apply(this, args), delay);
  39. };
  40. };
  41.  
  42. const highlightStalePRs = () => {
  43. document.querySelectorAll('relative-time').forEach(el => {
  44. const daysOld = (Date.now() - new Date(el.dateTime)) / (1000 * 60 * 60 * 24);
  45. if (daysOld > 7) {
  46. el.style.color = 'red';
  47. el.style.fontWeight = 'bold';
  48. }
  49. });
  50. };
  51.  
  52. const expandDiffs = () => {
  53. document.querySelectorAll('button').forEach(btn => {
  54. const txt = btn.innerText.toLowerCase();
  55. if (txt.includes('load diff') || txt.includes('show')) btn.click();
  56. });
  57. };
  58.  
  59. const collapseDiffs = () => {
  60. document.querySelectorAll('button[aria-label="Collapse file"]').forEach(btn => btn.click());
  61. };
  62.  
  63. const showFileSizes = () => {
  64. document.querySelectorAll('a.js-navigation-open').forEach(el => {
  65. const row = el.closest('tr');
  66. const sizeEl = row?.querySelector('td:last-child');
  67. if (sizeEl && sizeEl.innerText.match(/[0-9.]+ (KB|MB)/) && !el.querySelector('.gh-size')) {
  68. const tag = document.createElement('span');
  69. tag.className = 'gh-size';
  70. tag.style.marginLeft = '6px';
  71. tag.style.fontSize = 'smaller';
  72. tag.style.color = '#888';
  73. tag.textContent = `(${sizeEl.innerText.trim()})`;
  74. el.appendChild(tag);
  75. }
  76. });
  77. };
  78.  
  79. const autoFocusSearch = () => {
  80. const input = document.querySelector('input.header-search-input');
  81. if (input) input.focus();
  82. };
  83.  
  84. const addFastDownloadButtons = () => {
  85. document.querySelectorAll('a.js-navigation-open').forEach(el => {
  86. const href = el.getAttribute('href');
  87. if (href?.includes('/blob/') && !el.parentNode.querySelector('.gh-download-link')) {
  88. const rawURL = 'https://raw.githubusercontent.com' + href.replace('/blob', '');
  89. const dl = document.createElement('a');
  90. dl.href = rawURL;
  91. dl.textContent = ' ⬇';
  92. dl.download = '';
  93. dl.className = 'gh-download-link';
  94. dl.style.marginLeft = '5px';
  95. el.parentNode.appendChild(dl);
  96. }
  97. });
  98. };
  99.  
  100. const applyDarkMode = () => {
  101. const theme = settings.themeMode;
  102. if (theme === 'auto') return;
  103. document.documentElement.setAttribute('data-color-mode', theme);
  104. document.documentElement.setAttribute('data-dark-theme', 'dark');
  105. document.documentElement.setAttribute('data-light-theme', 'light');
  106. };
  107.  
  108. const pinCurrentRepo = () => {
  109. const repo = getCurrentRepo();
  110. if (repo && !settings.pinnedRepos.includes(repo)) {
  111. settings.pinnedRepos.push(repo);
  112. saveSettings();
  113. updatePinnedList();
  114. }
  115. };
  116.  
  117. const updatePinnedList = () => {
  118. const list = document.getElementById('gh-pinned-list');
  119. if (!list) return;
  120. list.innerHTML = '';
  121. settings.pinnedRepos.forEach(repo => {
  122. const li = document.createElement('li');
  123. const a = document.createElement('a');
  124. a.href = `https://github.com/${repo}`;
  125. a.textContent = repo;
  126. a.target = '_blank';
  127. const del = document.createElement('button');
  128. del.textContent = '❌';
  129. del.style.marginLeft = '6px';
  130. del.onclick = () => {
  131. settings.pinnedRepos = settings.pinnedRepos.filter(r => r !== repo);
  132. saveSettings();
  133. updatePinnedList();
  134. };
  135. li.appendChild(a);
  136. li.appendChild(del);
  137. list.appendChild(li);
  138. });
  139. };
  140.  
  141. const createFloatingMenu = () => {
  142. const gear = document.createElement('div');
  143. gear.innerHTML = '⚙️';
  144. Object.assign(gear.style, {
  145. position: 'fixed', bottom: '20px', right: '20px',
  146. fontSize: '22px', zIndex: '9999', cursor: 'pointer', userSelect: 'none'
  147. });
  148.  
  149. const panel = document.createElement('div');
  150. panel.id = 'gh-enhancer-panel';
  151. panel.style.cssText = `
  152. position: fixed; bottom: 60px; right: 20px;
  153. z-index: 9999; background: #222; color: #fff;
  154. padding: 12px; border-radius: 10px; font-size: 14px;
  155. display: none; box-shadow: 0 0 12px rgba(0,0,0,0.6);
  156. transition: all 0.3s ease;
  157. `;
  158.  
  159. const checkbox = (label, key) => {
  160. const row = document.createElement('div');
  161. row.innerHTML = `<label><input type="checkbox" ${settings[key] ? 'checked' : ''}/> ${label}</label>`;
  162. row.querySelector('input').addEventListener('change', e => {
  163. settings[key] = e.target.checked;
  164. saveSettings();
  165. });
  166. return row;
  167. };
  168.  
  169. panel.appendChild(checkbox('Highlight stale PRs', 'highlightStalePRs'));
  170. panel.appendChild(checkbox('Auto-expand diffs', 'autoExpandDiffs'));
  171. panel.appendChild(checkbox('Show file sizes', 'addFileSizeLabels'));
  172. panel.appendChild(checkbox('Auto-focus search', 'autoFocusSearch'));
  173. panel.appendChild(checkbox('Fast downloads', 'showFastDownload'));
  174.  
  175. const themeSelect = document.createElement('select');
  176. ['auto', 'dark', 'light'].forEach(t => {
  177. const opt = document.createElement('option');
  178. opt.value = t;
  179. opt.textContent = t.charAt(0).toUpperCase() + t.slice(1);
  180. themeSelect.appendChild(opt);
  181. });
  182. themeSelect.value = settings.themeMode;
  183. themeSelect.onchange = () => {
  184. settings.themeMode = themeSelect.value;
  185. saveSettings();
  186. applyDarkMode();
  187. };
  188.  
  189. panel.appendChild(document.createElement('hr'));
  190. panel.appendChild(document.createTextNode('Theme:'));
  191. panel.appendChild(themeSelect);
  192. panel.appendChild(document.createElement('hr'));
  193.  
  194. const pinBtn = document.createElement('button');
  195. pinBtn.textContent = '📌 Pin This Repo';
  196. pinBtn.onclick = pinCurrentRepo;
  197. panel.appendChild(pinBtn);
  198.  
  199. const pinnedList = document.createElement('ul');
  200. pinnedList.id = 'gh-pinned-list';
  201. panel.appendChild(pinnedList);
  202.  
  203. document.body.appendChild(gear);
  204. document.body.appendChild(panel);
  205.  
  206. gear.onclick = () => panel.style.display = (panel.style.display === 'none') ? 'block' : 'none';
  207. updatePinnedList();
  208. };
  209.  
  210. const runEnhancements = () => {
  211. applyDarkMode();
  212. if (settings.highlightStalePRs) highlightStalePRs();
  213. if (settings.autoExpandDiffs) expandDiffs();
  214. if (settings.addFileSizeLabels) showFileSizes();
  215. if (settings.autoFocusSearch) autoFocusSearch();
  216. if (settings.showFastDownload) addFastDownloadButtons();
  217. };
  218.  
  219. const observer = new MutationObserver(debounce(runEnhancements));
  220. observer.observe(document.body, { childList: true, subtree: true });
  221.  
  222. runEnhancements();
  223. if (settings.showFloatingMenu) createFloatingMenu();
  224.  
  225. document.addEventListener('keydown', e => {
  226. if (e.altKey && e.key === 'g') document.getElementById('gh-enhancer-panel')?.click();
  227. if (e.altKey && e.key === 'e') expandDiffs();
  228. if (e.altKey && e.key === 'c') collapseDiffs();
  229. });
  230. })();