Jira Issue Links & Files Tablification

Makes Links and Files sections of Issues into tables that can be sorted

  1. // ==UserScript==
  2. // @name Jira Issue Links & Files Tablification
  3. // @namespace http://home.comcast.net/~mailerdaemon
  4. // @description Makes Links and Files sections of Issues into tables that can be sorted
  5. // @include *jira*/browse/*-*
  6. // @version 1.9
  7. // ==/UserScript==
  8.  
  9. //Test URLS
  10. //http://jira.atlassian.com/browse/JRA-16868
  11. //http://jira.activemath.org/browse/IG-370
  12. //http://jira.atlassian.com/browse/JRA-5959
  13. //http://jira.secondlife.com/browse/WEB-856
  14. //http://jira.atlassian.com/browse/JRA-2509
  15.  
  16. init();
  17.  
  18. GM_addStyle([
  19. "table.jilft {}",
  20. "table.jilft tr.row > td:nth-child(n+2) { border-left:1px dotted rgba(0, 0, 0, 0.133);}",
  21. "table.jilft tr.header td { font-size:9px; border-bottom:1px solid rgba(0,0,0,0.25); cursor:pointer; -moz-user-select: none;}",
  22.  
  23. "table.links_table { width:100%; }",
  24. "table.links_table td { padding-left: 1px; padding-right: 1px; }",
  25. "table.files_table { empty-cells:show; width:auto; }",
  26. "table.files_table td { padding: 1px 3px; }",// text-shadow: 0 0 2px white;
  27. "#attachment_thumbnails li.latest, table.files_table tr.latest { "+CSSmulti("box-shadow", "0 0 8px rgba(0, 255, 0, 0.20)") +"}", //0.025
  28. "#attachment_thumbnails li.latest, table.files_table tr.latest td { background-color:rgba(0, 255, 0, 0.08); text-shadow: 0 0 2px white, 0 0 2px white, 0 0 2px white, 0 0 1px white;}",
  29. "table.files_table tr.stale td { color: rgb(153, 153, 153); }",
  30. "table.files_table tr.stale td:hover { color: rgb(138, 138, 138); }",
  31. "table.links_table tr.header, table.links_table td.key { white-space: nowrap; }",
  32. "#attachment_thumbnails span.blender {display:none;}",
  33. ".t_links tbody td { width: auto; }",
  34.  
  35. "#attachment_thumbnails .attachment-content { height: 256px;}",
  36. "#attachment_thumbnails .attachment-content > td { display:none; }",
  37. "#attachment_thumbnails .attachment-size { top:auto; float:none;}",
  38. "#attachment_thumbnails .attachment-title, #attachment_thumbnails dl { height:auto; }",
  39. "#attachment_thumbnails dt, #attachment_thumbnails dd { line-height:1.1em; font-size:90%; text-align:center; float:none;}",
  40. //copied from atlassian css
  41. "a.user-no-hover.user-avatar, span.user-no-hover.user-avatar { background-position: left center; background-repeat: no-repeat; padding: 2px 0 2px 19px; }",
  42. ].join("\n\n"));
  43.  
  44. //update these!!!
  45. var priorities = ["Showstopper","Critical","Major","Normal","Low","Trivial","Nice to have"];
  46.  
  47. var statuses = {
  48. "Open":1,
  49. "In Progress":50,
  50. "Reopened":100,
  51. "Fix Pending":150,
  52. "Resolved":200,
  53. "Closed":250,
  54. "Ready for Translation":-1,
  55. "Translation In Progress":-1,
  56. "Translated":-1,
  57. "In QA":-1,
  58. "Passed QA":-1,
  59. "Translation Final":-1,
  60. "Published":-1,
  61. "On Hold":-1,
  62. "Editing In Progress":-1,
  63. "Edited":-1,
  64. "Testing":-1,
  65. "Verified":-1,
  66. "Verified: Fixed":-1,
  67. "Verified: Fix Needed":-1,
  68. "Republished":-1,
  69. large:10000,
  70. }
  71.  
  72. if($X("//div[contains(concat(' ', @class, ' '), ' content ') and div[contains(concat(' ', @class, ' '), ' module ')]]"))
  73. {//V4.1.1
  74. $Z("//div[contains(concat(' ', @class, ' '), ' content ')]/div[@id='attachmentmodule']", function(module){
  75. $Z(".//ol[@id='file_attachments']", function(r, i){
  76. var table = document.createElement("table");
  77. table.cellPadding = 1;
  78. table.cellSpacing = 0;
  79. table.className = "files_table jilft";
  80. table.setAttribute("order", [-1]);
  81. tbody = document.createElement("tbody");
  82. table.appendChild(tbody);
  83. var tr = document.createElement("tr");
  84. tr.align="center"
  85. tr.style.whiteSpace="nowrap";
  86. tr.style.boarderBottom="1px;"
  87. tr.className="header row";
  88. tr.appendChild(NewHeader(1, "#", "Initial Ordering"));
  89. tr.appendChild(NewHeader(2, "T", "Type"));
  90. tr.appendChild(NewHeader(3, "Filename"));
  91. tr.appendChild(NewHeader(4, "Size"));
  92. tr.appendChild(NewHeader(5, "Date"));
  93. tr.appendChild(NewHeader(6, "Uploader"));
  94. tr.appendChild(NewHeader(7, "Latest"));
  95. tbody.appendChild(tr);
  96. $Z("li[contains(concat(' ', @class, ' '), ' attachment-content ')]", function(li, i){
  97. tbody.appendChild(File(i + 1,
  98. $X(".//div[contains(concat(' ', @class, ' '), ' attachment-thumb ')]/a", li),
  99. $X(".//*[contains(concat(' ', @class, ' '), ' attachment-title ')]/descendant-or-self::a", li),
  100. $X(".//dd[contains(concat(' ', @class, ' '), ' attachment-size ')]", li)
  101. ))
  102. }, r);
  103. var click = generic_click(1, FileSort);//FileShifter
  104. click.apply(tr.cells[0]);//we cheat!
  105. replace(r, table);
  106. //r.parentNode.appendChild(table);
  107. for(j = 0; j < tr.cells.length; j++)
  108. tr.cells[j].addEventListener('click', click, false);
  109. }, module);
  110. $Z(".//ol[@id='attachment_thumbnails']", function(ol, i){
  111. $Z("li[contains(concat(' ', @class, ' '), ' attachment-content ')]", function(li, i){
  112. //just provide the missing info
  113. var dl = $X("dl", li);
  114. var thumb = $X(".//div[contains(concat(' ', @class, ' '), ' attachment-thumb ')]/a", li);
  115. var link = $X(".//*[contains(concat(' ', @class, ' '), ' attachment-title ')]/descendant-or-self::a", li);
  116. var size = $X(".//dd[contains(concat(' ', @class, ' '), ' attachment-size ')]", li)
  117. obj = FileObj(i + 1, null, link, size)
  118. FileSetAttributes(li, obj)
  119. if(!(date = $X("dd[contains(concat(' ', @class, ' '), ' attachment-date ')]", dl)))
  120. dl.insertBefore(date = document.createElement("dd"), size).appendChild(obj.date[0]);
  121. dl.insertBefore(creator = document.createElement("dd"), size).appendChild(obj.creator[0]);
  122. // dl.appendChild(latest = document.createElement("dd")).appendChild($X("td[7]/text()", tr));
  123. //GM_log(tr.className);
  124. li.title = link.title;
  125. }, ol);
  126. var table = document.createElement("table");
  127. table.cellPadding =1;
  128. table.cellSpacing = 0;
  129. table.className = "files_table jilft";
  130. ol.setAttribute("order", [-1]);
  131. tbody = document.createElement("tbody");
  132. table.appendChild(tbody);
  133. var tr = document.createElement("tr");
  134. tr.align="center"
  135. tr.style.whiteSpace="nowrap";
  136. tr.style.boarderBottom="1px;"
  137. tr.className="header row";
  138. tr.appendChild(NewHeader(1, "Initial Ordering"));
  139. tr.appendChild(NewHeader(2, "Filename"));
  140. tr.appendChild(NewHeader(3, "Size"));
  141. tr.appendChild(NewHeader(4, "Date"));
  142. tr.appendChild(NewHeader(5, "Uploader"));
  143. tr.appendChild(NewHeader(6, "Latest"));
  144. tbody.appendChild(tr);
  145. var click = generic_list_click(ol, ImageSort);
  146. click.apply(tr.cells[0]);//we cheat!
  147. ol.insertBefore(table, ol.firstChild);
  148. for(j = 0; j < tr.cells.length; j++)
  149. tr.cells[j].addEventListener('click', click, false);
  150. }, module);
  151. });
  152. {//Link Tables
  153. var click = generic_click(2, LinkSort)//, LinkShifter);
  154. $Z("//table[@id = 'outwardLinks_table' or @id = 'inwardLinks_table']", function L1(table){
  155. table.className = (table.className + " links_table jilft").trim();
  156. var top = $X("thead/tr/th", table);
  157. top.colSpan=5;
  158. table.setAttribute("order", [1]);
  159. $Z("tbody/tr", function(tr, i){
  160. var summary = $X("td/div[contains(concat(' ', @class, ' '), ' flooded ')]", tr);
  161. var wrapper = $X(".//div[contains(concat(' ', @class, ' '), ' attribute-wrap ')]", summary);
  162. var priority = $X(".//span[contains(concat(' ', @class, ' '), ' priority ')]", wrapper);
  163. var status = $X(".//span[contains(concat(' ', @class, ' '), ' status ')]", wrapper);
  164. var link = $X(".//a", summary);
  165. tr.className = tr.className.split(" ").concat("row").join(" ");
  166. tr.setAttribute("number", i + 1);
  167. replace(summary, document.createTextNode(i + 1));
  168. tr.appendChild(key = document.createElement("td")).appendChild(link);
  169. tr.appendChild(sum = document.createElement("td")).appendChild(summary);
  170. if(priority)
  171. {
  172. var s = $X(".//img", priority).alt;
  173. var e = priorities.indexOf(s);
  174. if(e == null || e == -1) e = priorities.length;
  175. tr.setAttribute("priority", e + 1);
  176. tr.appendChild(document.createElement("td")).appendChild(priority);
  177. }
  178. else
  179. {
  180. tr.appendChild(document.createElement("td"));
  181. tr.setAttribute("priority", priorities.length + 1);
  182. }
  183. tr.appendChild(document.createElement("td")).appendChild(status);
  184. key.className = "key";
  185. sum.title = link.title && link.title.trim();
  186. var s = link.textContent.trim();
  187. var e = s.lastIndexOf("-") + 1;
  188. tr.setAttribute("key", s.slice(0, e).concat(Array(9 + e - s.length).join("0"), s.slice(e)));
  189. var s = $X(".//img", status).alt;
  190. var e = statuses[s];
  191. if(e == null || e == -1) e = statuses.large;
  192. tr.setAttribute("status", e);
  193. if(!$X("*", wrapper))
  194. remove(wrapper);
  195. else
  196. GM_log("unhandled attribute!")
  197. }, table);
  198. var tr = document.createElement("tr");
  199. tr.align="center"
  200. tr.className="header row";
  201. tr.appendChild(number = NewHeader(1, "#", "Initial Ordering", {status:true}));
  202. tr.appendChild(key = NewHeader(2, "Key", null, {status:true}));
  203. tr.appendChild(summary = NewHeader(3, "Summary", null, {status:true}));
  204. tr.appendChild(priority = NewHeader(4, "P", "Priority", {status:true}));
  205. tr.appendChild(status = NewHeader(5, "S", "Status", {status:true}));
  206.  
  207. summary.width="100%";
  208. insertAfter(tr, top.parentNode);
  209. for(j = 0; j < tr.cells.length; j++)
  210. tr.cells[j].addEventListener('click', click, false);
  211. });
  212. }
  213. }
  214. else
  215. {//Old V3 to V4.1.0
  216. if((files = $X("//td[@id='file_attachments']")) && (files.textContent.trim() != "None")) {
  217. table = document.createElement("table");
  218. table.cellPadding =1;
  219. table.cellSpacing = 0;
  220. table.className = "files_table jilft";
  221. table.setAttribute("order", [-1]);
  222. tbody = document.createElement("tbody");
  223. table.appendChild(tbody);
  224. var tr = document.createElement("tr");
  225. tr.align="center"
  226. tr.style.whiteSpace="nowrap";
  227. tr.style.boarderBottom="1px;"
  228. tr.className="header row";
  229. tr.appendChild(NewHeader(1, "#", "Initial Ordering"));
  230. tr.appendChild(NewHeader(2, "T", "Type"));
  231. tr.appendChild(NewHeader(3, "Filename"));
  232. tr.appendChild(NewHeader(4, "Size"));
  233. tr.appendChild(NewHeader(5, "Date"));
  234. tr.appendChild(NewHeader(6, "Uploader"));
  235. tr.appendChild(NewHeader(7, "Latest"));
  236. tbody.appendChild(tr);
  237.  
  238. var items = [];
  239. var nodes = [files];
  240. while(nodes.length > 0)
  241. {
  242. var node = nodes.shift();
  243. var offset = 0;
  244. do
  245. {
  246. if(offset + 7 < node.childNodes.length)
  247. {
  248. while(e = /^[\d]+\.$/.exec(node.childNodes[offset].nodeValue.trim()))
  249. {
  250. tbody.appendChild(File(e[0], node.childNodes[offset + 1], node.childNodes[offset + 3], node.childNodes[offset + 5]));
  251. offset += 7;
  252. }
  253. }
  254. if((node.childNodes.length > ++offset) && node.childNodes[offset].nodeName == "SPAN")
  255. nodes.push(node.childNodes[offset++]);
  256. else
  257. break;
  258. }while(true);
  259. }
  260. var click = generic_click(1, FileSort);//FileShifter
  261. click.apply(tr.cells[0]);//we cheat!
  262. files.innerHTML="";
  263. files.appendChild(table);
  264. for(j = 0; j < tr.cells.length; j++)
  265. tr.cells[j].addEventListener('click', click, false);
  266. }
  267.  
  268. {//Link Tables
  269. var click = generic_click(2, LinkSort, LinkShifter);
  270. $Z("//table[@id = 'outwardLinks_table' or @id = 'inwardLinks_table']", function L1(r, i, p){
  271. r.className = (r.className + " links_table jilft").trim();
  272. top = $X(".//tr[1]/td[@colspan]", r);
  273. top.colspan=5;
  274. r.setAttribute("order", [1]);
  275. $Z(".//tr[position()>1 and td[@width]]", L2, r);
  276. var tr = document.createElement("tr");
  277. tr.align="center"
  278. tr.className="header row";
  279. tr.appendChild(number = NewHeader(1, "#", "Initial Ordering", {status:true}));
  280. tr.appendChild(key = NewHeader(2, "Key", null, {status:true}));
  281. tr.appendChild(summary = NewHeader(3, "Summary", null, {status:true}));
  282. tr.appendChild(priority = NewHeader(4, "P", "Priority", {status:true}));
  283. tr.appendChild(status = NewHeader(5, "S", "Status", {status:true}));
  284.  
  285. insertAfter(tr, top.parentNode);
  286. for(j = 0; j < tr.cells.length; j++)
  287. tr.cells[j].addEventListener('click', click, false);
  288. });
  289. }
  290. }
  291.  
  292. /*
  293. {//Sub-Tasks
  294. //https://jira.secondlife.com/browse/SVC-5165
  295. $Z("//form[@id='stqcform']/table[@id='issuetable']", function(r, i){
  296. r.className = (r.className + " links_table jilft").trim();
  297. r.setAttribute("order", [1]);
  298. $Z("./tbody/tr", function(tr, i){
  299. tr.setAttribute("sequence", Number(tr.cells[0]).textContent))
  300. var sum = $X(".//a", tr.cells[1]);
  301. tr.cells[1].colSpan="2"
  302. var key = /\/browse\/([A-Z]{3,})-([\d]+)/.match(sum.getAttribute("href"))
  303. tr.setAttribute("key", key[1] + key[2].leftPad(7))
  304. tr.setAttribute("summary", sum.textContent)
  305. tr.setAttribute("issuetype", $X(".//img", tr.cells[2])).alt)
  306. tr.setAttribute("status", statuses[$X(".//img", tr.cells[3])).alt])
  307. var assignee = $X("./td[contains(concat(concat(' ', @class), ' '), ' assignee ')]//img", tr);
  308. tr.setAttribute("assignee", )
  309. }, r);
  310. var tr = document.createElement("tr");
  311. tr.align="center"
  312. tr.className="header row";
  313. tr.appendChild(number = NewHeader(1, "#", "Initial Ordering", {status:true}));
  314. tr.appendChild(key = NewHeader(2, "Key", null, {status:true}));
  315. tr.appendChild(summary = NewHeader(3, "Summary", null, {status:true}));
  316. tr.appendChild(priority = NewHeader(4, "P", "Priority", {status:true}));
  317. tr.appendChild(status = NewHeader(5, "S", "Status", {status:true}));
  318.  
  319. insertAfter(tr, top.parentNode);
  320. for(j = 0; j < tr.cells.length; j++)
  321. tr.cells[j].addEventListener('click', click, false);
  322. });
  323. var click = generic_click(2, TaskSort);
  324. }
  325. //*/
  326. function TaskSort(by, a, b){
  327. by = Math.abs(by) - 1;
  328. var name = ["sequence", "key", "summary", "issuetype", "status", "assignee"][by];
  329. var left, right, ret = 0;
  330. if(name)
  331. {
  332. left = a.getAttribute(name);
  333. right = b.getAttribute(name);
  334. }
  335. switch(by)
  336. {
  337. case 2:
  338. left = a.cells[2].title;
  339. right = b.cells[2].title;
  340. case 1:
  341. ret = (left==right)?0:(left<right)?-1:1;
  342. break;
  343. default:
  344. ret = left - right;
  345. break;
  346. }
  347. return ret;
  348. }
  349.  
  350. function TaskShifter(row){
  351. return $X("./td[1]/div", row);
  352. }
  353.  
  354. function LinkSort(by, a, b){
  355. by = Math.abs(by) - 1;
  356. var name = ["number", "key", null, "priority", "status"][by];
  357. var left, right, ret = 0;
  358. if(name)
  359. {
  360. left = a.getAttribute(name);
  361. right = b.getAttribute(name);
  362. }
  363. switch(by)
  364. {
  365. case 2:
  366. left = a.cells[2].title;
  367. right = b.cells[2].title;
  368. case 1:
  369. ret = (left==right)?0:(left<right)?-1:1;
  370. break;
  371. default:
  372. ret = left - right;
  373. break;
  374. }
  375. //log({by:by, name:name, left:left, right:right, ret:ret});
  376. return ret;
  377. }
  378.  
  379. function LinkShifter(row){
  380. return $X("./td[1]/img", row);
  381. }
  382.  
  383. function FileShifter(row){
  384. return row.cells[0];
  385. }
  386.  
  387. function ImageSort(by, a, b){
  388. by = Math.abs(by) - 1;
  389. var name = ["number", "name", "size", "date", "creator", "latest"][by];
  390. var left = a.getAttribute(name), right = b.getAttribute(name);
  391. var ret = 0;
  392. switch(by)
  393. {
  394. case 5:
  395. left = (left=="false");
  396. right = (right=="false");
  397. case 0:
  398. case 2:
  399. case 3:
  400. ret = left - right;
  401. break;
  402. // case 1:
  403. // left = left.toLowerCase();
  404. // right = right.toLowerCase();
  405. default:
  406. ret = (left==right)?0:(left<right)?-1:1;
  407. break;
  408. }
  409. // log({by:by, name:name, left:left, right:right});
  410. return ret;
  411. }
  412.  
  413. function FileSort(by, a, b){
  414. by = Math.abs(by) - 1;
  415. var name = ["number", "type", "name", "size", "date", "creator", "latest"][by];
  416. var left = a.getAttribute(name), right = b.getAttribute(name);
  417. var ret = 0;
  418. switch(by)
  419. {
  420. case 0:
  421. case 3:
  422. case 4:
  423. ret = left - right;
  424. break;
  425. case 6:
  426. left = (left=="false");
  427. right = (right=="false");
  428. default:
  429. ret = (left==right)?0:(left<right)?-1:1;
  430. break;
  431. }
  432. // log({by:by, name:name, left:left, right:right});
  433. return ret;
  434. }
  435.  
  436. function NewHeader(num, text, name, obj){
  437. td = document.createElement("td")
  438. td.appendChild(document.createTextNode(text));
  439. td.title = "Sort by " + (name?name:text);
  440. td.setAttribute("type", num);
  441. if(obj)
  442. for(var a in obj)
  443. td.setAttribute(a, obj[a]);
  444. return td;
  445. }
  446.  
  447. function FileObj(num, icon, link, size)
  448. {
  449. var m = /^(?:(.+?) - |)(?:(Latest) |)(([^-]+)|([\d]{4})-([\d]{2})-([\d]{2}) ([\d]{2}):([\d]{2})) - ([^-]+)$/.exec(link.title);
  450. var latest = m[2] != null;
  451. var date = m[3];
  452. var creator = m[10];
  453. var nice_date = (m[4] && JiraTimeStringToDate(m[4])) || (m[5] && new Date(m[5],m[6],m[7],m[8],m[9]))
  454. var name = m[1] || link.textContent.trim()
  455. m = /([\d]+(?:\.[\d]+)?) ([kTGM]B)/.exec(size.textContent);
  456. var bytes = parseFloat(m[1]);
  457. switch(m[2])
  458. {
  459. case "TB":
  460. bytes *= 1024;
  461. case "GB":
  462. bytes *= 1024;
  463. case "MB":
  464. bytes *= 1024;
  465. case "kB":
  466. bytes *= 1024;
  467. break;
  468. }
  469. //GM_log([num,icon,link,size,m[3],m[2]].join(", "));
  470. var crl = $X("//a[text()=\""+creator+"\" and @style]") || $X("//a[text()=\""+creator+"\"]");
  471. var cre = document.createElement("a")
  472. cre.appendChild(document.createTextNode(creator));
  473. //*
  474. if(crl)
  475. {
  476. cre.href = crl.getAttribute("href");
  477. //cre.className = crl.className;
  478. cre.className = (" "+crl.className+" ").replace(" user-hover "," user-no-hover ").trim();//hover doesn't work!
  479. if(crl.style.cssText)
  480. cre.style.cssText = crl.style.cssText;
  481. }
  482. //*/
  483. //unsafeWindow.console.log(crl)
  484. //unsafeWindow.console.log(cre)
  485. var simple_size = size.cloneNode(false);
  486. simple_size.appendChild(document.createTextNode(m[0]));
  487. obj = {
  488. number: [document.createTextNode(num), parseInt(num)],
  489. name:[link.cloneNode(true), name.toLowerCase()],
  490. size:[simple_size, bytes],
  491. date:[document.createTextNode(date), (new Date()) - nice_date],
  492. creator:[cre, creator.toLowerCase()],
  493. latest:[document.createTextNode(latest?"Latest":" "), latest],
  494. }
  495. if(icon)
  496. obj.type = [icon.cloneNode(true), $X("./img",icon).alt];
  497. return obj;
  498. }
  499.  
  500. function FileSetAttributes(file, obj)
  501. {
  502. for(var a in obj)
  503. file.setAttribute(a, obj[a][1]);
  504. if(typeof(obj.latest) !== "undefined")
  505. file.className = (file.className + (obj.latest[1]?" latest":" stale")).trim();
  506. }
  507.  
  508. function File(num, icon, link, size){
  509. obj = FileObj(num, icon, link, size);
  510. tr =document.createElement("tr");
  511.  
  512. tr.className="row";
  513. [obj.number, obj.type, obj.name, obj.size, obj.date, obj.creator, obj.latest].forEach(function (i){
  514. tr.appendChild(td = document.createElement("td"));
  515. td.appendChild(i[0]);
  516. })
  517. FileSetAttributes(tr, obj);
  518. return tr;
  519. }
  520.  
  521. function generic_list_click(ol, sorter){
  522. return function (event) {
  523. var type = parseInt(this.getAttribute("type"));
  524. var a_type = Math.abs(type);
  525.  
  526. var order = ol.getAttribute("order");
  527. if(order == "" || order == null)
  528. order = [1];
  529. else
  530. order = order.split(",").map(function(f){return parseInt(f);});
  531. var k;
  532. // GM_log("------------------------------------------------");
  533. if(Math.abs(order[0]) == a_type)
  534. order[0] = -order[0];
  535. else if(type == 1)
  536. order = [1];//numbers are unique a good opertunity to clear the list.
  537. else
  538. (order = order.filter( function(r){return (Math.abs(r) != a_type);})).unshift(type);
  539. ol.setAttribute("order", order.join(","));
  540. //we cheated <.<
  541. var rows = [];
  542. $Z("li", function(r,i){ rows[i]=r; }, ol);
  543. rows.sort(function(a, b){
  544. var i = 0, j = 0, neg = (order[0] < 0);
  545. //we don't bounds check this because every entry is unique.
  546. //if we overflow order than there is something SERIOUSLY wrong with the data.
  547. while(!(j = sorter(order[i], a, b)))
  548. {
  549. if(order[i] == null)
  550. break;
  551. neg ^= (order[++i] < 0);
  552. }
  553. return neg ? -j : j;
  554. });
  555. for(k = 0; k < rows.length; ++k)
  556. ol.appendChild(remove(rows[k]));
  557. // if(n = $X("li["+(k+2)+"]", ol))
  558. // if(rows[k].getAttribute("number") != n.getAttribute("number"))
  559. // insertAfter(rows[k], n);
  560. //[2,4,1,3] -> [1,2,3,4]
  561. //if(a != b)
  562. /*
  563. log({rows:rows.map(function(f){return f.getAttribute("number");}), order:order,
  564. "order-names":order.map(function(by){return tbody.rows[RS-1].cells[Math.abs(by) - 1].textContent;})});
  565. //*/
  566. delete rows;
  567. }
  568. }
  569.  
  570. function generic_click(RS, sorter, deshifter){
  571. return function (event) {
  572. var type = parseInt(this.getAttribute("type"));
  573. var a_type = Math.abs(type);
  574. var table = $X(".//ancestor::table[1]", this);
  575. var tbody = $X("tbody", table);
  576. var order = table.getAttribute("order");
  577. if(order == "" || order == null)
  578. order = [1];
  579. else
  580. order = order.split(",").map(function(f){return parseInt(f);});
  581. var k;
  582. // GM_log("------------------------------------------------");
  583. if(Math.abs(order[0]) == a_type)
  584. order[0] = -order[0];
  585. else if(type == 1)
  586. order = [1];//numbers are unique a good opertunity to clear the list.
  587. else
  588. (order = order.filter( function(r){return (Math.abs(r) != a_type);})).unshift(type);
  589. table.setAttribute("order", order.join(","));
  590. //we cheated <.<
  591. var rows = new Array(table.rows.length - RS);
  592. for(k = 0; k < rows.length; ++k)
  593. rows[k] = table.rows[k + RS];
  594. rows.sort(function(a, b){
  595. var i = 0, j = 0, neg = (order[0] < 0);
  596. //we don't bounds check this because every entry is unique.
  597. //if we overflow order than there is something SERIOUSLY wrong with the data.
  598. while(!(j = sorter(order[i], a, b)))
  599. {
  600. if(order[i] == null)
  601. break;
  602. neg ^= (order[++i] < 0);
  603. }
  604. return neg ? -j : j;
  605. });
  606. if(deshifter)
  607. {
  608. //keep the numbers in place
  609. if(rows[0] != table.rows[RS])
  610. swap(deshifter(rows[0]), deshifter(table.rows[RS]), "n[0]<->o[0]");
  611. if(k = (rows.length - 1))
  612. {
  613. if(rows[0] == table.rows[k+RS]) //new[0] == old[last]
  614. {
  615. if(rows[k] != table.rows[RS])
  616. swap(deshifter(rows[k]), deshifter(table.rows[RS]), "n[k]<->o[0]");
  617. }
  618. else
  619. {
  620. if(rows[k] != table.rows[k+RS])
  621. swap(deshifter(rows[k]), deshifter(table.rows[k+RS]), "n[k]<->o[k]");
  622. }
  623. }
  624. }
  625. for(k = 0; k < rows.length; ++k)
  626. if(rows[k] != table.rows[k+1])
  627. insertAfter(rows[k], table.rows[k+1]);
  628. /*
  629. log({rows:rows.map(function(f){return f.getAttribute("number");}), order:order,
  630. "order-names":order.map(function(by){return tbody.rows[RS-1].cells[Math.abs(by) - 1].textContent;})});
  631. //*/
  632. delete rows;
  633. }
  634. }
  635.  
  636. function CSSmulti(name, value){
  637. var m = name+": "+value+"; ";
  638. return ["-moz-", "-webkit-", "-ie-", ""].map(function(f){return f + m;}).join("");
  639. }
  640.  
  641. function L2(r, i, p){
  642. var summary = r.cells[1];
  643. var key = document.createElement("td");
  644. var priority = r.cells[2];
  645. var status = r.cells[3];
  646. insertBefore(key, summary);
  647. r.className = r.className.split(" ").concat("row").join(" ");
  648. r.setAttribute("number", i + 1);
  649. var link = $X(".//font/a", summary);
  650. var font = link.parentNode.cloneNode(false);
  651. font.appendChild(link);
  652. key.appendChild(font);
  653. key.className = "key"
  654.  
  655. summary.title = link.title && link.title.trim();
  656. var s = link.textContent.trim();
  657. var e = s.lastIndexOf("-") + 1;
  658. r.setAttribute("key", s.slice(0, e).concat(Array(9 + e - s.length).join("0"), s.slice(e)));
  659. var s = $X(".//img", r.cells[3]).title.split(" - ", 1)[0];
  660. var e = priorities.indexOf(s);
  661. if(e == null || e == -1) e = priorities.length;
  662. r.setAttribute("priority", e + 1);
  663. var s = $X(".//img", r.cells[4]).title.split(" - ", 1)[0];
  664. var e = statuses[s];
  665. if(e == null || e == -1) e = statuses.large;
  666. r.setAttribute("status", e);
  667. }
  668.  
  669. function log()
  670. {
  671. var arg;
  672. switch(arguments.length)
  673. {
  674. case 1:
  675. arg = arguments[0];
  676. break;
  677. case 0:
  678. arg = null;
  679. break;
  680. default:
  681. arg = arguments;
  682. break;
  683. }
  684. var f = JSON.stringify(arg);
  685. if(typeof(unsafeWindow.console) != "undefined" && typeof(unsafeWindow.console.log) != "undefined")
  686. unsafeWindow.console.log(f);
  687. else
  688. GM_log(f);
  689. return arg;
  690. }
  691.  
  692. //*/
  693.  
  694. function $X(_xpath, node){//to search in a frame, you must traverse it's .contentDocument attribute.
  695. var doc = (node)?(node.ownerDocument || node):(node = document);
  696. return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
  697. }
  698. function $Y(_xpath, node){
  699. var doc = (node)?(node.ownerDocument || node):(node = document);
  700. return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  701. }
  702. function $Z(_xpath, func, node){
  703. var doc = (node)?(node.ownerDocument || node):(node = document);
  704. var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  705. var args = Array.prototype.slice.call(arguments, 3);
  706. var i = 0;
  707. for (; i < res.snapshotLength; ++i)
  708. func.apply(func, [res.snapshotItem(i), i].concat(args));
  709. return i;
  710. }
  711.  
  712. function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);}
  713. function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);}
  714. function remove(r){return r.parentNode.removeChild(r);}
  715. function replace(old, New){return old.parentNode.replaceChild(New,old);}
  716. function swap(a, b, comment){
  717. if(a != b)
  718. {
  719. var c = a.nextSibling;
  720. var ap = a.parentNode;
  721. var bp = b.parentNode;
  722. bp.insertBefore(ap.removeChild(a), b);
  723. ap.insertBefore(bp.removeChild(b), c);
  724. // GM_log([comment, a.src?a.src:a, b.src?b.src:b].join("\n"));
  725. }
  726. }
  727. function findParent(node, parent){
  728. return $X(".//ancestor::"+parent+"[1]", node);
  729. }
  730.  
  731. function init(){
  732. if(typeof(String.prototype.trim) == "undefined")
  733. String.prototype.trim = function() {return this.replace(/^\s+|\s+$/g, "");}
  734. if(typeof(String.prototype.leftPad) == "undefined")
  735. String.prototype.leftPad = function (len, text) { return new Array(len - this.length + 1).join(text || '0') + this; }
  736. }
  737.  
  738. function JiraTimeStringToDate(str){
  739. return new Date(str.replace(/^(\d{2})\/([A-Z][a-z]{2})\/(\d{2})/, "$2 $1 20$3"));
  740. }