JIRA Enhanced Navigator

Makes the columns in tables resizable and remembers column width, additionally it allows the hiding of the filter box on the left.

  1. // ==UserScript==
  2. // @name JIRA Enhanced Navigator
  3. // @version 1.5
  4. // @description Makes the columns in tables resizable and remembers column width, additionally it allows the hiding of the filter box on the left.
  5. // @namespace http://home.comcast.net/~mailerdaemon
  6. // @include */ManageFilters.jspa*
  7. // @include */IssueNavigator.jspa*
  8. // @include */QuickSearch.jspa*
  9. // @include */BulkEditDetailsValidation.jspa*
  10. // @include */BulkEdit1!default.jspa*
  11. // @include */UserVotes!default.jspa*
  12. // @include */UserWatches!default.jspa*
  13. // @include */ViewUserIssueColumns!default.jspa*
  14. // @include */ViewSearchRequestIssueColumns!default.jspa*
  15. // ==/UserScript==
  16.  
  17. //enable resizing on the Column Order page
  18. const resize_on_ordering = false;
  19.  
  20. //size of the resize handle
  21. const div_width = 3;
  22.  
  23. if(!String.prototype.trim) String.prototype.trim = function() {return this.replace(/^\s+|\s+$/g,"");}
  24. function process(array, before, after, between){ return (before?before:"")+no_wrap.join((after?after:"") +(between?between:"")+ (before?before:""))+(after?after:""); }
  25.  
  26. var domain = document.location.host;
  27. var regex = /[\W\s \n\r]+/g;
  28.  
  29. var ColumnOrderPage = document.location.pathname.search(/\/View(User|SearchRequest)IssueColumns!default\.jspa/) > -1;
  30. if(ColumnOrderPage)
  31. {
  32. $Z("//table[@id='issuetable']//tr[following-sibling::tr[@class='rowAlternate']]/td/b", function(r,i,p){
  33. c = document.createElement("input")
  34. c.type = "checkbox";
  35. c.title = "Do not wrap long lines."
  36. var temp = r.textContent.replace(regex, "");
  37. c.id = temp?temp:i + "?";
  38. c.checked = GM_getValue(domain+".column."+c.id+".nowrap", false);
  39. insertBefore(c, r);
  40. addEvent(c, "click", function(event){GM_setValue(domain+".column."+this.id+".nowrap", this.checked)});
  41. });
  42. }
  43. if(resize_on_ordering || !ColumnOrderPage)
  44. {
  45. function stop(event){event.stopPropagation();}
  46.  
  47. function auto(event){
  48. // eb.style.marginLeft=eb.style.marginRight="";
  49. var link = GetParentNodeByTag(event.target, "td");
  50. var name = domain+".column."+link.id+".width";
  51. link.removeAttribute("width");
  52. base.className="resizing";
  53. var w = link.clientWidth-(pad+pad);
  54. link.width=(w<min_width)?min_width:w;
  55. GM_setValue(name, link.width);
  56. base.removeAttribute("class");
  57. // eb.style.marginLeft=eb.style.marginRight="auto";
  58. }
  59.  
  60. function down(event){
  61. addEvent(document, "mouseup", up);
  62. addEvent(document, "mousemove", movemouse);
  63. dobj = this;
  64. isdrag = true;
  65. x = event.pageX;
  66. w = parseInt(GetParentNodeByTag(dobj, "td").width+".0");
  67. // GM_log(new Array(x,w));
  68. return false;
  69. }
  70.  
  71. function up(event){
  72. if(isdrag)
  73. {
  74. isdrag=false;
  75. removeEvent(document, "mousemove", movemouse);
  76. removeEvent(document, "mouseup", up);
  77. var link = GetParentNodeByTag(dobj, "td");
  78. var name = domain+".column."+link.id+".width";
  79. // GM_log(name);
  80. GM_setValue( name, link.width);
  81. }
  82. }
  83.  
  84. function movemouse(event){
  85. if (isdrag)
  86. {
  87. var p = event.pageX;
  88. var k = w + p - x;
  89. GetParentNodeByTag(dobj, "td").width = k<min_width?min_width:k;
  90. // GM_log(new Array(w, p, x, k))
  91. return false;
  92. }
  93. }
  94.  
  95. function toggle(set){
  96. if(header.style.display == "none" && set != false)
  97. {
  98. header.style.display = "";
  99. GM_setValue(domain+".filter."+header.className+".visible", true)
  100. }
  101. else if(set != true)
  102. {
  103. header.style.display = "none";
  104. GM_setValue(domain+".filter."+header.className+".visible", false)
  105. }
  106. }
  107. var no_wrap = [];
  108.  
  109. var res = $Y("//table[@id='issuetable']/tbody/tr[position()=1 and not(@class)]/td");
  110.  
  111. var len = res.snapshotLength;
  112. if(len <= 0) return;
  113. var first = res.snapshotItem(0);
  114. var row = GetParentNodeByTag(first, "tr")
  115. var base = GetParentNodeByTag(row, "table");
  116. var space=parseInt(base.cellSpacing+".0");
  117. var pad = parseInt(base.cellPadding+".0");
  118. var height = (first.height = (base.rows[0].clientHeight - space)) - (pad + pad);// + space + space + pad + pad;
  119. var eb = GetParentNodeByTag(base, "table", base);
  120. //eb.style.backgroundColor="transparent";
  121. eb.removeAttribute("width");
  122. eb.style.marginLeft="20px;"//eb.style.marginRight="auto";
  123.  
  124. var div_center_offset = (pad /*+ Math.floor(div_width / 2)*/ + space);
  125. var min_width = (ColumnOrderPage?56:div_width - space + 1);
  126. const etype = "div";
  127.  
  128. for (i = 0; link = res.snapshotItem(i); ++i)
  129. {
  130. link.style.whiteSpace="nowrap";
  131. link.style.minWidth=min_width+"px";
  132.  
  133. var text = link;
  134. var u = link.getElementsByTagName("span");
  135. if(u && u[0])
  136. {
  137. text = link.removeChild(u[0]);
  138. link.title = text.title;
  139. }
  140. else
  141. {//for BulkEdit1
  142. if(u = getFirstNonTextChild(link))
  143. {
  144. text = document.createElement("span");
  145. text.appendChild(link.removeChild(u));
  146. }
  147. }
  148. var temp = text.textContent.replace(regex, "");
  149. link.id = temp?temp:i + "?";
  150. if(GM_getValue(domain+".column."+link.id+".nowrap", false))
  151. no_wrap.push($X("//table[@id='issuetable']/tbody/tr[preceding-sibling::tr[position()=1 and not(@class)]]/td["+(i+1)+"]").className.replace("nav ",""));
  152. var width = parseInt(GM_getValue(domain+".column."+link.id+".width", "0"));
  153. // GM_log(width);
  154. if(width == 0 || isNaN(width))
  155. width = link.clientWidth - (pad + pad);
  156. if(width < min_width && width >= 0)
  157. width = min_width;
  158. var burn = document.createElement(etype);
  159. var divider = document.createElement(etype);
  160. burn.className="GM_burn";
  161. var t = link.childNodes.length;
  162. while(t > 0)
  163. {
  164. var m = link.removeChild(link.childNodes[--t]);
  165. if(m.nodeName != "#text")
  166. burn.appendChild(m);
  167. }
  168. if(burn.childNodes.length > 0)
  169. {
  170. link.appendChild(divider);
  171. link.appendChild(burn);
  172. }
  173. else
  174. link.appendChild(divider = burn);
  175. if(link != text)
  176. link.appendChild(text);
  177. divider.title="Click and drag to resize this column";
  178. divider.className="GM_divider";
  179. // addEvent(floater, "mouseup", up);
  180. addEvent(divider, "mousedown", down);
  181. addEvent(divider, "click", stop);
  182. addEvent(divider, "dblclick", auto);
  183. link.width=Math.abs(width) + (width>=0?"":"%");
  184. }
  185.  
  186. GM_addStyle(process(no_wrap, "#issuetable .", " {white-space:nowrap;} "));
  187. //GM_log(no_wrap.join(","));
  188. var tx;
  189. var ty;
  190. var x;
  191. var w;
  192. var dobj;
  193.  
  194. GM_addStyle(".GM_divider {background-color:black; cursor:col-resize; height:"+height+"px; opacity:0.125; width:"+div_width+"px;} .GM_burn, .GM_divider {float:right; left:"+div_center_offset+"px; position:relative;}")
  195. GM_addStyle("table.resizing {table-layout:auto!important; overflow:auto!important;} table.resizing tbody tr td.colHeaderLink *, table.resizing tbody tr td.colHeaderOver * {display:none;}" );
  196. GM_addStyle("table#issuetable tbody tr td {overflow:hidden!important;} table#issuetable {table-layout:fixed; overflow:hidden; width:0px;}");
  197. GM_addStyle("table#issuetable tbody tr td:hover {overflow:visible!important;} table#issuetable tbody tr td:hover + td {opacity:0.2;} .faded {opacity:0.1!important;}");
  198. GM_addStyle("td.colHeaderOver {color:black;} td.colHeaderOver:hover + td + td {opacity:0.4;}");
  199.  
  200. var header = $X("//tr[not(boolean(@class))]/td[@class='filterSummaryCell' or @class='filterFormCell' or (not(@class) and div[@class='vcard'])]");
  201.  
  202. if(header)
  203. {
  204. var div = document.createElement("td");
  205. div.style.background = "#9DBBDA";
  206. div.style.verticalAlign = "middle";
  207. div.style.cursor="pointer";
  208. text = document.createElement("div");
  209. text.style.width = "5px";
  210. div.appendChild(text);
  211. insertAfter(div, header);
  212. addEvent( div, "click", toggle );
  213. if(!header.className)
  214. if(new_name = document.URL.replace(/.*\/([^!]+)\!default\.jspa.*/, "$1"))
  215. header.className = "URL-" + new_name;
  216. toggle(GM_getValue(domain+".filter."+header.className+".visible", true));
  217. div.title="Click to toggle the visiblity of the filters";
  218. }
  219.  
  220. $Z("//td[contains(@class, 'assignee') and contains(text(), 'Unassigned')]", function(r, i, p){r.className += " faded"}, base);
  221. }
  222.  
  223. function $X(_xpath, node){return document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);}
  224. function $Y(_xpath, node){return document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);}
  225. function $Z(_xpath, func, node, payload){
  226. var res = document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  227. var i, j;
  228. for (i = j = 0; link = res.snapshotItem(i); ++i)
  229. j += func(link, i, payload);
  230. return j;
  231. }
  232. function GetParentNodeByTag(child, tag, bad) {
  233. tag = tag.toUpperCase();
  234. while((child = child.parentNode) && child.tagName != tag);
  235. return child?child:bad;
  236. }
  237. function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);}
  238. function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);}
  239.  
  240. function addEvent( obj, type, fn, capture ) {
  241. if ( obj.attachEvent ) {
  242. obj["e"+type+fn] = fn;
  243. obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
  244. obj.attachEvent( "on"+type, obj[type+fn] );
  245. } else
  246. obj.addEventListener( type, fn, capture?capture:false );
  247. }
  248. function removeEvent( obj, type, fn, capture ) {
  249. if ( obj.detachEvent ) {
  250. obj.detachEvent( "on"+type, obj[type+fn] );
  251. obj[type+fn] = obj["e"+type+fn] = null;
  252. } else
  253. obj.removeEventListener( type, fn, capture?capture:false );
  254. }
  255. function getFirstNonTextChild(obj){
  256. if(obj.firstChild.nodeName != "#text") return obj.firstChild;
  257. return getNextNonTextSibling(obj.firstChild);
  258. }
  259. function getNextNonTextSibling(obj){
  260. while((obj=obj.nextSibling) && (obj.nodeName == "#text"));
  261. return obj;
  262. }