GitHub Sortable Filelist

appends sorting function to github directories

当前为 2014-11-18 提交的版本,查看 最新版本

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