- // ==UserScript==
- // @name Linguist Expand
- // @namespace https://davoleo.net
- // @author Davoleo
- // @homepage https://github.com/Davoleo/scripts/tree/master/linguist_expand
- // @description Expands Github's Linguist language list on repositories to show every language instead of hiding the small percentage under "Other"
- // @contributionURL https://davoleo.net/donate
- // @match https://github.com/*
- // @require https://unpkg.com/js-yaml@4.1.0/dist/js-yaml.min.js
- // @resource languageColors https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml
- // @grant GM_getResourceText
- // @grant GM_log
- // @grant GM_xmlhttpRequest
- // @connect api.github.com
- // @run-at document-idle
- // @version 1.0.2
- // @license MIT
- // @noframes
- // ==/UserScript==
-
- //Loads languages.yml (from Github's linguist repo) the most updated, official and complete collection of github languages and their colors
- //loaded into a JS object via jsyaml (a library to parse yaml inside of javascript)
- const languages = jsyaml.load(GM_getResourceText('languageColors'));
-
- //Contains information about languages and their percentages in the repository
- let langPercentagesMap = {}
- //Contains information about languages and their colors
- let langColorsMap = {}
-
- /**
- * Function to standardize and modernize GM_xmlhttpRequest to work with promises
- * @param {String} url of the endpoint
- * @param {Object} options Contains extra information about the request
- * @returns a promise with the requested content
- */
- function request(url, options={}) {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- url,
- method: options.method || "GET",
- headers: options.headers || {Accept: "application/json",
- "Content-Type": "application/json"},
- responseType: options.responseType || "json",
- data: options.body || options.data,
- onload: res => resolve(res.response),
- onerror: reject
- })
- })
- }
-
- /**
- * Retrieve information about the languages of a repository via the Github API
- * @param {String} user owner of the repository
- * @param {String} repo name
- * @returns the languages of the repository as a JS Object | null if the promise is rejected for any reason.
- */
- async function retrieveLanguages(user, repo) {
- try {
- return await request(`https://api.github.com/repos/${user}/${repo}/languages`, {
- headers: {
- Accept: "application/vnd.github.v3+json"
- }
- });
- }
- catch(e) {
- return null;
- }
- }
-
- /**
- * Builds language bar segments assigning the correct colors and width depending on the language and it's frequency in the repository
- * @param {string} name of the language
- * @param {string} color of the language
- * @param {number} percentage of the language in the repository code
- * @returns a segment span of the language bar with the correct width and color
- */
- function buildBarSegmentSpan(name, color, percentage) {
- const segment = document.createElement('span');
- segment.style.setProperty('background-color', color, 'important');
- segment.style.width = percentage + '%';
- //Removes any margin which would make the language bar otherwise inaccurate
- segment.style.setProperty('margin', '0', 'important');
- //Make sure there's at least 1px of width in the bar segment (fixes width of 0.0% segments)
- //TODO: investigate a better way to do this
- segment.style.paddingLeft = '1px';
- segment.setAttribute("itemprop", "keywords");
- segment.setAttribute("aria-label", name + ' ' + percentage);
- segment.setAttribute("data-view-component", "true");
- segment.setAttribute("class", "Progress-item color-bg-success-inverse lingustexpand");
- return segment;
- }
-
- /**
- * Builds a chip for each language containing
- * - The Color of the language in the bar
- * - The Name of the language
- * - The Percentage of the language in repository files
- * @param {String} owner of the repository
- * @param {String} repo name
- * @param {String} name of the language
- * @param {String} color of the language
- * @param {number} percentage percentage of the language in the repository code
- * @returns A chip components featured as legend for the language bar
- */
- function buildLanguageChip(owner, repo, name, color, percentage) {
- const chip = document.createElement('li');
- chip.classList.add('d-inline');
-
- const chipLink = document.createElement('a');
- chipLink.classList.add('d-inline-flex', 'flex-items-center', 'flex-nowrap', 'Link--secondary', 'no-underline', 'text-small', 'mr-3');
- chipLink.href = `/${owner}/${repo}/search?l=${name}` //Chip link should bring you to the search query with the correct language in place
-
- //Parse SVG BALL directly injecting the correct color as in-line style
- const svgText = `
- <svg style="color:${color};" aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1"
- width="16" data-view-component="true" class="octicon octicon-dot-fill mr-2">
- <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"></path>
- </svg>
- `;
- const svgTMP = document.createElement('template');
- svgTMP.innerHTML = svgText;
- chipLink.append(svgTMP.content);
- //^ uses a template HTMLElement to parse HTML into its respective DOM elements
-
- //Adds language name to the chip
- const chipName = document.createElement('span');
- chipName.classList.add('color-fg-default', 'text-bold', 'mr-1');
- chipName.textContent = name;
- chipLink.append(chipName);
-
- //Adds Language percentage to the chip
- const chipValue = document.createElement('span');
- chipValue.textContent = percentage + '%';
- chipLink.append(chipValue);
-
- chip.append(chipLink);
- return chip;
- }
-
- /**
- * Builds the custom language stats section and returns it
- * @returns The full section with complete repository language stats
- */
- function buildLanguagesSection(owner, repo) {
-
- const languageSection = document.createElement("div");
- languageSection.classList.add("mb-3", "mt-1");
-
- const bar = document.createElement('span');
- bar.classList.add("Progress", 'mb-2');
- bar.setAttribute("data-view-component", "true");
- Object.keys(langColorsMap).forEach((lang, i) => {
- const segment = buildBarSegmentSpan(lang, langColorsMap[lang], langPercentagesMap[lang]);
- //if (i !== 0) {
- // segment.style.setProperty('margin-left', '1px');
- //}
- bar.appendChild(segment);
- });
- languageSection.append(bar);
-
- const languageUL = document.createElement('ul');
- Object.keys(langColorsMap).forEach((lang) => {
- const languageChip = buildLanguageChip(owner, repo, lang, langColorsMap[lang], langPercentagesMap[lang]);
- languageUL.append(languageChip);
- });
- languageSection.append(languageUL);
-
- return languageSection;
- }
-
- //MAIN ENTRY POINT
- (() => {
- 'use strict';
-
- //Selects the box element that contains files and folders on the repo page
- const mainContent = document.querySelector(".Box-sc-g0xbh4-0.yfPnm");
- if (!mainContent)
- throw Error("mainContent Hook Selector is dead!")
-
- //The original language bar in the sidebar
- const originalLangBar = document.querySelector("div.Layout-sidebar span.Progress");
-
- //array that is generated from the tab URL, it's structured this way: ["", "<repo_owner>", "<repo_name>"]
- const ownerRepo = window.location.pathname.split('/');
-
- //only works against github.com/ABC/DEF links
- if (ownerRepo.length === 3) {
- //retrieves necessary information about the repository's languages
- retrieveLanguages(ownerRepo[1], ownerRepo[2]).then((lang_vals) => {
- //assume request is successful if object is not null and it doesn't contain 'message' in its keys
- if (lang_vals !== null && !lang_vals.message) {
- //Sum of all language values
- const total = Object.values(lang_vals).reduce((prev, curr) => prev + curr);
- //for each language in the object
- Object.keys(lang_vals).forEach((lang) => {
- //
- langColorsMap[lang] = languages[lang].color;
- langPercentagesMap[lang] = ((lang_vals[lang] / total) * 100).toFixed(1);
- });
- }
- else return; //Short Circuit
-
- //Build the new custom lang stats
- const languageSection = buildLanguagesSection(ownerRepo[1], ownerRepo[2]);
- mainContent.insertAdjacentElement('beforebegin', languageSection);
- //^ inserts our custom language stats before the box containing directories and files
-
- //GM_log(langColorsMap);
- //GM_log(langPercentagesMap);
-
- //Remove original Language Section (sidebar)
- originalLangBar.parentElement.parentElement.remove();
- });
- }
- })();