GitHub Enhancer (Ultra Edition)

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

// ==UserScript==
// @name         GitHub Enhancer (Ultra Edition)
// @namespace    https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts
// @version      3.0
// @description  GitHub UI enhancer w/ drag menu, fast downloads, dark mode override, and more. Author: Eliminater74 | MIT License
// @author       Eliminater74
// @license      MIT
// @match        https://github.com/*
// @icon         https://github.githubassets.com/favicons/favicon.svg
// @homepage     https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts
// @supportURL   https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts/issues
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const defaultSettings = {
    highlightStalePRs: true,
    autoExpandDiffs: true,
    addFileSizeLabels: true,
    autoFocusSearch: true,
    showFastDownload: true,
    showFloatingMenu: true,
    themeMode: 'auto',
    pinnedRepos: []
  };

  const settingsKey = 'githubEnhancerSettings';
  const settings = JSON.parse(localStorage.getItem(settingsKey)) || defaultSettings;
  const saveSettings = () => localStorage.setItem(settingsKey, JSON.stringify(settings));
  const getCurrentRepo = () => location.pathname.split('/').slice(1, 3).join('/');

  const debounce = (fn, delay = 300) => {
    let timeout;
    return (...args) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => fn.apply(this, args), delay);
    };
  };

  const highlightStalePRs = () => {
    document.querySelectorAll('relative-time').forEach(el => {
      const daysOld = (Date.now() - new Date(el.dateTime)) / (1000 * 60 * 60 * 24);
      if (daysOld > 7) {
        el.style.color = 'red';
        el.style.fontWeight = 'bold';
      }
    });
  };

  const expandDiffs = () => {
    document.querySelectorAll('button').forEach(btn => {
      const txt = btn.innerText.toLowerCase();
      if (txt.includes('load diff') || txt.includes('show')) btn.click();
    });
  };

  const collapseDiffs = () => {
    document.querySelectorAll('button[aria-label="Collapse file"]').forEach(btn => btn.click());
  };

  const showFileSizes = () => {
    document.querySelectorAll('a.js-navigation-open').forEach(el => {
      const row = el.closest('tr');
      const sizeEl = row?.querySelector('td:last-child');
      if (sizeEl && sizeEl.innerText.match(/[0-9.]+ (KB|MB)/) && !el.querySelector('.gh-size')) {
        const tag = document.createElement('span');
        tag.className = 'gh-size';
        tag.style.marginLeft = '6px';
        tag.style.fontSize = 'smaller';
        tag.style.color = '#888';
        tag.textContent = `(${sizeEl.innerText.trim()})`;
        el.appendChild(tag);
      }
    });
  };

  const autoFocusSearch = () => {
    const input = document.querySelector('input.header-search-input');
    if (input) input.focus();
  };

  const addFastDownloadButtons = () => {
    document.querySelectorAll('a.js-navigation-open').forEach(el => {
      const href = el.getAttribute('href');
      if (href?.includes('/blob/') && !el.parentNode.querySelector('.gh-download-link')) {
        const rawURL = 'https://raw.githubusercontent.com' + href.replace('/blob', '');
        const dl = document.createElement('a');
        dl.href = rawURL;
        dl.textContent = ' ⬇';
        dl.download = '';
        dl.className = 'gh-download-link';
        dl.style.marginLeft = '5px';
        el.parentNode.appendChild(dl);
      }
    });
  };

  const applyDarkMode = () => {
    const theme = settings.themeMode;
    if (theme === 'auto') return;
    document.documentElement.setAttribute('data-color-mode', theme);
    document.documentElement.setAttribute('data-dark-theme', 'dark');
    document.documentElement.setAttribute('data-light-theme', 'light');
  };

  const pinCurrentRepo = () => {
    const repo = getCurrentRepo();
    if (repo && !settings.pinnedRepos.includes(repo)) {
      settings.pinnedRepos.push(repo);
      saveSettings();
      updatePinnedList();
    }
  };

  const updatePinnedList = () => {
    const list = document.getElementById('gh-pinned-list');
    if (!list) return;
    list.innerHTML = '';
    settings.pinnedRepos.forEach(repo => {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = `https://github.com/${repo}`;
      a.textContent = repo;
      a.target = '_blank';
      const del = document.createElement('button');
      del.textContent = '❌';
      del.style.marginLeft = '6px';
      del.onclick = () => {
        settings.pinnedRepos = settings.pinnedRepos.filter(r => r !== repo);
        saveSettings();
        updatePinnedList();
      };
      li.appendChild(a);
      li.appendChild(del);
      list.appendChild(li);
    });
  };

  const createFloatingMenu = () => {
    const gear = document.createElement('div');
    gear.innerHTML = '⚙️';
    Object.assign(gear.style, {
      position: 'fixed', bottom: '20px', right: '20px',
      fontSize: '22px', zIndex: '9999', cursor: 'pointer', userSelect: 'none'
    });

    const panel = document.createElement('div');
    panel.id = 'gh-enhancer-panel';
    panel.style.cssText = `
      position: fixed; bottom: 60px; right: 20px;
      z-index: 9999; background: #222; color: #fff;
      padding: 12px; border-radius: 10px; font-size: 14px;
      display: none; box-shadow: 0 0 12px rgba(0,0,0,0.6);
      transition: all 0.3s ease;
    `;

    const checkbox = (label, key) => {
      const row = document.createElement('div');
      row.innerHTML = `<label><input type="checkbox" ${settings[key] ? 'checked' : ''}/> ${label}</label>`;
      row.querySelector('input').addEventListener('change', e => {
        settings[key] = e.target.checked;
        saveSettings();
      });
      return row;
    };

    panel.appendChild(checkbox('Highlight stale PRs', 'highlightStalePRs'));
    panel.appendChild(checkbox('Auto-expand diffs', 'autoExpandDiffs'));
    panel.appendChild(checkbox('Show file sizes', 'addFileSizeLabels'));
    panel.appendChild(checkbox('Auto-focus search', 'autoFocusSearch'));
    panel.appendChild(checkbox('Fast downloads', 'showFastDownload'));

    const themeSelect = document.createElement('select');
    ['auto', 'dark', 'light'].forEach(t => {
      const opt = document.createElement('option');
      opt.value = t;
      opt.textContent = t.charAt(0).toUpperCase() + t.slice(1);
      themeSelect.appendChild(opt);
    });
    themeSelect.value = settings.themeMode;
    themeSelect.onchange = () => {
      settings.themeMode = themeSelect.value;
      saveSettings();
      applyDarkMode();
    };

    panel.appendChild(document.createElement('hr'));
    panel.appendChild(document.createTextNode('Theme:'));
    panel.appendChild(themeSelect);
    panel.appendChild(document.createElement('hr'));

    const pinBtn = document.createElement('button');
    pinBtn.textContent = '📌 Pin This Repo';
    pinBtn.onclick = pinCurrentRepo;
    panel.appendChild(pinBtn);

    const pinnedList = document.createElement('ul');
    pinnedList.id = 'gh-pinned-list';
    panel.appendChild(pinnedList);

    document.body.appendChild(gear);
    document.body.appendChild(panel);

    gear.onclick = () => panel.style.display = (panel.style.display === 'none') ? 'block' : 'none';
    updatePinnedList();
  };

  const runEnhancements = () => {
    applyDarkMode();
    if (settings.highlightStalePRs) highlightStalePRs();
    if (settings.autoExpandDiffs) expandDiffs();
    if (settings.addFileSizeLabels) showFileSizes();
    if (settings.autoFocusSearch) autoFocusSearch();
    if (settings.showFastDownload) addFastDownloadButtons();
  };

  const observer = new MutationObserver(debounce(runEnhancements));
  observer.observe(document.body, { childList: true, subtree: true });

  runEnhancements();
  if (settings.showFloatingMenu) createFloatingMenu();

  document.addEventListener('keydown', e => {
    if (e.altKey && e.key === 'g') document.getElementById('gh-enhancer-panel')?.click();
    if (e.altKey && e.key === 'e') expandDiffs();
    if (e.altKey && e.key === 'c') collapseDiffs();
  });
})();