GitHub Sortable Filelist

appends sorting function to github directories

目前為 2015-04-26 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name GitHub Sortable Filelist
  3. // @namespace trespassersW
  4. // @description appends sorting function to github directories
  5. // @include https://github.com/*
  6. // @version 15.04.26
  7. // 15.04.26 fixed width of content column :(
  8. // 15.03.15 css fixes: variable width of content column;
  9. // 14.11.19.13 fixes for latest github changes
  10. // .12 new age format; fix for chrome
  11. // .11 patch for the very first page;
  12. // .10 datetime auto-updating fix; right-aligned datetime column; proper local time; .ext sorting fix;
  13. // .8 sorting by file extention
  14. // .7 date/time display mode switching
  15. // .4 now works on all github pages
  16. // @created 2014-11-10
  17. // @updated 2015-03-25
  18. // @author trespassersW
  19. // @license MIT
  20. // @icon https://i.imgur.com/8buFLcs.png
  21. // (C) Icon: Aaron Nichols CC Attribution 3.0 Unported
  22. // @run-at document-end
  23. // @grant GM_none
  24. // ==/UserScript==
  25.  
  26. if(document.body && document.querySelector('#js-repo-pjax-container')){
  27.  
  28. var llii=0, _l= function(){/* * /
  29. for (var s=++llii +':', li=arguments.length, i = 0; i<li; i++)
  30. s+=' ' + arguments[i];
  31. console.log(s)
  32. /* */
  33. }
  34. var fakejs = // avoid compiler warning
  35. (function(){ "use strict";
  36.  
  37. var ii=0,tt;
  38. var d0=[0,0,1];
  39. var C=[{c:1, d: 0, s: 0},{c:2, d: 0, s: 0},{c:3, d: 1, s: 0}];
  40. var ASC;
  41. var oa=[],ca=[],clock,ext,dtStyle;
  42. var D=document, TB;
  43. var catcher,locStor;
  44. var prefs={dtStyle:0, ext: 0};
  45.  
  46. function stickStyle(css){
  47. var s=document.createElement("style"); s.type="text/css";
  48. s.appendChild(document.createTextNode(css));
  49. return (document.head||document.documentElement).appendChild(s);
  50. }
  51. function insBefore(n,e){
  52. return e.parentNode.insertBefore(n,e);
  53. }
  54. function insAfter(n,e){
  55. if(e.nextElementSibling)
  56. return e.parentNode.insertBefore(n,e.nextElementSibling);
  57. return e.parentNode.appendChild(n);
  58. }
  59. function outerNode(target, node) {
  60. if (target.nodeName==node) return target;
  61. if (target.parentNode)
  62. while (target = target.parentNode) try{
  63. if (target.nodeName==node)
  64. return target;
  65. }catch(e){};
  66. return null;
  67. }
  68. function savePrefs(){
  69. if(locStor) locStor.setItem('GHSFL',JSON.stringify(prefs));
  70. }
  71.  
  72. function css(){
  73. stickStyle('\
  74. .fsort-butt,\n\
  75. .tables.file td.content, .tables.file td.message, .tables.file td.age\n\
  76. {position: relative; }\n\
  77. \n\
  78. .fsort-butt:before{\n\
  79. position: absolute; display: inline-block;\n\
  80. cursor: pointer;\n\
  81. text-align:center; vertical-align: top;\n\
  82. width: 18px; height: 14px;\n\
  83. line-height: 14px;\n\
  84. padding:0; margin:0;\n\
  85. border-color: transparent;\n\
  86. border-width: 0;\n\
  87. content: "";\n\
  88. }\n\
  89. .fsort-butt.fsort-asc:before,.fsort-butt.fsort-desc:before{\n\
  90. opacity:.2;\n\
  91. left:1.5em; top: -1em;\n\
  92. }\n\
  93. td.age.fsort-butt.fsort-asc:before,td.age.fsort-butt.fsort-desc:before{\n\
  94. right:3em;\n\
  95. }\n\
  96. .fsort-asc:before,.fsort-desc:before{\n\
  97. background-color: #48C;\n\
  98. }\n\
  99. .fsort-asc:before{\n\
  100. border-radius: 24px 24px 8px 8px;\n\
  101. }\n\
  102. .fsort-desc:before{\n\
  103. border-radius: 8px 8px 24px 24px;\n\
  104. }\n\
  105. .fsort-asc:before,\n\
  106. .fsort-desc.fsort-sel:hover:before\n\
  107. {\n\
  108. content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAqklEQVR42mNgQAIfPnzg//fv3z4g3vjs2TNOBmzg27dv0kAFl/5DAZB95OPHj4Ioin79+qUFlHj0Hw0Axa5+//5dFqzo58+fGkCB9/9xAKDcw69fv0ow/P37twAqAAJLkRQsgLGBapIZQO4ACk798+eP9+/fv21gkkArZYAKYoFy09++fcuL4lZ0hQy4wMAqtIQpBEaAFE6FL1684AL6chcQb1m1ahUTshwAw2kAJAeNI30AAAAASUVORK5CYII=);\n\
  109. }\n\
  110. .fsort-desc:before,\n\
  111. .fsort-asc.fsort-sel:hover:before{content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAAACXBIWXMAAAsTAAALEwEAmpwYAAAApUlEQVR42mNgQAKrVq1i+vfv3xYg3vXixQsuBlzg27dvUv+h4Pfv35Y4FX7//l0GSaHNYFH49u1bXqAvp//9+zcWXeGfP3+8gXJTP378KMgAVJAMkwQKLkBiLwXifyA2UE0Bw9evXyWA/If/cQCg3PufP39qwNwmCxS4ikXRo1+/fmmhuBXkDqDEESRFl4ARII3V18+ePeMEKtgIxPs+fPjAjywHANCcACRZ1c8XAAAAAElFTkSuQmCC);\n\
  112. }\n\
  113. \n\
  114. .fsort-sel:before,\n\
  115. .fsort-sel:before{\n\
  116. background-color: #4183C4 !important;\n\
  117. }\n\
  118. \n\
  119. .fsort-butt.fsort-sel:before{ opacity: .6 }\n\
  120. .fsort-butt:hover:before{ opacity: 1 !important;}\n\
  121. \n\
  122. #fsort-clock:before{\n\
  123. left:5em; top: -15px; \n\
  124. text-align:center; vertical-align: top; top:-15px;\n\
  125. width: 16px; height: 16px;\
  126. border-radius: 16px;\n\
  127. opacity:1;\n\
  128. content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNqkU0tLG1EUvjORFJPMw6mhTUgxy75EUxiJj6CGLKQV0o2IQd3pxp2bbPs/3MVCIHSRgC3uXBhCSErjtLRkK7TcNCYhk4eGPHvO6AwjXXrgu/fOOff77plzz2VGoxF5iI3hEA6HCcuyxGKxEIZhOMBLcHsAwt0+FfAbDvsFaA4GAzIcDkkqlboVMNlzB8+vzgUCq3N+v+xyOl3opFdXNJfN5nPn52dNVT0DV/FeBjpZcjrfR/b3Dzwul0e02ciPiwsyPTtLptxur7C25n0xMzMfPzoSypQmdREWB0iLt3NcKLK3dzAhSR5+fFxT/JbPG+row9g2HABZhpBjCMA/vZYDgRVOFD02q5WwDKOROp2OIYA+jDlgz8Ly8gpyDIF+v++d9vlkXDdubshlpaKhWCwaawTGsHg+WZaRY9QAPiYEUXyinzY1OanNzWbTWGv7gPynViO4FzlmARavcWgivw2FSDAYJDubm4TjOA0Oh4O8WVwk80tLGscsoLZUtWwThGf6afFEgmxvbZGP8biRAbac2u2S60ajjByjBr1e7/JrJqOMQSNhmmiCJGlknHVAAckjuI1cOq0gx3wLyudkMt1ttejgTkAXMRsWkO106KdEIo0cQwAC9b+l0umHaPRYrVaphWX/63n0XasqjR4eHlNKT5FzrxPBoXxXFEtkY6Ozs7u78G59/dVTt/sxdkSJ0uqXk5OfsVgsU6lUUjzPKzqPwdfo8/lIF4pTr9dJu92WID0/tjZAv8MKti48tqzdbq+JUAsrNFWhULgVeIj9E2AAamUckFr2UCoAAAAASUVORK5CYII=\n\
  129. );}\n\
  130. #fsort-clock:before{ background-color: #CCC }\n\
  131. #fsort-clock.fsort-on:before{ background-color: #4183C4 }\n\
  132. \n\
  133. td.age .fsort-butt.fsort-asc:before,td.age .fsort-butt.fsort-desc:before{\n\
  134. left:3em !important;\n\
  135. }\
  136. #fsort-ext:before{\n\
  137. left:4em; top:-14px;\n\
  138. opacity:.6;\n\
  139. width:28px; height: 14px;\n\
  140. border-radius: 6px;\n\
  141. content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAANCAYAAAC3mX7tAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVQ4y+2SP0sDQRDFT7BQTGtnITYSK8Emha1NCrXwC4gWYiUKiiB4jYKdWAmC4AewtxEExSqVjZ1/OEgwrEQwkLvbmd/aTCBI0IBYCD7YYmaYfW/mTRT9VWRZNq6qm79OBBwBrueGPM8ngWvgHbgXkQX7aC+EEFR1x+IDi7dUdQOQYACuviSp1+tDQA2oquoycAlInucT1Wp1EHgA3r3300AG3FUqlX4RKQOvQFNVV0Rk9ksiEZkzRbtJkgx476faqq1etnoK4L0vdazuqefVqepq6ALgKIqiKI7jPuDRcpVPHn1P5JwrxHHc1zHRofe+1H5pmo6akCWrv4QQgojMdxA9AI3vzPfAsXOuACRAXVW3VXUNOM2yrNhsNofNh1qapmPAG5A45wpGdGMiToD9bvdftKZ9I54ALoCGXd5tq9UaAc7Mr0Wbbr09vfk3Azybf+fRP36KDxJ2sN2uMATcAAAAAElFTkSuQmCC);\n\
  142. }\n\
  143. #fsort-ext:before{ background-color: #BBB}\n\
  144. #fsort-ext:hover:before{ opacity:1 !important;}\n\
  145. .fsort-butt:not([class*="fsort-sel"]) ~ .fsort-on#fsort-ext:not(:hover):before\n\
  146. {opacity:.2 !important;}\n\
  147. .fsort-on:before{ background-color: #4183C4 !important; opacity: .5 !important;}\n\
  148. .fsort-on:hover:before{ opacity: 1 !important; }\n\
  149. \n\
  150. table.files td.age .css-truncate.css-truncate-target{\n\
  151. width: 99% !important; \n\
  152. max-width: none !important;\n\
  153. }\n\
  154. /*table.files td.age span.css-truncate time{\n\
  155. position: relative !important;\n\
  156. }*/\n\
  157. .fsort-time {\n\
  158. visibility: hidden;\n\
  159. display: none;\n\
  160. padding-right: 14px;\n\
  161. }\n\
  162. .fsort-time i {\n\
  163. display:inline-block;\
  164. color: #BBB;\
  165. font-style: normal !important;\n\
  166. transform: scale(0.9);\n\
  167. /* font-size: 12px;*/\n\
  168. }\n\
  169. \n\
  170. /* patches (--min-width:12em!important;) */\n\
  171. table.files td.age {text-align: right !important; padding-right: 10px !important;\n\
  172. width:12em!important;\n\
  173. \n\
  174. max-width:none!important;\n\
  175. overflow:visible!important;\n\
  176. }\n\
  177. table.files td.message {overflow: visible !important;}\n\
  178. /*.file-wrap .include-fragment-error { display: table-row !important;}*/\n\
  179. /* 150315 wide filelist *150426 better not touch this* /\n\
  180. div.wrapper div.container{\n\
  181. min-width: 980px!important;\n\
  182. width:90%!important;}\n\
  183. div.wrapper div#js-repo-pjax-container{\n\
  184. min-width: 790px!important;\n\
  185. width: calc(100% - 200px)!important;\n\
  186. }/* */\n\
  187. \
  188. ');
  189.  
  190. dtStyle=stickStyle('\
  191. td.age span.css-truncate time{\
  192. visibility: hidden !important;\
  193. display: none !important;\
  194. }\
  195. td.age span.css-truncate .fsort-time {\
  196. visibility: visible !important;\
  197. display: inline !important;\
  198. }\
  199. ')
  200. }
  201.  
  202. function setC(n){
  203. for(var i=0,il=C.length; i<il; i++ ){
  204. if(i!=n) C[i].s= 0, C[i].d=d0[i];
  205. else C[i].s=1;
  206. oa[i].className='fsort-butt fsort-'+(C[i].d?'desc':'asc')+(C[i].s?' fsort-sel':'') ;
  207. //oa[i].title=C[i].d? '\u21ca' : '\u21c8';
  208. }
  209. }
  210.  
  211. function dd(s)
  212. { s=s.toString(); if(s.length<2)return'0'+s; return s}
  213. function d2s(n){
  214. var hs=dd(n.getHours())+':'+dd(n.getMinutes());
  215. return {
  216. d: n.getFullYear()+'-'+dd(n.getMonth()+1)+'-'+dd(n.getDate())+' <i>'+ hs+'</i>',
  217. t: hs+':'+dd(n.getSeconds())
  218. }
  219. }
  220.  
  221. function setDateTime(x){
  222. var dt,dtm,dta,dtd,tc,m,now,t;
  223. var DT=D.querySelectorAll('td.age span.css-truncate time');
  224. _l('sDT',x?'refresh':'create');
  225. try{
  226. now = new Date();
  227. for(var dl=DT.length, i=0; i<dl; i++){
  228. dta=DT[i].getAttribute('datetime');
  229. dtd=new Date(dta);
  230. dt= d2s(dtd); // 2014-07-24T17:06:11Z
  231. dtm=null;
  232. if(x){
  233. dtm=DT[i].parentNode.querySelector('.fsort-time');
  234. }
  235. if(!dtm){
  236. dtm=D.createElement('span');
  237. dtm.className='fsort-time';
  238. x=0;
  239. }
  240. if(!x || !dtm.title || dtm.title != DT[i].title)
  241. { dtm.title= DT[i].title;
  242. t= dt.d;
  243. if( (now.getTime() - dtd.getTime() < 12*3600*1000) ||
  244. ((now.getTime() - dtd.getTime() < 24*3600*1000) &&
  245. (now.getDate() == dtd.getDate()) )
  246. ) t=dt.t;
  247. dtm.innerHTML=t;
  248. }
  249. if(!x) insAfter(dtm,DT[i]);
  250. }
  251. }catch(e){(console.log(e+'\n*GHSFL* wrong datetime'+x))}
  252. }
  253.  
  254. function isDir(x){
  255. var c= TB.rows[x].cells[0].querySelector("span");
  256. if(c.className.indexOf("-directory")>0) return 0;
  257. if(c.className.indexOf("-file")>0) return -1;
  258. return 1;
  259. }
  260. function getCell(r,c,s,p){
  261. var rc=TB.rows[r].cells[c],q=null;
  262. if(typeof rc == "undefined") {
  263. _l('r:',r,'c:',c,'- ???' );
  264. }else
  265. q=rc.querySelector(s);
  266. if(q) q= p? q.getAttribute(p): q.textContent;
  267. if(q) return q;
  268. return "";
  269. }
  270. var sDir,sCells,sExts;
  271. var fa=[
  272. function(a){
  273. return getCell(a,1,'span.css-truncate-target a');
  274. },
  275. function(a){
  276. return getCell(a,2,'span.css-truncate');
  277. },
  278. function(a){
  279. var c = getCell(a,3,'span.css-truncate>time','datetime');
  280. if(c) return c;
  281. return "2099-12-31T23:59:59Z"
  282. }
  283. ]
  284.  
  285. var b9='\x20\x20\x20'; b9+=b9+b9;
  286. function pad9(s){
  287. if(s.length<9) return (s+b9).substr(0,9);
  288. return s;
  289. }
  290. function sort_p(n){// prepare data for sorting
  291. sDir=[],sCells=[];
  292. for(var tl=TB.rows.length, a=0; a<tl; a++)
  293. sDir.push(isDir(a));
  294. if( n === 0 && prefs.ext ){
  295. for( a=0; a<tl; a++){ // f.x -> x.f
  296. var x=fa[n](a),
  297. m= x.match(/(.*)(\..*)$/);
  298. if(!m || !m[2]) m=['',x,''];
  299. x=pad9(m[2])+' '+m[1];
  300. sCells.push(x);
  301. }
  302. }else for( a=0; a<tl; a++) sCells.push(fa[n](a));
  303. }
  304.  
  305. function sort_fn(a,b){
  306. var x=sDir[a], y=sDir[b];
  307. if(x!=y) return ((x<y)? 1: -1);
  308. x= sCells[a], y= sCells[b];
  309. return x==y? 0: (((x>y)^ASC)<<1)-1;
  310. }
  311.  
  312. var CNn={content: 0, message: 1, age: 2}
  313.  
  314. function oClr(){
  315. var o= catcher.querySelectorAll('.fsort-butt,.fsort-time')
  316. for(var ol=o.length,i=0;i<ol;i++)
  317. o[i].parentNode.removeChild(o[i]);
  318. }
  319. //
  320. function extclassName(){
  321. ext.className='fsort-butt'+ (prefs.ext? ' fsort-on': '' );
  322. }
  323. function clockclassName(){
  324. clock.className='fsort-butt'+ (prefs.dtStyle? '': ' fsort-on');
  325. }
  326. //
  327. function doSort(t){
  328. TB=outerNode(t,'TBODY');
  329. if(!TB){ _l( "*GHSFL* TBODY not found"); return; }
  330. var n=CNn[t.parentNode.className];
  331. if(typeof n=="undefined"){ _l( "*GHSFL* undefined col"); return; }
  332. if(t.id=='fsort-clock'){
  333. dtStyle.disabled = (prefs.dtStyle ^= 1);
  334. savePrefs();
  335. clockclassName();
  336. return;
  337. }
  338. var xt = (t.id=='fsort-ext');
  339. if( xt ){
  340. if(C[n].s) prefs.ext ^= 1;
  341. else prefs.ext= 1;
  342. savePrefs();
  343. extclassName();
  344. C[n].d^=C[n].s; // don't toggle dir on ext.click
  345. }
  346. var tb=[],ix=[], i, tl;
  347. _l('n:'+n);
  348. tl=TB.rows.length;
  349. ASC=C[n].d^=C[n].s;
  350. for( i=0; i<tl; i++)
  351. ix.push(i);
  352. oClr();
  353. sort_p(n);
  354. ix.sort(sort_fn);
  355. for( i=0; i<tl; i++)
  356. tb.push(TB.rows[ix[i]].innerHTML);
  357. for( i=0; i<tl; i++)
  358. TB.rows[i].innerHTML=tb[i];
  359. setC(n);
  360. gitDir1(0);
  361. }
  362.  
  363. function onClik(e){doSort(e.target)}
  364.  
  365. function gitDir1(x){
  366. if(x && document.querySelector('.fsort-butt')) {
  367. _l('gitDir'+x+ '- already'); return;
  368. }
  369. _l('gitDir',x?'create':'refresh')
  370. var c,o;
  371. ca=[];
  372. c= D.querySelector('.file-wrap table.files td.content >span');
  373. if(!c){ _l( '*GHSFL* no content') ; return; }
  374. ca.push(c);
  375. c=D.querySelector('.file-wrap table.files td.message >span');
  376. if(!c){ _l( '*GHSFL* no messages'); return; }
  377. ca.push(c);
  378. c=D.querySelector('.file-wrap table.files td.age >span');
  379. if(!c){_l( '*GHSFL* no ages'); return; }
  380. ca.push(c);
  381. if(x){ oClr(); oa=[];
  382. o=D.createElement('span');
  383. o.textContent='';
  384. oa.push(o);
  385. o=o.cloneNode(true);
  386. oa.push(o);
  387. o=o.cloneNode(true);
  388. oa.push(o);
  389. clock=D.createElement('span');
  390. clock.id='fsort-clock'; clockclassName();
  391. ext=D.createElement('span');
  392. ext.id='fsort-ext'; extclassName();
  393. setDateTime();
  394. setC(-1);
  395. }
  396. insBefore(oa[0],ca[0]);
  397. insBefore(ext,ca[0]);
  398. insBefore(oa[1],ca[1]);
  399. insBefore(oa[2],ca[2]);
  400. insBefore(clock,ca[2]);
  401. }
  402.  
  403. function gitDir(){
  404. gitDir1(1);
  405. }
  406.  
  407. catcher= D.querySelector('#js-repo-pjax-container');
  408. if(!catcher){ _l( "*GHSFL* err0r"); return; }
  409.  
  410. catcher.addEventListener('mousedown',function(e){
  411. if(e.target.nodeName && e.target.nodeName=='SPAN' &&
  412. e.target.className.indexOf('fsort-butt')>-1)
  413. { onClik(e); }
  414. }
  415. ,false);
  416.  
  417. _l('startup()');
  418.  
  419. try {
  420. locStor = window.localStorage;
  421. tt=locStor.getItem("GHSFL");
  422. } catch(e){ locStor =null}
  423.  
  424. if(locStor && tt) try{
  425. prefs =JSON.parse(tt);
  426. }catch(e){ console.log(e+"\n*GHSFL* bad prefs") }
  427.  
  428. css();
  429. dtStyle.disabled=(prefs.dtStyle===1);
  430.  
  431. gitDir();
  432. var target = catcher; //document.body; //D.querSelector('.file-wrap');
  433. var MO = window.MutationObserver;
  434. if(!MO) MO= window.WebKitMutationObserver;
  435. if(!MO) return;
  436. var __started=0;
  437. var mutI=0;
  438. var observer = new MO(function(mutations) {
  439.  
  440. for(var m,t, ml=mutations.length, i=0; i<ml; i++)
  441. {
  442. m=mutations[i],t = m.target;
  443. if( m.type=="attributes")
  444. {
  445. if( t.nodeName == 'DIV' &&
  446. t.className == "file-wrap"
  447. ){
  448. gitDir();
  449. return;
  450. }
  451. // patch for the very first page
  452. if( 0===__started && t.nodeName=='TIME' )
  453. {
  454. if( t.parentNode.parentNode.className=="age" )
  455. {
  456. //_l('T'+mutI++,ml,' T:' +m.type,'N:'+t.nodeName, 'C:',"age" ) ;
  457. if(!catcher.querySelector('.fsort-butt'))
  458. gitDir(1); //chrome ?!11
  459. setDateTime(1);
  460. __started=1;
  461. return;
  462. }
  463. else continue;
  464. }
  465. }
  466. if( m.type=="childList" )
  467. {
  468. if( t.className=='age' )
  469. {
  470. if(!catcher.querySelector('.fsort-butt'))
  471. gitDir(1); //chrome ?!11
  472. //_l('C'+mutI++,ml,' T:' +m.type,'N:'+t.nodeName,'C:'+ t.className,t.querySelector('TIME').textContent) ;
  473. setDateTime(1);
  474. return;
  475. }
  476. else continue;
  477. }
  478. }
  479. });
  480.  
  481. observer.observe(D.body, { attributes: true, childList: true, subtree: true } );
  482. /* attributes: true , childList: true, subtree: true,
  483. characterData: true, attributeOldValue:true, characterDataOldValue:true
  484. */
  485.  
  486. })()};