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.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. const ICON_BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons/';
  18. function normalizeLanguageName(language) {
  19. const languageMappings = {
  20. 'csharp': ['c#'],
  21. 'cpp': ['c++'],
  22. 'docker': ['dockerfile'],
  23. 'console': ['batchfile', 'shell']
  24. };
  25.  
  26. const normalizedLanguage = language.toLowerCase();
  27.  
  28. for (const [iconName, languageList] of Object.entries(languageMappings)) {
  29. if (languageList.includes(normalizedLanguage)) {
  30. return iconName;
  31. }
  32. }
  33.  
  34. return normalizedLanguage;
  35. }
  36. async function fetchAvailableIcons() {
  37. const cacheKey = 'githubLanguageIconsCache';
  38. const currentTime = Date.now();
  39. const cachedData = JSON.parse(GM_getValue(cacheKey, '{}'));
  40. if (cachedData.timestamp && (currentTime - cachedData.timestamp < 7 * 24 * 60 * 60 * 1000)) {
  41. return cachedData.fileTypes;
  42. }
  43. try {
  44. const response = await fetch('https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/icons.json');
  45. const data = await response.json();
  46. GM_setValue(cacheKey, JSON.stringify({
  47. fileTypes: data.fileTypes,
  48. timestamp: currentTime
  49. }));
  50. return data.fileTypes;
  51. } catch (error) {
  52. console.error('Failed to fetch icon list:', error);
  53. return cachedData.fileTypes || [];
  54. }
  55. }
  56. async function replaceLanguageIcons() {
  57. let availableIcons;
  58. try {
  59. availableIcons = await fetchAvailableIcons();
  60. } catch (error) {
  61. console.error('Error getting available icons:', error);
  62. return;
  63. }
  64. const elementsToProcess = [
  65. ...document.querySelectorAll('.d-inline'),
  66. ...document.querySelectorAll('.f6.color-fg-muted .repo-language-color + span[itemprop="programmingLanguage"]'),
  67. ...document.querySelectorAll('.mb-3 .no-wrap span[itemprop="programmingLanguage"]')
  68. ];
  69. elementsToProcess.forEach(element => {
  70. let langElement, language;
  71. if (element.closest('.d-inline')) {
  72. const parentItem = element.closest('.d-inline');
  73. langElement = parentItem.querySelector('.text-bold');
  74. if (!langElement || langElement.textContent.toLowerCase() === 'other' || parentItem.dataset.iconChecked) return;
  75. language = normalizeLanguageName(langElement.textContent);
  76. parentItem.dataset.iconChecked = 'true';
  77. const svg = parentItem.querySelector('svg');
  78. if (!svg || !availableIcons.includes(language)) return;
  79. const img = document.createElement('img');
  80. img.src = `${ICON_BASE_URL}${language}.svg`;
  81. img.width = 16;
  82. img.height = 16;
  83. img.className = 'mr-2';
  84. img.style.verticalAlign = 'middle';
  85. svg.parentNode.replaceChild(img, svg);
  86. }
  87. else if (element.closest('.f6.color-fg-muted')) {
  88. language = normalizeLanguageName(element.textContent);
  89. if (!availableIcons.includes(language)) return;
  90. const parentSpan = element.parentElement;
  91. const colorSpan = parentSpan.querySelector('.repo-language-color');
  92. const img = document.createElement('img');
  93. img.src = `${ICON_BASE_URL}${language}.svg`;
  94. img.width = 16;
  95. img.height = 16;
  96. img.style.marginRight = '2px';
  97. img.style.verticalAlign = 'sub';
  98. if (colorSpan) {
  99. colorSpan.parentNode.insertBefore(img, colorSpan);
  100. colorSpan.remove();
  101. }
  102. }
  103. else if (element.closest('.mb-3 .no-wrap')) {
  104. language = normalizeLanguageName(element.textContent);
  105. if (!availableIcons.includes(language)) return;
  106. const parentSpan = element.parentElement;
  107. const colorSpan = parentSpan.querySelector('.repo-language-color');
  108. const img = document.createElement('img');
  109. img.src = `${ICON_BASE_URL}${language}.svg`;
  110. img.width = 16;
  111. img.height = 16;
  112. img.style.marginRight = '4px';
  113. const flexContainer = document.createElement('span');
  114. flexContainer.style.display = 'inline-flex';
  115. flexContainer.style.alignItems = 'center';
  116. if (colorSpan) {
  117. colorSpan.remove();
  118. flexContainer.appendChild(img);
  119. flexContainer.appendChild(element);
  120. parentSpan.parentNode.replaceChild(flexContainer, parentSpan);
  121. }
  122. }
  123. });
  124. }
  125. let iconListPromise = null;
  126. function init() {
  127. const observer = new MutationObserver(() => {
  128. iconListPromise = iconListPromise
  129. ? iconListPromise.then(() => replaceLanguageIcons())
  130. : replaceLanguageIcons();
  131. });
  132. observer.observe(document.body, {
  133. childList: true,
  134. subtree: true
  135. });
  136. iconListPromise = replaceLanguageIcons();
  137. }
  138. if (document.readyState === 'loading') {
  139. document.addEventListener('DOMContentLoaded', init);
  140. } else {
  141. init();
  142. }
  143. })();

QingJ © 2025

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