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