GitHub Sortable Filelist

appends sorting function to github directories

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

  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.14.8
  7. // .8 sorting by file extention
  8. // .7 date/time display mode switching
  9. // .4 now works on all github pages
  10. // @created 2014-11-10
  11. // @updated 2014-11-14
  12. // @author trespassersW
  13. // @license MIT
  14. // @icon https://github.com/trespassersW/UserScripts/raw/master/res/github64.png
  15. // (C) Icon: Aaron Nichols CC Attribution 3.0 Unported
  16. // @run-at document-end
  17. // @grant GM_none
  18. // ==/UserScript==
  19.  
  20. if(document.body || document.querySelector('#js-repo-pjax-container')){ // .file-wrap
  21.  
  22. var llii=0; function _l(m){/* * /
  23. console.log(++llii +': '+m)
  24. /* */
  25. }
  26.  
  27. (function(){ "use strict";
  28.  
  29. var ii=0,tt;
  30. var d0=[0,0,1];
  31. var C=[{c:1, d: 0, s: 0},{c:2, d: 0, s: 0},{c:3, d: 1, s: 0}];
  32. var ASC;
  33. var oa=[],ca=[],clock,ext,dtStyle;
  34. var D=document, TB;
  35. var catcher,locStor;
  36. var prefs={dtStyle:0, ext: 0};
  37.  
  38. function stickStyle(css){
  39. var s=document.createElement("style"); s.type="text/css";
  40. s.appendChild(document.createTextNode(css));
  41. return (document.head||document.documentElement).appendChild(s);
  42. }
  43. function insBefore(n,e){
  44. return e.parentNode.insertBefore(n,e);
  45. }
  46. function insAfter(n,e){
  47. if(e.nextElementSibling)
  48. return e.parentNode.insertBefore(n,e.nextElementSibling);
  49. return e.parentNode.appendChild(n);
  50. }
  51. function outerNode(target, node) {
  52. if (target.nodeName==node) return target;
  53. if (target.parentNode)
  54. while (target = target.parentNode) try{
  55. if (target.nodeName==node)
  56. return target;
  57. }catch(e){};
  58. return null;
  59. }
  60. function savePrefs(){
  61. if(locStor) locStor.setItem('GHSFL',JSON.stringify(prefs));
  62. }
  63.  
  64. function css(){
  65.  
  66. stickStyle('\
  67. .fsort-butt,\
  68. .tables.xxfile td.content, .tables.file td.message, .tables.file td.age\
  69. {position: relative; }\
  70. .fsort-butt:before{\
  71. position: absolute; display: inline-block;\
  72. cursor: pointer;\
  73. z-index:99999;\
  74. content: "";\
  75. width: 16px; height: 16px;\
  76. }\
  77. .fsort-butt.fsort-asc:before,.fsort-butt.fsort-desc:before{\
  78. opacity:.2;\
  79. left:1.5em; top: -1em;\
  80. width: 0; height: 0;\
  81. }\
  82. .fsort-asc:before,.fsort-desc:before{\
  83. border-style: solid;\
  84. border-color: #654;\
  85. }\
  86. .fsort-asc:before,\
  87. .fsort-desc.fsort-sel:hover:before\
  88. {\
  89. border-left: 6px solid transparent;\
  90. border-right: 6px solid transparent;\
  91. border-bottom-width: 14px;\
  92. border-top-width: 0;\
  93. }\
  94. .fsort-desc:before,\
  95. .fsort-asc.fsort-sel:hover:before{\
  96. border-left: 6px solid transparent;\
  97. border-right: 6px solid transparent;\
  98. border-bottom-width: 0;\
  99. border-top-width: 14px;\
  100. }\
  101. \
  102. .fsort-sel:before,\
  103. .fsort-sel:before{\
  104. border-bottom-color: #4183C4 !important;\
  105. border-top-color: #4183C4 !important;\
  106. }\
  107. \
  108. .fsort-butt.fsort-sel:before{ opacity: .6 }\
  109. .fsort-butt:hover:before{ opacity: 1 !important;}\
  110. \
  111. #fsort-clock:before{\
  112. left:3em; top:-1.2em;\
  113. border-radius: 16px;\
  114. opacity:1;\
  115. content: url(data:image/png;base64,\
  116. 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=\
  117. );}\
  118. #fsort-clock:before{ background-color: #CCC }\
  119. #fsort-clock.fsort-on:before{ background-color: #4183C4 }\
  120. \
  121. #fsort-ext:before{\
  122. left:4em; top:-14px;\
  123. border-radius: 6px;\
  124. width:28px;height:16px;\
  125. opacity:.6;\
  126. content: url(data:image/png;base64,\
  127. iVBORw0KGgoAAAANSUhEUgAAABoAAAANCAYAAAC3mX7tAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVQ4y+2SP0sDQRDFT7BQTGtnITYSK8Emha1NCrXwC4gWYiUKiiB4jYKdWAmC4AewtxEExSqVjZ1/OEgwrEQwkLvbmd/aTCBI0IBYCD7YYmaYfW/mTRT9VWRZNq6qm79OBBwBrueGPM8ngWvgHbgXkQX7aC+EEFR1x+IDi7dUdQOQYACuviSp1+tDQA2oquoycAlInucT1Wp1EHgA3r3300AG3FUqlX4RKQOvQFNVV0Rk9ksiEZkzRbtJkgx476faqq1etnoK4L0vdazuqefVqepq6ALgKIqiKI7jPuDRcpVPHn1P5JwrxHHc1zHRofe+1H5pmo6akCWrv4QQgojMdxA9AI3vzPfAsXOuACRAXVW3VXUNOM2yrNhsNofNh1qapmPAG5A45wpGdGMiToD9bvdftKZ9I54ALoCGXd5tq9UaAc7Mr0Wbbr09vfk3Azybf+fRP36KDxJ2sN2uMATcAAAAAElFTkSuQmCC\
  128. );}\
  129. #fsort-ext:before{ background-color: #CCC}\
  130. .fsort-on:before{ background-color: #4183C4 !important;}\
  131. .fsort-on:hover:before{ opacity:1}\
  132. \
  133. table.files td.age .css-truncate.css-truncate-target{\
  134. width: 99% !important; \
  135. max-width: none !important;\
  136. }\
  137. table.files td.age span.css-truncate time{\
  138. position: relative !important;\
  139. }\
  140. \
  141. /* patches */\
  142. table.files td.age {text-align: left !important; padding: 0 4px !important;\
  143. }\
  144. table.files td.message {overflow: visible !important;}\
  145. ');
  146.  
  147. dtStyle=stickStyle('\
  148. table.files td.age span.css-truncate time{\
  149. visibility: hidden !important;\
  150. }\
  151. table.files td.age span.css-truncate > time:before{\
  152. content: attr(time-or-date);\
  153. visibility: visible !important;\
  154. position: absolute !important;\
  155. }\
  156. ')
  157. }
  158.  
  159. function setC(n){
  160. for(var i=0,il=C.length; i<il; i++ ){
  161. if(i!=n) C[i].s= 0, C[i].d=d0[i];
  162. else C[i].s=1;
  163. oa[i].className='fsort-butt fsort-'+(C[i].d?'desc':'asc')+(C[i].s?' fsort-sel':'') ;
  164. oa[i].title=C[i].d? '\u21ca' : '\u21c8';
  165. }
  166. }
  167.  
  168. function setDateTime(){
  169. try{ //014-10-02T16:09:05Z
  170. var DT=D.querySelectorAll('td.age span.css-truncate time'),res;
  171. for(var dt, dl=DT.length, i=0; i<dl; i++){
  172. dt = DT[i].getAttribute('datetime').match(/([0-9\-]+).([0-9\:]+)./);
  173. if(/minut|hour|just/.test(DT[i].textContent))
  174. DT[i].setAttribute("time-or-date",dt[2]);
  175. else
  176. DT[i].setAttribute("time-or-date",dt[1]);
  177. }
  178. }catch(e){(_l(e+'\n*GHSFL* wrong datetime'))}
  179. }
  180.  
  181. function isDir(x){
  182. return (TB.rows[x].cells[0].querySelector("span.octicon-file-directory")) != null;
  183. }
  184.  
  185. var sDir,sCells,sExts;
  186. var fa=[
  187. function(a){
  188. return TB.rows[a].cells[1].querySelector('span.css-truncate-target a').textContent;
  189. },
  190. function(a){
  191. return TB.rows[a].cells[2].querySelector('span.css-truncate').textContent;
  192. },
  193. function(a){
  194. return TB.rows[a].cells[3].querySelector('span.css-truncate>time').getAttribute('datetime');
  195. }
  196. ]
  197. function sort_p(n){// prepare data for sorting
  198. sDir=[],sCells=[];
  199. for(var tl=TB.rows.length, a=0; a<tl; a++) sDir.push(isDir(a));
  200. console.log(' sp:'+n+' ext:'+prefs.ext);
  201. if( n === 0 && prefs.ext ){
  202. for( a=0; a<tl; a++){ // f.x -> x.f
  203. var m, x=fa[n](a);
  204. m= x.match(/(.*)(\..*)$/);
  205. if(m && m.length==3) x=m[2]+m[1];
  206. sCells.push(x);
  207. }
  208. }else for( a=0; a<tl; a++) sCells.push(fa[n](a));
  209. }
  210.  
  211. function sort_fn(a,b){
  212. var x=sDir[a], y=sDir[b];
  213. if(x!=y) return ((x<y)<<1)-1;
  214. x= sCells[a], y= sCells[b];
  215. return x==y? 0: (((x>y)^ASC)<<1)-1;
  216. }
  217.  
  218. var CNn={content: 0, message: 1, age: 2}
  219.  
  220. function oClr(){
  221. var o= catcher.querySelectorAll('.fsort-butt')
  222. for(var ol=o.length,i=0;i<ol;i++)
  223. o[i].parentNode.removeChild(o[i]);
  224. }
  225. //
  226. function extclassName(){
  227. ext.className='fsort-butt'+ (prefs.ext? ' fsort-on': '' );
  228. }
  229. function clockclassName(){
  230. clock.className='fsort-butt'+ (prefs.dtStyle? '': ' fsort-on');
  231. }
  232. //
  233. function doSort(t){
  234. TB=outerNode(t,'TBODY');
  235. if(!TB){ _l( "*GHSFL* TBODY not found"); return; }
  236. var n=CNn[t.parentNode.className];
  237. if(typeof n=="undefined"){ _l( "*GHSFL* undefined col"); return; }
  238. if(t.id=='fsort-clock'){
  239. dtStyle.disabled = (prefs.dtStyle ^= 1);
  240. savePrefs();
  241. clockclassName();
  242. return;
  243. }
  244. var xt = (t.id=='fsort-ext');
  245. if( xt ){
  246. if(C[n].s) prefs.ext ^= 1;
  247. else prefs.ext= 1;
  248. savePrefs();
  249. extclassName();
  250. C[n].d^=C[n].s; // don't toggle dir on ext.click
  251. }
  252. var tb=[],ix=[], i, tl;
  253. _l('n:'+n);
  254. tl=TB.rows.length;
  255. ASC=C[n].d^=C[n].s;
  256. for( i=0; i<tl; i++)
  257. ix.push(i);
  258. oClr();
  259. sort_p(n);
  260. ix.sort(sort_fn);
  261. for( i=0; i<tl; i++)
  262. tb.push(TB.rows[ix[i]].innerHTML);
  263. for( i=0; i<tl; i++)
  264. TB.rows[i].innerHTML=tb[i];
  265. setC(n);
  266. gitDir1(0);
  267. }
  268.  
  269. function onClik(e){doSort(e.target)}
  270.  
  271. function gitDir1(x){
  272. if(x && document.querySelector('.fsort-butt')) {
  273. _l('gitDir'+x+' - already'); return;
  274. }
  275. _l('gitDir'+x)
  276. var c,o;
  277. ca=[];
  278. c= D.querySelector('.file-wrap table.files td.content >span');
  279. if(!c){ _l( '*GHSFL* no content') ; return; }
  280. ca.push(c);
  281. c=D.querySelector('.file-wrap table.files td.message >span');
  282. if(!c){ _l( '*GHSFL* no messages'); return; }
  283. ca.push(c);
  284. c=D.querySelector('.file-wrap table.files td.age >span');
  285. if(!c){_l( '*GHSFL* no ages'); return; }
  286. ca.push(c);
  287. if(x){ oClr(); oa=[];
  288. o=D.createElement('span');
  289. o.textContent='';
  290. oa.push(o);
  291. o=o.cloneNode(true);
  292. oa.push(o);
  293. o=o.cloneNode(true);
  294. oa.push(o);
  295. clock=D.createElement('span');
  296. clock.id='fsort-clock'; clockclassName();
  297. ext=D.createElement('span');
  298. ext.id='fsort-ext'; extclassName();
  299. setDateTime();
  300. setC(-1);
  301. }
  302. insBefore(oa[0],ca[0]);
  303. insBefore(ext,ca[0]);
  304. insBefore(oa[1],ca[1]);
  305. insBefore(oa[2],ca[2]);
  306. insBefore(clock,ca[2]);
  307. }
  308.  
  309. function gitDir(){
  310. gitDir1(1);
  311. }
  312.  
  313.  
  314. catcher= D.querySelector('#js-repo-pjax-container');
  315. if(!catcher){ _l( "*GHSFL* err0r"); return; }
  316.  
  317. catcher.addEventListener('mousedown',function(e){
  318. if(e.target.nodeName && e.target.nodeName=='SPAN' &&
  319. e.target.className.indexOf('fsort-butt')>-1)
  320. { onClik(e); }
  321. }
  322. ,false);
  323. _l('startup()');
  324.  
  325. try {
  326. locStor = window.localStorage;
  327. tt=locStor.getItem("GHSFL");
  328. } catch(e){ locStor =null}
  329.  
  330. if(locStor && tt) try{
  331. prefs =JSON.parse(tt);
  332. }catch(e){ console.log(e+"\n*GHSFL* bad prefs") }
  333.  
  334. css();
  335. dtStyle.disabled=(prefs.dtStyle===1);
  336.  
  337. gitDir();
  338.  
  339. var target = catcher; //document.body; //D.querSelector('.file-wrap');
  340. var MO = window.MutationObserver;
  341. if(!MO) MO= window.WebKitMutationObserver;
  342. if(!MO) return;
  343. var observer = new MO(function(mutations) {
  344. mutations.forEach(function(m) {
  345. if( m.type== "attributes" &&
  346. m.target.nodeName == 'DIV' &&
  347. m.target.className == "file-wrap" )
  348. gitDir();
  349. return;
  350. });
  351. });
  352.  
  353. //if(m.target.nodeName!='TIME')_l('mo'+' '+m.type +'.'+m.target.nodeName+m.target.className));
  354.  
  355. observer.observe(D.body, { attributes: true, subtree: true } );
  356. /* attributes: true , childList: true, subtree: true,
  357. characterData: true, attributeOldValue:true, characterDataOldValue:true
  358. */
  359.  
  360. })()};
  361. /*
  362. to do: persistent settings; sorting by file extensions; toggling date/time display mode
  363. ... do we really need it?
  364. */