GitHub FileSize Viewer

Show the file size next to it on the website

  1. // ==UserScript==
  2. // @name GitHub FileSize Viewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.9.12
  5. // @description Show the file size next to it on the website
  6. // @author nmaxcom
  7. // @match https://*.github.com/*
  8. // @grant GM_xmlhttpRequest
  9. // ==/UserScript==
  10.  
  11. // TODO: try document.addEventListener("pjax:start", function(){...}) and pjax:end as triggers
  12. (function(){
  13. /****************
  14. * Options:
  15. ****************/
  16. const DEBUG_MODE = true; // in production mode should be false
  17. const SHOW_BYTES = false; // false: always KB, i.e. '>1 KB', true: i.e. '180 B' when less than 1 KB
  18. /****************/
  19.  
  20. var textColor = '#6a737d'; // Default github style
  21. // var textColor = '#888'; // my dark github style
  22. createStyles();
  23.  
  24. var origXHROpen = XMLHttpRequest.prototype.open;
  25. XMLHttpRequest.prototype.open = function(){
  26. this.addEventListener('loadend', function(){
  27. if(DEBUG_MODE) console.log('%cStart: ' + document.title, 'color:white;background-color:#20A6E8;padding:3px');
  28. tableCheckandGo();
  29. });
  30. origXHROpen.apply(this, arguments);
  31. };
  32.  
  33. var vars = {}, response;
  34. tableCheckandGo();
  35.  
  36.  
  37. /**
  38. * Order of business:
  39. * - Detect table, if present, launch async API call and insert new blank cells
  40. * - Detect necessary info to make the async API call
  41. * - When promised is successful, change the blanks for the numbers
  42. * done: detect page change since github does that youtube thing
  43. */
  44.  
  45. function tableCheckandGo(){
  46. if(document.querySelector('table.files')){
  47. if(setVars()){
  48. var callPromise = callGitHub();
  49. callPromise
  50. .then(function(resp){
  51. response = resp;
  52. if(DEBUG_MODE) console.info('GitHub call went through');
  53. if(DEBUG_MODE) console.info(resp.responseText);
  54. insertBlankCells();
  55. fillTheBlanks(JSON.parse(resp.responseText));
  56. recheckAndFix();
  57. })
  58. .catch(function(fail){
  59. if(DEBUG_MODE) console.error(fail);
  60. });
  61. } else {
  62. if(DEBUG_MODE) console.info('setVars() failed. Vars: ', vars);
  63. }
  64. } else {
  65. if(DEBUG_MODE) console.info('No data table detected, nothing to do');
  66. }
  67. }
  68.  
  69. /**
  70. * API call
  71. */
  72. function callGitHub(){
  73. return new Promise(function(resolve, reject){
  74. // I'm forced to use GM_xmlhttpRequest to avoid Same Origin Policy issues
  75. GM_xmlhttpRequest({
  76. method : "GET",
  77. url : getAPIurl(),
  78. onload : function(res){
  79. resolve(res);
  80. },
  81. onerror: function(res){
  82. reject(res);
  83. }
  84. });
  85. });
  86. }
  87.  
  88. function getAPIurl(){
  89. // .../repos/:owner/:repo/contents/:path?ref=branch
  90. vars.dir = vars.dir || '';
  91. return "https://api.github.com/repos/" +
  92. vars.owner + "/" +
  93. vars.repo +
  94. "/contents/" +
  95. vars.dir + "?ref=" + vars.branch;
  96. }
  97.  
  98. /**
  99. * - Directories get new cellmate too
  100. *
  101. */
  102. function insertBlankCells(){
  103. var filenameCells = document.querySelectorAll('tr[class~="js-navigation-item"] > td.content');
  104. for(var i = 0, len = filenameCells.length; i < len; i++){
  105. var newtd = document.createElement('td');
  106. newtd.className = 'filesize';
  107. filenameCells[i].parentNode.insertBefore(newtd, filenameCells[i].nextSibling);
  108. }
  109. if(DEBUG_MODE) console.info(`Inserted ${i} cells`);
  110. }
  111.  
  112. /**
  113. * If we get the data, we insert it carefully so each filename gets matched
  114. * with the correct filesize.
  115. */
  116. function fillTheBlanks(JSONelements){
  117. if(!document.querySelectorAll('td.filesize').length){
  118. debugger;
  119. }
  120. var nametds = document.querySelectorAll('tr[class~="js-navigation-item"] > td.content a');
  121. var i, len;
  122. toploop:
  123. for(i = 0, len = JSONelements.length; i < len; i++){
  124. for(var cellnum in nametds){
  125. if(nametds.hasOwnProperty(cellnum) && JSONelements[i].name === nametds[cellnum].innerHTML){
  126. if(JSONelements[i].type === 'file'){
  127. var sizeNumber = (JSONelements[i].size / 1024).toFixed(0);
  128. if(SHOW_BYTES){
  129. sizeNumber = sizeNumber < 1 ? JSONelements[i].size + ' B' : sizeNumber + ' KB';
  130. } else {
  131. sizeNumber = sizeNumber < 1 ? '> 1 KB' : sizeNumber + ' KB';
  132. }
  133. nametds[cellnum].parentNode.parentNode.nextSibling.innerHTML = sizeNumber;
  134. }
  135. continue toploop;
  136. }
  137. }
  138. }
  139. if(DEBUG_MODE) console.info(`Processed ${i} of ${len} elements`);
  140. // if(DEBUG_MODE) console.info('Dumping json y nodes:');
  141. // if(DEBUG_MODE) console.log(JSONelements.forEach(function(e,i){if(DEBUG_MODE) console.log(JSONelements[i].name)}));
  142. // if(DEBUG_MODE) console.log(nametds.forEach(function(e,i){if(DEBUG_MODE) console.log(nametds[i].innerHTML)}));
  143.  
  144.  
  145. }
  146.  
  147. function createStyles(){
  148. var css = 'td.filesize { color: ' + textColor + ';' +
  149. 'text-align: right;' +
  150. 'padding-right: 50px !important; }' +
  151. 'table.files td.message { max-width: 250px !important;',
  152. head = document.head || document.getElementsByTagName('head')[0],
  153. style = document.createElement('style');
  154.  
  155. style.type = 'text/css';
  156. if(style.styleSheet){
  157. style.styleSheet.cssText = css;
  158. } else {
  159. style.appendChild(document.createTextNode(css));
  160. }
  161.  
  162. head.appendChild(style);
  163. }
  164.  
  165.  
  166. /**
  167. * Hay que satisfacer en la api el GET /repos/:owner/:repo/contents/:path?ref=branch
  168. * Con este regex capturamos del título \w+(.*?)\sat\s(.*?)\s.*?(\w+)\/(\w+)
  169. * 1) dir path, 2) branch, 3) owner, 4) repo
  170. * Ese regex no funciona en el root
  171. */
  172. function setVars(){
  173. var title = document.title;
  174. // Root folder:
  175. var match3 = title.match(/.*?([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+):/);
  176. // Non root folder, any branch:
  177. //var match1 = title.match(/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\sat\s([a-zA-Z0-9._-]+)\s·\s([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)/);
  178. // Root folder, we'll extract branch from scrape
  179. var match2 = title.match(/.+?\/([a-zA-Z0-9._\/-]+).*?·\s([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._\/-]+)/);
  180. if(match3){
  181. vars = {owner: match3[1], repo: match3[2]};
  182. vars.branch = document.querySelector('.branch-select-menu button span').innerHTML;
  183. }/*else if(match1) {
  184. vars = {repo: match1[1], dir: match1[2], branch: match1[3], owner: match1[4]};
  185. } */ else if(match2){
  186. vars = {dir: match2[1], owner: match2[2], repo: match2[3]};
  187. vars.branch = document.querySelector('.branch-select-menu button span').innerHTML;
  188. } else if(DEBUG_MODE) console.log(getAPIurl());
  189. return 1;
  190. }
  191.  
  192. /**
  193. * Sometimes, even though data has been correctly recieved, the DOM doesn't play well
  194. * for whatever reason. This function will quickly check if the data is indeed
  195. * there and if it's not will repaint the data again with the original functions.
  196. * TODO: finish this part
  197. */
  198. function recheckAndFix(){
  199. // Count td.filesize and compare to total rows
  200. let filesizes = document.querySelectorAll('td.filesize').length;
  201. let ages = document.querySelectorAll('td.age').length;
  202. if(filesizes === ages){
  203. if(DEBUG_MODE) console.info(`Good empty check: ${filesizes} of ${ages}`);
  204. } else {
  205. if(DEBUG_MODE) console.info(`Bad empty check: ${filesizes} of ${ages}. Repainting`);
  206.  
  207. }
  208. // Count non-empty td.filesize and compare to number of files from response
  209.  
  210. if(DEBUG_MODE) console.info(`Say something...`);
  211. }
  212. })();