GitHub Code Language Icons

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

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

  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.1
  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 BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/';
  19. const ICON_BASE_URL = `${BASE_URL}icons/`;
  20.  
  21. async function fetchLanguageMappings() {
  22. const cacheKey = 'githubLanguageRemapCache';
  23. const currentTime = Date.now();
  24. const cachedData = JSON.parse(GM_getValue(cacheKey, '{}'));
  25. if (cachedData.timestamp && (currentTime - cachedData.timestamp < 7 * 24 * 60 * 60 * 1000)) {
  26. return cachedData.mappings;
  27. }
  28. try {
  29. const response = await fetch(`${BASE_URL}remap.json`);
  30. const data = await response.json();
  31. GM_setValue(cacheKey, JSON.stringify({
  32. mappings: data.iconRemap,
  33. timestamp: currentTime
  34. }));
  35. return data.iconRemap;
  36. } catch (error) {
  37. console.error('Failed to fetch language mappings:', error);
  38. return cachedData.mappings || {};
  39. }
  40. }
  41.  
  42. let languageMappings = {};
  43.  
  44. function normalizeLanguageName(language) {
  45. const normalizedLanguage = language.toLowerCase();
  46. for (const [iconName, languageList] of Object.entries(languageMappings)) {
  47. if (languageList.includes(normalizedLanguage)) {
  48. return iconName;
  49. }
  50. }
  51. return normalizedLanguage;
  52. }
  53.  
  54. async function fetchAvailableIcons() {
  55. const cacheKey = 'githubLanguageIconsCache';
  56. const currentTime = Date.now();
  57.  
  58. const cachedData = JSON.parse(GM_getValue(cacheKey, '{}'));
  59.  
  60. if (cachedData.timestamp && (currentTime - cachedData.timestamp < 7 * 24 * 60 * 60 * 1000)) {
  61. return cachedData.fileTypes;
  62. }
  63.  
  64. try {
  65. const response = await fetch(`${BASE_URL}icons.json`);
  66. const data = await response.json();
  67.  
  68. GM_setValue(cacheKey, JSON.stringify({
  69. fileTypes: data.fileTypes,
  70. timestamp: currentTime
  71. }));
  72.  
  73. return data.fileTypes;
  74. } catch (error) {
  75. console.error('Failed to fetch icon list:', error);
  76. return cachedData.fileTypes || [];
  77. }
  78. }
  79.  
  80. async function replaceLanguageIcons() {
  81. let availableIcons;
  82. try {
  83. availableIcons = await fetchAvailableIcons();
  84. } catch (error) {
  85. console.error('Error getting available icons:', error);
  86. return;
  87. }
  88. const processedElements = new Set();
  89.  
  90. function processElement(element) {
  91. if (processedElements.has(element)) return;
  92. processedElements.add(element);
  93.  
  94. let langElement, language;
  95. if (element.matches('.d-inline')) {
  96. langElement = element.querySelector('.text-bold');
  97. if (!langElement || langElement.textContent.toLowerCase() === 'other' || element.dataset.iconChecked) return;
  98. language = normalizeLanguageName(langElement.textContent);
  99. element.dataset.iconChecked = 'true';
  100. const svg = element.querySelector('svg');
  101.  
  102. if (!svg || !availableIcons.includes(language)) return;
  103. const img = document.createElement('img');
  104. img.src = `${ICON_BASE_URL}${language}.svg`;
  105. img.width = 16;
  106. img.height = 16;
  107. img.className = 'mr-2';
  108. img.style.verticalAlign = 'middle';
  109. svg.parentNode.replaceChild(img, svg);
  110. }
  111. else if (element.matches('.f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"]')) {
  112. language = normalizeLanguageName(element.textContent);
  113. if (!availableIcons.includes(language)) return;
  114. const parentSpan = element.parentElement;
  115. const colorSpan = parentSpan.querySelector('.repo-language-color');
  116. const img = document.createElement('img');
  117. img.src = `${ICON_BASE_URL}${language}.svg`;
  118. img.width = 16;
  119. img.height = 16;
  120. img.style.marginRight = '2px';
  121. img.style.verticalAlign = 'sub';
  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. if (!availableIcons.includes(language)) return;
  130. const parentSpan = element.parentElement;
  131. const colorSpan = parentSpan.querySelector('.repo-language-color');
  132. const img = document.createElement('img');
  133. img.src = `${ICON_BASE_URL}${language}.svg`;
  134. img.width = 16;
  135. img.height = 16;
  136. img.style.marginRight = '4px';
  137. const flexContainer = document.createElement('span');
  138. flexContainer.style.display = 'inline-flex';
  139. flexContainer.style.alignItems = 'center';
  140. if (colorSpan) {
  141. colorSpan.remove();
  142. flexContainer.appendChild(img);
  143. flexContainer.appendChild(element.cloneNode(true));
  144. parentSpan.replaceWith(flexContainer);
  145. }
  146. }
  147. else if (element.matches('.Box-sc-g0xbh4-0.fCvgBf')) {
  148. const preLanguageElement = element.querySelector('.Box-sc-g0xbh4-0:not(.fVplbS)');
  149. if (preLanguageElement) {
  150. preLanguageElement.style.display = 'none';
  151. }
  152.  
  153. const languageSpan = element.querySelector('.Box-sc-g0xbh4-0.fVplbS');
  154. if (languageSpan && !languageSpan.dataset.iconProcessed) {
  155. language = normalizeLanguageName(languageSpan.textContent.trim());
  156. if (availableIcons.includes(language)) {
  157. const iconImg = document.createElement('img');
  158. iconImg.src = `${ICON_BASE_URL}${language}.svg`;
  159. iconImg.alt = `${language} icon`;
  160. iconImg.width = 16;
  161. iconImg.height = 16;
  162. iconImg.style.marginRight = '2px';
  163. iconImg.style.verticalAlign = 'middle';
  164.  
  165. languageSpan.insertAdjacentElement('beforebegin', iconImg);
  166. languageSpan.dataset.iconProcessed = 'true';
  167. }
  168. }
  169. }
  170. else if (element.matches('.repo-language-color')) {
  171. const languageSpan = element.parentElement.querySelector('[itemprop="programmingLanguage"]');
  172. if (languageSpan && !languageSpan.dataset.iconProcessed) {
  173. language = normalizeLanguageName(languageSpan.textContent);
  174. if (availableIcons.includes(language)) {
  175. const iconImg = document.createElement('img');
  176. iconImg.src = `${ICON_BASE_URL}${language}.svg`;
  177. iconImg.alt = `${language} icon`;
  178. iconImg.width = 16;
  179. iconImg.height = 16;
  180. iconImg.style.marginRight = '2px';
  181. iconImg.style.verticalAlign = 'sub';
  182.  
  183. element.parentElement.insertBefore(iconImg, element);
  184. element.remove();
  185. languageSpan.dataset.iconProcessed = 'true';
  186. }
  187. }
  188. }
  189. else if (element.matches('.Box-sc-g0xbh4-0.hjDqIa')) {
  190. const languageSpan = element.nextElementSibling;
  191. if (languageSpan && languageSpan.getAttribute('aria-label') && !languageSpan.dataset.iconProcessed) {
  192. language = normalizeLanguageName(languageSpan.getAttribute('aria-label').replace(' language', ''));
  193. if (availableIcons.includes(language)) {
  194. const iconImg = document.createElement('img');
  195. iconImg.src = `${ICON_BASE_URL}${language}.svg`;
  196. iconImg.alt = `${language} icon`;
  197. iconImg.width = 16;
  198. iconImg.height = 16;
  199. iconImg.style.marginRight = '4px';
  200. iconImg.style.verticalAlign = 'middle';
  201.  
  202. element.style.display = 'none';
  203.  
  204. languageSpan.parentNode.insertBefore(iconImg, languageSpan);
  205. languageSpan.dataset.iconProcessed = 'true';
  206. }
  207. }
  208. }
  209. }
  210.  
  211. const observer = new MutationObserver((mutations) => {
  212. for (const mutation of mutations) {
  213. for (const node of mutation.addedNodes) {
  214. if (node.nodeType === Node.ELEMENT_NODE) {
  215. 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, .repo-language-color, .Box-sc-g0xbh4-0.hjDqIa')) {
  216. processElement(node);
  217. } else {
  218. 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, .repo-language-color, .Box-sc-g0xbh4-0.hjDqIa').forEach(processElement);
  219. }
  220. }
  221. }
  222. }
  223. });
  224.  
  225. observer.observe(document.body, {
  226. childList: true,
  227. subtree: true
  228. });
  229.  
  230. 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, .repo-language-color, .Box-sc-g0xbh4-0.hjDqIa').forEach(processElement);
  231. }
  232.  
  233. function hideAndReplaceDivs() {
  234. document.querySelectorAll('span.Box-sc-g0xbh4-0.eXDtUe').forEach(spanElement => {
  235. if (!spanElement) return;
  236.  
  237. const divToHide = spanElement.querySelector('div[class^="Box-sc-g0xbh4-0"]');
  238. const titleElement = spanElement.closest('a')?.querySelector('div[title]');
  239. const title = titleElement?.getAttribute('title');
  240. if (divToHide && title && !divToHide.dataset.processed) {
  241. const normalizedTitle = normalizeLanguageName(title);
  242. divToHide.style.display = 'none';
  243. divToHide.dataset.processed = 'true';
  244. const svgElement = document.createElement('img');
  245. svgElement.src = `${ICON_BASE_URL}${normalizedTitle}.svg`;
  246. svgElement.alt = title;
  247. svgElement.width = svgElement.height = 16;
  248. divToHide.parentNode.insertBefore(svgElement, divToHide);
  249. }
  250. });
  251. }
  252.  
  253. function observeDOMChanges() {
  254. const observer = new MutationObserver((mutations) => {
  255. mutations.forEach((mutation) => {
  256. if (mutation.type === 'childList') {
  257. hideAndReplaceDivs();
  258. }
  259. });
  260. });
  261.  
  262. const config = { childList: true, subtree: true };
  263. observer.observe(document.body, config);
  264.  
  265. hideAndReplaceDivs();
  266. }
  267.  
  268. async function init() {
  269. languageMappings = await fetchLanguageMappings();
  270. replaceLanguageIcons();
  271. observeDOMChanges();
  272. }
  273.  
  274. if (document.readyState === 'loading') {
  275. document.addEventListener('DOMContentLoaded', init);
  276. } else {
  277. init();
  278. }
  279. })();

QingJ © 2025

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