Greasy Fork 支持简体中文。

GitHub Sortable Filelist

appends sorting function to github directories

目前為 2014-11-14 提交的版本,檢視 最新版本

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