Greasy Fork is available in English.

GitHub file list beautifier

Adds colors to files by type, displays small images in place of file-type icons in a repository source tree

Від 24.06.2020. Дивіться остання версія.

  1. // ==UserScript==
  2. // @name GitHub file list beautifier
  3. // @description Adds colors to files by type, displays small images in place of file-type icons in a repository source tree
  4. // @license MIT License
  5. //
  6. // @version 3.0.9
  7. //
  8. // @match https://github.com/*
  9. //
  10. // @grant none
  11. // @run-at document-start
  12. //
  13. // @author wOxxOm
  14. // @namespace wOxxOm.scripts
  15. // @icon https://octodex.github.com/images/murakamicat.png
  16. // ==/UserScript==
  17.  
  18. 'use strict';
  19.  
  20. let savedConfig = {};
  21. try {
  22. savedConfig = JSON.parse(localStorage.FileListBeautifier) || {};
  23. } catch (e) {}
  24.  
  25. const config = Object.assign({},
  26. ...Object.entries({
  27. iconSize: 24,
  28. colorSeed1: 13,
  29. colorSeed2: 1299721,
  30. colorSeed3: 179426453,
  31. }).map(([k, v]) => ({[k]: +savedConfig[k] || v})));
  32.  
  33. const IMG_CLS = 'wOxxOm-image-icon';
  34. const styleQueue = [];
  35. const {sheet} = document.documentElement.appendChild($create('style', {
  36. textContent: /*language=CSS*/ `
  37. .${IMG_CLS} {
  38. max-width: ${config.iconSize}px;
  39. max-height: ${config.iconSize}px;
  40. width: auto;
  41. height: auto;
  42. margin: auto;
  43. position: absolute;
  44. top: 0;
  45. left: 0;
  46. right: 0;
  47. bottom: 0;
  48. }
  49. .wOxxOm-image-cell {
  50. position: relative;
  51. padding: 0;
  52. margin: 0 10px 0 -6px;
  53. min-width: ${config.iconSize + 4}px;
  54. line-height: inherit;
  55. }
  56. .wOxxOm-image-cell svg {
  57. display: none;
  58. }
  59. a[file-type=":folder"] {
  60. font-weight: bold;
  61. }
  62. `.replace(/;/g, '!important;'),
  63. }));
  64.  
  65. const filetypes = {};
  66. const ME = Symbol(GM_info.script.name);
  67. const ob = new MutationObserver(() => {
  68. if (document.getElementById('files')) {
  69. ob.disconnect();
  70. start();
  71. }
  72. });
  73.  
  74. let lumaBias, lumaFix, lumaAmp;
  75.  
  76. start();
  77.  
  78. function start() {
  79. beautify();
  80. ob.observe(document, {subtree: true, childList: true});
  81. }
  82.  
  83. // postpone observing until the parser can breathe after the initial burst of activity during page load
  84. function beautify() {
  85. const el = document.getElementById('files');
  86. const table = el && el.parentElement.querySelector('.js-navigation-container');
  87. if (!table)
  88. return;
  89. for (const a of table.getElementsByClassName('js-navigation-open')) {
  90. if (!a.hasAttribute('href') || ME in a)
  91. continue;
  92. a[ME] = true;
  93. const row = a.closest('.js-navigation-item');
  94. if (row && row.querySelector('.octicon-file-directory')) {
  95. a.setAttribute('file-type', ':folder');
  96. continue;
  97. }
  98.  
  99. const ext = a.href.match(/\.(\w+)$|$/)[1] || ':empty';
  100. a.setAttribute('file-type', ext);
  101. if (!filetypes[ext])
  102. addFileTypeStyle(ext);
  103.  
  104. if (/^(png|jpe?g|bmp|gif|cur|ico)$/.test(ext)) {
  105. const m = a.href.match(/github\.com\/(.+?\/)blob\/(.*)$/);
  106. const icon = row.querySelector('.octicon');
  107. const next = icon && icon.nextElementSibling;
  108. if (!m || !icon || next && next.classList.contains(IMG_CLS))
  109. continue;
  110. icon.insertAdjacentElement('afterend', $create('img', {
  111. className: IMG_CLS,
  112. src: `https://raw.githubusercontent.com/${m[1]}${m[2]}`,
  113. }));
  114. icon.parentElement.classList.add('wOxxOm-image-cell');
  115. }
  116. }
  117. }
  118.  
  119. function addFileTypeStyle(type) {
  120. filetypes[type] = true;
  121. if (!styleQueue.length)
  122. requestAnimationFrame(commitStyleQueue);
  123. styleQueue.push(type);
  124. }
  125.  
  126. function commitStyleQueue() {
  127. if (!lumaAmp) initLumaScale();
  128. const seed2 = config.colorSeed2;
  129. const seed3 = config.colorSeed3;
  130. for (const type of styleQueue) {
  131. const hash = calcSimpleHash(type);
  132. const H = hash % 360;
  133. const Hq = H / 60;
  134. const S = hash * seed2 % 50 + 50 | 0;
  135. const redFix = (Hq < 1 ? 1 - Hq : Hq > 4 ? (Hq - 4) / 2 : 0);
  136. const blueFix = (Hq < 3 || Hq > 5 ? 0 : Hq < 4 ? Hq - 3 : 5 - Hq) * 3;
  137. const L = hash * seed3 % lumaAmp + lumaBias + (redFix + blueFix) * lumaFix * S / 100 | 0;
  138. sheet.insertRule(/*language=CSS*/ `
  139. a[file-type="${type}"]:not(#foo) {
  140. color: hsl(${H},${S}%,${L}%) !important;
  141. }
  142. `);
  143. }
  144. styleQueue.length = 0;
  145. }
  146.  
  147. function calcSimpleHash(text) {
  148. let hash = 0;
  149. for (let i = 0, len = text.length; i < len; i++)
  150. hash = ((hash << 5) - hash) + text.charCodeAt(i);
  151. return Math.abs(hash * config.colorSeed1 | 0);
  152. }
  153.  
  154. function initLumaScale() {
  155. const [, r, g, b] = getComputedStyle(document.body).backgroundColor.split(/[^\d.]+/).map(parseFloat);
  156. const isDark = (r * .2126 + g * .7152 + b * .0722) < 128;
  157. [lumaBias, lumaAmp, lumaFix] = isDark ? [30, 50, 12] : [25, 15, 0];
  158. }
  159.  
  160. function $create(tag, props) {
  161. return Object.assign(document.createElement(tag), props);
  162. }