Linguist Expand

Expands Github's Linguist language list on repositories to show every language instead of hiding the small percentage under "Other"

  1. // ==UserScript==
  2. // @name Linguist Expand
  3. // @namespace https://davoleo.net
  4. // @author Davoleo
  5. // @homepage https://github.com/Davoleo/scripts/tree/master/linguist_expand
  6. // @description Expands Github's Linguist language list on repositories to show every language instead of hiding the small percentage under "Other"
  7. // @contributionURL https://davoleo.net/donate
  8. // @match https://github.com/*
  9. // @require https://unpkg.com/js-yaml@4.1.0/dist/js-yaml.min.js
  10. // @resource languageColors https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml
  11. // @grant GM_getResourceText
  12. // @grant GM_log
  13. // @grant GM_xmlhttpRequest
  14. // @connect api.github.com
  15. // @run-at document-idle
  16. // @version 1.0.2
  17. // @license MIT
  18. // @noframes
  19. // ==/UserScript==
  20.  
  21. //Loads languages.yml (from Github's linguist repo) the most updated, official and complete collection of github languages and their colors
  22. //loaded into a JS object via jsyaml (a library to parse yaml inside of javascript)
  23. const languages = jsyaml.load(GM_getResourceText('languageColors'));
  24.  
  25. //Contains information about languages and their percentages in the repository
  26. let langPercentagesMap = {}
  27. //Contains information about languages and their colors
  28. let langColorsMap = {}
  29.  
  30. /**
  31. * Function to standardize and modernize GM_xmlhttpRequest to work with promises
  32. * @param {String} url of the endpoint
  33. * @param {Object} options Contains extra information about the request
  34. * @returns a promise with the requested content
  35. */
  36. function request(url, options={}) {
  37. return new Promise((resolve, reject) => {
  38. GM_xmlhttpRequest({
  39. url,
  40. method: options.method || "GET",
  41. headers: options.headers || {Accept: "application/json",
  42. "Content-Type": "application/json"},
  43. responseType: options.responseType || "json",
  44. data: options.body || options.data,
  45. onload: res => resolve(res.response),
  46. onerror: reject
  47. })
  48. })
  49. }
  50.  
  51. /**
  52. * Retrieve information about the languages of a repository via the Github API
  53. * @param {String} user owner of the repository
  54. * @param {String} repo name
  55. * @returns the languages of the repository as a JS Object | null if the promise is rejected for any reason.
  56. */
  57. async function retrieveLanguages(user, repo) {
  58. try {
  59. return await request(`https://api.github.com/repos/${user}/${repo}/languages`, {
  60. headers: {
  61. Accept: "application/vnd.github.v3+json"
  62. }
  63. });
  64. }
  65. catch(e) {
  66. return null;
  67. }
  68. }
  69.  
  70. /**
  71. * Builds language bar segments assigning the correct colors and width depending on the language and it's frequency in the repository
  72. * @param {string} name of the language
  73. * @param {string} color of the language
  74. * @param {number} percentage of the language in the repository code
  75. * @returns a segment span of the language bar with the correct width and color
  76. */
  77. function buildBarSegmentSpan(name, color, percentage) {
  78. const segment = document.createElement('span');
  79. segment.style.setProperty('background-color', color, 'important');
  80. segment.style.width = percentage + '%';
  81. //Removes any margin which would make the language bar otherwise inaccurate
  82. segment.style.setProperty('margin', '0', 'important');
  83. //Make sure there's at least 1px of width in the bar segment (fixes width of 0.0% segments)
  84. //TODO: investigate a better way to do this
  85. segment.style.paddingLeft = '1px';
  86. segment.setAttribute("itemprop", "keywords");
  87. segment.setAttribute("aria-label", name + ' ' + percentage);
  88. segment.setAttribute("data-view-component", "true");
  89. segment.setAttribute("class", "Progress-item color-bg-success-inverse lingustexpand");
  90. return segment;
  91. }
  92.  
  93. /**
  94. * Builds a chip for each language containing
  95. * - The Color of the language in the bar
  96. * - The Name of the language
  97. * - The Percentage of the language in repository files
  98. * @param {String} owner of the repository
  99. * @param {String} repo name
  100. * @param {String} name of the language
  101. * @param {String} color of the language
  102. * @param {number} percentage percentage of the language in the repository code
  103. * @returns A chip components featured as legend for the language bar
  104. */
  105. function buildLanguageChip(owner, repo, name, color, percentage) {
  106. const chip = document.createElement('li');
  107. chip.classList.add('d-inline');
  108.  
  109. const chipLink = document.createElement('a');
  110. chipLink.classList.add('d-inline-flex', 'flex-items-center', 'flex-nowrap', 'Link--secondary', 'no-underline', 'text-small', 'mr-3');
  111. chipLink.href = `/${owner}/${repo}/search?l=${name}` //Chip link should bring you to the search query with the correct language in place
  112.  
  113. //Parse SVG BALL directly injecting the correct color as in-line style
  114. const svgText = `
  115. <svg style="color:${color};" aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1"
  116. width="16" data-view-component="true" class="octicon octicon-dot-fill mr-2">
  117. <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"></path>
  118. </svg>
  119. `;
  120. const svgTMP = document.createElement('template');
  121. svgTMP.innerHTML = svgText;
  122. chipLink.append(svgTMP.content);
  123. //^ uses a template HTMLElement to parse HTML into its respective DOM elements
  124.  
  125. //Adds language name to the chip
  126. const chipName = document.createElement('span');
  127. chipName.classList.add('color-fg-default', 'text-bold', 'mr-1');
  128. chipName.textContent = name;
  129. chipLink.append(chipName);
  130.  
  131. //Adds Language percentage to the chip
  132. const chipValue = document.createElement('span');
  133. chipValue.textContent = percentage + '%';
  134. chipLink.append(chipValue);
  135.  
  136. chip.append(chipLink);
  137. return chip;
  138. }
  139.  
  140. /**
  141. * Builds the custom language stats section and returns it
  142. * @returns The full section with complete repository language stats
  143. */
  144. function buildLanguagesSection(owner, repo) {
  145.  
  146. const languageSection = document.createElement("div");
  147. languageSection.classList.add("mb-3", "mt-1");
  148.  
  149. const bar = document.createElement('span');
  150. bar.classList.add("Progress", 'mb-2');
  151. bar.setAttribute("data-view-component", "true");
  152. Object.keys(langColorsMap).forEach((lang, i) => {
  153. const segment = buildBarSegmentSpan(lang, langColorsMap[lang], langPercentagesMap[lang]);
  154. //if (i !== 0) {
  155. // segment.style.setProperty('margin-left', '1px');
  156. //}
  157. bar.appendChild(segment);
  158. });
  159. languageSection.append(bar);
  160.  
  161. const languageUL = document.createElement('ul');
  162. Object.keys(langColorsMap).forEach((lang) => {
  163. const languageChip = buildLanguageChip(owner, repo, lang, langColorsMap[lang], langPercentagesMap[lang]);
  164. languageUL.append(languageChip);
  165. });
  166. languageSection.append(languageUL);
  167.  
  168. return languageSection;
  169. }
  170.  
  171. //MAIN ENTRY POINT
  172. (() => {
  173. 'use strict';
  174.  
  175. //Selects the box element that contains files and folders on the repo page
  176. const mainContent = document.querySelector(".Box-sc-g0xbh4-0.yfPnm");
  177. if (!mainContent)
  178. throw Error("mainContent Hook Selector is dead!")
  179.  
  180. //The original language bar in the sidebar
  181. const originalLangBar = document.querySelector("div.Layout-sidebar span.Progress");
  182.  
  183. //array that is generated from the tab URL, it's structured this way: ["", "<repo_owner>", "<repo_name>"]
  184. const ownerRepo = window.location.pathname.split('/');
  185.  
  186. //only works against github.com/ABC/DEF links
  187. if (ownerRepo.length === 3) {
  188. //retrieves necessary information about the repository's languages
  189. retrieveLanguages(ownerRepo[1], ownerRepo[2]).then((lang_vals) => {
  190. //assume request is successful if object is not null and it doesn't contain 'message' in its keys
  191. if (lang_vals !== null && !lang_vals.message) {
  192. //Sum of all language values
  193. const total = Object.values(lang_vals).reduce((prev, curr) => prev + curr);
  194. //for each language in the object
  195. Object.keys(lang_vals).forEach((lang) => {
  196. //
  197. langColorsMap[lang] = languages[lang].color;
  198. langPercentagesMap[lang] = ((lang_vals[lang] / total) * 100).toFixed(1);
  199. });
  200. }
  201. else return; //Short Circuit
  202.  
  203. //Build the new custom lang stats
  204. const languageSection = buildLanguagesSection(ownerRepo[1], ownerRepo[2]);
  205. mainContent.insertAdjacentElement('beforebegin', languageSection);
  206. //^ inserts our custom language stats before the box containing directories and files
  207.  
  208. //GM_log(langColorsMap);
  209. //GM_log(langPercentagesMap);
  210.  
  211. //Remove original Language Section (sidebar)
  212. originalLangBar.parentElement.parentElement.remove();
  213. });
  214. }
  215. })();

QingJ © 2025

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