GitHub Code Language Icons

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

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

  1. // ==UserScript==
  2. // @name GitHub Code Language Icons
  3. // @description Replaces GitHub's code language icons with Material Design Icons.
  4. // @icon https://github.githubassets.com/favicons/favicon-dark.svg
  5. // @version 1.0.4
  6. // @author afkarxyz
  7. // @namespace https://github.com/afkarxyz/misc-scripts/
  8. // @supportURL https://github.com/afkarxyz/misc-scripts/issues
  9. // @license MIT
  10. // @match https://github.com/*
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const ICON_BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons/';
  19.  
  20. function normalizeLanguageName(language) {
  21. const languageMappings = {
  22. 'csharp': ['c#'],
  23. 'cpp': ['c++'],
  24. 'sass': ['scss'],
  25. 'bazel': ['starlark'],
  26. 'docker': ['dockerfile'],
  27. 'jupyter': ['jupyter notebook'],
  28. 'console': ['batchfile', 'shell']
  29. };
  30.  
  31. const normalizedLanguage = language.toLowerCase();
  32.  
  33. for (const [iconName, languageList] of Object.entries(languageMappings)) {
  34. if (languageList.includes(normalizedLanguage)) {
  35. return iconName;
  36. }
  37. }
  38.  
  39. return normalizedLanguage;
  40. }
  41.  
  42. async function fetchAvailableIcons() {
  43. const cacheKey = 'githubLanguageIconsCache';
  44. const currentTime = Date.now();
  45.  
  46. const cachedData = JSON.parse(GM_getValue(cacheKey, '{}'));
  47.  
  48. if (cachedData.timestamp && (currentTime - cachedData.timestamp < 7 * 24 * 60 * 60 * 1000)) {
  49. return cachedData.fileTypes;
  50. }
  51.  
  52. try {
  53. const response = await fetch('https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons.json');
  54. const data = await response.json();
  55.  
  56. GM_setValue(cacheKey, JSON.stringify({
  57. fileTypes: data.fileTypes,
  58. timestamp: currentTime
  59. }));
  60.  
  61. return data.fileTypes;
  62. } catch (error) {
  63. console.error('Failed to fetch icon list:', error);
  64.  
  65. return cachedData.fileTypes || [];
  66. }
  67. }
  68.  
  69. async function replaceLanguageIcons() {
  70. let availableIcons;
  71. try {
  72. availableIcons = await fetchAvailableIcons();
  73. } catch (error) {
  74. console.error('Error getting available icons:', error);
  75. return;
  76. }
  77.  
  78. const processedElements = new Set();
  79.  
  80. function processElement(element) {
  81. if (processedElements.has(element)) return;
  82. processedElements.add(element);
  83.  
  84. let langElement, language;
  85.  
  86. if (element.matches('.d-inline')) {
  87. langElement = element.querySelector('.text-bold');
  88.  
  89. if (!langElement || langElement.textContent.toLowerCase() === 'other' || element.dataset.iconChecked) return;
  90.  
  91. language = normalizeLanguageName(langElement.textContent);
  92. element.dataset.iconChecked = 'true';
  93.  
  94. const svg = element.querySelector('svg');
  95.  
  96. if (!svg || !availableIcons.includes(language)) return;
  97.  
  98. const img = document.createElement('img');
  99. img.src = `${ICON_BASE_URL}${language}.svg`;
  100. img.width = 16;
  101. img.height = 16;
  102. img.className = 'mr-2';
  103. img.style.verticalAlign = 'middle';
  104.  
  105. svg.parentNode.replaceChild(img, svg);
  106. }
  107. else if (element.matches('.f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"]')) {
  108. language = normalizeLanguageName(element.textContent);
  109.  
  110. if (!availableIcons.includes(language)) return;
  111.  
  112. const parentSpan = element.parentElement;
  113. const colorSpan = parentSpan.querySelector('.repo-language-color');
  114.  
  115. const img = document.createElement('img');
  116. img.src = `${ICON_BASE_URL}${language}.svg`;
  117. img.width = 16;
  118. img.height = 16;
  119. img.style.marginRight = '2px';
  120. img.style.verticalAlign = 'sub';
  121.  
  122. if (colorSpan) {
  123. colorSpan.parentNode.insertBefore(img, colorSpan);
  124. colorSpan.remove();
  125. }
  126. }
  127. else if (element.matches('.mb-3 .no-wrap span[itemprop="programmingLanguage"]')) {
  128. language = normalizeLanguageName(element.textContent);
  129.  
  130. if (!availableIcons.includes(language)) return;
  131.  
  132. const parentSpan = element.parentElement;
  133. const colorSpan = parentSpan.querySelector('.repo-language-color');
  134.  
  135. const img = document.createElement('img');
  136. img.src = `${ICON_BASE_URL}${language}.svg`;
  137. img.width = 16;
  138. img.height = 16;
  139. img.style.marginRight = '4px';
  140.  
  141. const flexContainer = document.createElement('span');
  142. flexContainer.style.display = 'inline-flex';
  143. flexContainer.style.alignItems = 'center';
  144.  
  145. if (colorSpan) {
  146. colorSpan.remove();
  147.  
  148. flexContainer.appendChild(img);
  149. flexContainer.appendChild(element.cloneNode(true));
  150.  
  151. parentSpan.replaceWith(flexContainer);
  152. }
  153. }
  154. else if (element.matches('.Box-sc-g0xbh4-0.fCvgBf')) {
  155. const preLanguageElement = element.querySelector('.Box-sc-g0xbh4-0:not(.fVplbS)');
  156. if (preLanguageElement) {
  157. preLanguageElement.style.display = 'none';
  158. }
  159.  
  160. const languageSpan = element.querySelector('.Box-sc-g0xbh4-0.fVplbS');
  161. if (languageSpan && !languageSpan.dataset.iconProcessed) {
  162. language = normalizeLanguageName(languageSpan.textContent.trim());
  163.  
  164. if (availableIcons.includes(language)) {
  165. const iconImg = document.createElement('img');
  166. iconImg.src = `${ICON_BASE_URL}${language}.svg`;
  167. iconImg.alt = `${language} icon`;
  168. iconImg.width = 16;
  169. iconImg.height = 16;
  170. iconImg.style.marginRight = '2px';
  171. iconImg.style.verticalAlign = 'middle';
  172.  
  173. languageSpan.insertAdjacentElement('beforebegin', iconImg);
  174. languageSpan.dataset.iconProcessed = 'true';
  175. }
  176. }
  177. }
  178. }
  179.  
  180. const observer = new MutationObserver((mutations) => {
  181. for (const mutation of mutations) {
  182. for (const node of mutation.addedNodes) {
  183. if (node.nodeType === Node.ELEMENT_NODE) {
  184. if (node.matches('.d-inline, .f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"], .mb-3 .no-wrap span[itemprop="programmingLanguage"], .Box-sc-g0xbh4-0.fCvgBf')) {
  185. processElement(node);
  186. } else {
  187. node.querySelectorAll('.d-inline, .f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"], .mb-3 .no-wrap span[itemprop="programmingLanguage"], .Box-sc-g0xbh4-0.fCvgBf').forEach(processElement);
  188. }
  189. }
  190. }
  191. }
  192. });
  193.  
  194. observer.observe(document.body, {
  195. childList: true,
  196. subtree: true
  197. });
  198.  
  199. document.querySelectorAll('.d-inline, .f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"], .mb-3 .no-wrap span[itemprop="programmingLanguage"], .Box-sc-g0xbh4-0.fCvgBf').forEach(processElement);
  200. }
  201.  
  202. function init() {
  203. replaceLanguageIcons();
  204. }
  205.  
  206. if (document.readyState === 'loading') {
  207. document.addEventListener('DOMContentLoaded', init);
  208. } else {
  209. init();
  210. }
  211. })();

QingJ © 2025

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