GitHub Code Language Icons

Replaces GitHub's code language icons with Material Design Icons.

目前为 2024-12-10 提交的版本。查看 最新版本

// ==UserScript==
// @name         GitHub Code Language Icons
// @description  Replaces GitHub's code language icons with Material Design Icons.
// @icon         https://github.githubassets.com/favicons/favicon-dark.svg
// @version      1.0.1
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/misc-scripts/
// @supportURL   https://github.com/afkarxyz/misc-scripts/issues
// @license      MIT
// @match        https://github.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';
  
    const ICON_BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons/';
  
    function normalizeLanguageName(language) {
        const languageMappings = {
            'csharp': ['c#'],
            'cpp': ['c++'],
            'docker': ['dockerfile'],
            'console': ['batchfile', 'shell']
        };

        const normalizedLanguage = language.toLowerCase();

        for (const [iconName, languageList] of Object.entries(languageMappings)) {
            if (languageList.includes(normalizedLanguage)) {
                return iconName;
            }
        }

        return normalizedLanguage;
    }
  
    async function fetchAvailableIcons() {
      const cacheKey = 'githubLanguageIconsCache';
      const currentTime = Date.now();
  
      const cachedData = JSON.parse(GM_getValue(cacheKey, '{}'));
  
      if (cachedData.timestamp && (currentTime - cachedData.timestamp < 7 * 24 * 60 * 60 * 1000)) {
        return cachedData.fileTypes;
      }
  
      try {
        const response = await fetch('https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons.json');
        const data = await response.json();
  
        GM_setValue(cacheKey, JSON.stringify({
          fileTypes: data.fileTypes,
          timestamp: currentTime
        }));
  
        return data.fileTypes;
      } catch (error) {
        console.error('Failed to fetch icon list:', error);
        
        return cachedData.fileTypes || [];
      }
    }
  
    async function replaceLanguageIcons() {
      let availableIcons;
      try {
        availableIcons = await fetchAvailableIcons();
      } catch (error) {
        console.error('Error getting available icons:', error);
        return;
      }
      
      const elementsToProcess = [
        ...document.querySelectorAll('.d-inline'),
        ...document.querySelectorAll('.f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"]'),
        ...document.querySelectorAll('.mb-3 .no-wrap span[itemprop="programmingLanguage"]')
      ];
  
      elementsToProcess.forEach(element => {
        let langElement, language;
        
        if (element.closest('.d-inline')) {
          const parentItem = element.closest('.d-inline');
          langElement = parentItem.querySelector('.text-bold');
          
          if (!langElement || langElement.textContent.toLowerCase() === 'other' || parentItem.dataset.iconChecked) return;
          
          language = normalizeLanguageName(langElement.textContent);
          parentItem.dataset.iconChecked = 'true';
          
          const svg = parentItem.querySelector('svg');
  
          if (!svg || !availableIcons.includes(language)) return;
          
          const img = document.createElement('img');
          img.src = `${ICON_BASE_URL}${language}.svg`;
          img.width = 16;
          img.height = 16;
          img.className = 'mr-2';
          img.style.verticalAlign = 'middle';
          
          svg.parentNode.replaceChild(img, svg);
        }
        else if (element.closest('.f6.color-fg-muted')) {
          language = normalizeLanguageName(element.textContent);
          
          if (!availableIcons.includes(language)) return;
          
          const parentSpan = element.parentElement;
          const colorSpan = parentSpan.querySelector('.repo-language-color');
          
          const img = document.createElement('img');
          img.src = `${ICON_BASE_URL}${language}.svg`;
          img.width = 16;
          img.height = 16;
          img.style.marginRight = '2px';
          img.style.verticalAlign = 'sub';
          
          if (colorSpan) {
            colorSpan.parentNode.insertBefore(img, colorSpan);
            colorSpan.remove();
          }
        }
        else if (element.closest('.mb-3 .no-wrap')) {
          language = normalizeLanguageName(element.textContent);
          
          if (!availableIcons.includes(language)) return;
          
          const parentSpan = element.parentElement;
          const colorSpan = parentSpan.querySelector('.repo-language-color');
          
          const img = document.createElement('img');
          img.src = `${ICON_BASE_URL}${language}.svg`;
          img.width = 16;
          img.height = 16;
          img.style.marginRight = '4px';
          
          const flexContainer = document.createElement('span');
          flexContainer.style.display = 'inline-flex';
          flexContainer.style.alignItems = 'center';
          
          if (colorSpan) {
            colorSpan.remove();
            
            flexContainer.appendChild(img);
            flexContainer.appendChild(element);
            
            parentSpan.parentNode.replaceChild(flexContainer, parentSpan);
          }
        }
      });
    }
  
    let iconListPromise = null;
  
    function init() {
      const observer = new MutationObserver(() => {
        iconListPromise = iconListPromise
          ? iconListPromise.then(() => replaceLanguageIcons())
          : replaceLanguageIcons();
      });
      
      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
      
      iconListPromise = replaceLanguageIcons();
    }
  
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', init);
    } else {
      init();
    }
  })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址