GitHub file list beautifier

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

当前为 2016-09-17 提交的版本,查看 最新版本

  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. // @author wOxxOm
  5. // @namespace wOxxOm.scripts
  6. // @license MIT License
  7. // @include https://github.com/*
  8. // @version 3.0.2
  9. // @icon https://octodex.github.com/images/murakamicat.png
  10. // @run-at document-start
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // ==/UserScript==
  14.  
  15. /* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */
  16.  
  17. // Your changes will be remembered in script database even after a forced update of the script
  18. var config = userConfig({
  19. iconSize: 24,
  20. colorSeed1: 13,
  21. colorSeed2: 1299721,
  22. colorSeed3: 179426453,
  23. });
  24.  
  25. (document.head || document.documentElement).insertAdjacentHTML('beforeend',
  26. '<style id="wOxxOm-GHB">\
  27. .wOxxOm-image-icon {\
  28. max-width:' + config.iconSize + 'px; max-height:' + config.iconSize + 'px;\
  29. width:auto; height:auto; margin:auto;\
  30. position:absolute; left:0; top:0; right:0; bottom:0 }\
  31. .wOxxOm-image-td {\
  32. position:relative; padding:0; min-width:' + (config.iconSize+4) + 'px; line-height:inherit }\
  33. a[file-type=":folder"] { font-weight: bold }\
  34. </style>'
  35. );
  36.  
  37. var filetypes = {};
  38. var styledom = document.getElementById('wOxxOm-GHB');
  39.  
  40. var filesdom = document.getElementsByTagName('table');
  41. var ob = new MutationObserver(GHBsentry);
  42.  
  43. document.addEventListener('DOMContentLoaded', function() { beautify(filesdom[0]) });
  44. beautify(filesdom[0]);
  45. lazyObserve();
  46.  
  47. function GHBsentry(mutations) {
  48. if (!/^\/[^\/]+\/[^\/]+(\/tree\/.*)?$/.test(location.pathname)) {
  49. ob.disconnect();
  50. lazyObserve();
  51. return;
  52. }
  53. if (mutations.length == 1 && !mutations[0].addedNodes.length || !filesdom[0])
  54. return;
  55. //console.log([].concat.apply([], mutations.map(m => [].slice.call(m.addedNodes))));
  56. for (var i=0, ml=mutations.length, m; i<ml && (m=mutations[i]); i++) {
  57. for (var j=0, added=m.addedNodes, nl=added.length, n; j<nl && (n=added[j]); j++) {
  58. if (n.nodeType != 3) {
  59. console.log(n.outerHTML);
  60. if (n.localName == 'a' && /\/(tree|blob)\//.test(n.href) && !n.hasAttribute('file-type')
  61. || n.localName == 'div' && n.matches('.file-wrap, .new-discussion-timeline, .repo-file-upload-tree-target')
  62. ) {
  63. var table = n.closest('table') || n.querySelector('table');
  64. if (table) {
  65. beautify(table);
  66. ob.disconnect();
  67. lazyObserve();
  68. break;
  69. }
  70. }
  71. }
  72. }
  73. }
  74. }
  75.  
  76. function lazyObserve() {
  77. // postpone observing until the parser can breathe after the initial burst of activity during page load
  78. setTimeout(function() {
  79. beautify(filesdom[0]);
  80. ob.observe(document, {subtree:true, childList:true});
  81. }, 0);
  82. }
  83.  
  84. function beautify(container, links) {
  85. if (!container && !links)
  86. return;
  87. //console.log(container.innerHTML);
  88. if (!links) {
  89. var table = container.localName == 'table' ? container : container.querySelector('table');
  90. if (!table)
  91. return;
  92. links = table.getElementsByClassName('js-navigation-open');
  93. }
  94.  
  95. for (var a, i=0, len=links.length; i<len && (a=links[i++]); ) {
  96. if (!a.href)
  97. continue;
  98. var tr = a.closest('tr');
  99. if (tr && tr.querySelector('.octicon-file-directory')) {
  100. a.setAttribute('file-type', ':folder');
  101. continue;
  102. }
  103.  
  104. var ext = a.href.match(/\.(\w+)$/);
  105. ext = ext && ext[1] || ':empty';
  106. a.setAttribute('file-type', ext);
  107. if (!filetypes[ext])
  108. addFileTypeStyle(ext);
  109.  
  110. if (/^(png|jpe?g|bmp|gif|cur|ico)$/.test(ext)) {
  111. var m = a.href.match(/github\.com\/(.+?\/)blob\/(.*)$/);
  112. if (!m)
  113. continue;
  114. var iconCell = a.closest('.js-navigation-item').querySelector('.icon');
  115. var icon = iconCell.querySelector('.octicon-file-text');
  116.  
  117. if (icon.style.display == 'none')
  118. continue;
  119. icon.style.display = 'none';
  120. icon.insertAdjacentHTML('afterend',
  121. '<img class="wOxxOm-image-icon" src="https://raw.githubusercontent.com/' + m[1] + m[2] + '">');
  122. iconCell.classList.add('wOxxOm-image-td');
  123. }
  124. }
  125. }
  126.  
  127. function addFileTypeStyle(type) {
  128. filetypes[type] = true;
  129. for (var hash=0, i=0, len=type.length; i<len; i++)
  130. hash = ((hash << 5) - hash) + type.charCodeAt(i);
  131. hash = Math.abs(hash*config.colorSeed1 |0);
  132. var color = 'a[file-type="' + type + '"] { color: hsl(' +
  133. (hash%360) + ',' +
  134. (hash*config.colorSeed2%50 + 50 |0) + '%,' +
  135. (hash*config.colorSeed3%30 + 25 |0) + '%) }';
  136. styledom.appendChild(document.createTextNode(color));
  137. }
  138.  
  139. function userConfig(params) {
  140. var def = {
  141. iconSize: 24,
  142. colorSeed1: 13,
  143. colorSeed2: 1299721,
  144. colorSeed3: 179426453,
  145. };
  146. Object.keys(params).forEach(function(k) {
  147. var saved = GM_getValue(k, def[k]);
  148. if (saved && saved != def[k])
  149. params[k] = saved;
  150. });
  151. return params;
  152. }