Replaces GitHub's code language icons with Material Design Icons.
当前为
// ==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
// @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#'],
'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();
}
})();