There are 16 previous versions of this script.
// ==UserScript==
// @name Jira Comment Highlighter
// @namespace http://home.comcast.net/~mailerdaemon
// @include *jira*/browse/*-*
// @version 2.0
// ==/UserScript==
const CollapsedAtStart = true;
const AssigneeColor = "#FFCCFF";
const ReporterColor = "#CCFFFF";
const AdminColor = "#FFFFCC";
const ReporterIsAssigneeColor = "#E4E4FF";
const MeColor = "#E4FFE4";
const CriticalColor = "rgba(255, 0, 0, 0.6)";
const InterestingColor = "rgba(96, 96, 256, 0.5)";
const CollapsableColor = "rgba(255, 140, 64, 0.45)";
const CommentColor = "orange";
const ChangeColor = "violet";
const domains = {
"jira.secondlife.com": {
admins: "contains(text(), ' Linden') or contains(text(), ' linden') or text()='lindenrobot'",
interest: ["Linden Lab Issue ID"],
},
"jira.atlassian.com": {
admins: "text()='Support Count Updater' or contains(text(), '[Atlassian]') or contains(text(), '[JIRA Product Manager]')",
},
get: function(name, default_value){
if((a = this[document.location.host]) && (b = a[name]))
return b;
return default_value;
},
getArrayWith: function(name, array){
if((a = this[document.location.host]) && (b = a[name]))
return (array === null)?b:Array.concat(b, array);
return array;
},
};
const interest = ["Priority", "Key", "Assignee", "Issue Type", "Type"];
const critical = ["Status", "Resolution", "Parent"];
const comment = ["Comment"]
const collapse = ["Description", "Comment"];
const CombinedDiff = ["Description"]
const SideBySideDiff = ["Summary", "Environment"];
const Debug_diffString = 0;
//collapses the extra whitespace
GM_addStyle([
"div#issue_actions_container > br{ line-height:0px;}",
"div.action-body, div.action-body p, div.action-body ul, div.action-body ol, div.action-body .panel { margin-bottom: 0;}",
"div.action-links { background-color: transparent; padding-bottom: 4px;}",
"div.action-details, div[id^=\"comment-\"][id$=\"-closed\"] > div.actionContainer > div.action-links { background-color: rgba(0, 0, 0, 0.05);}",
"div#changehistory td { background-color: transparent;} div#changehistory td[bgcolor='#dddddd'] { background-color: rgba(0, 0, 0, 0.13);}",
".collapsable .collapse {width:100%; overflow-y:auto; min-height:1.3em;} .collapsable .collapse[style*=\"height: 0px;\"]:hover { height:auto!important; }",
".collapsable .sizer {cursor: s-resize; height: 4px; width:100%; background-color: rgba(255, 140, 64, 0.25); -moz-border-radius-topleft:3px;}",//rgba(0, 0, 0, 0.1)
//".collapsable {background-color: "+CollapsableColor+";}",
//must overwride div#changehistory td
".collapsable > td:first-child {padding:0 5px 5px 3px; }",
".collapsable > td:first-child b {background-color: "+CollapsableColor+" !important; -moz-user-select: none; padding: 0 2px; -moz-border-radius: 6px; margin-right:0.4ch; }",
".diffed { padding:0!important; }",
".diffed del.diff, .diffed-left del.diff { background-color: rgba(255, 0, 0, 0.3); text-decoration:none;}",
".diffed ins.diff, .diffed-right ins.diff { background-color: rgba(0, 255, 0, 0.3); text-decoration:none;}",
".diffed-right del.diff, .diffed-left ins.diff {display:none;}",
".diffed .diff span.beforebr:before { content: \"\u21B2\"; opacity: 0.33;} ",//\u21B2 ↲
"div#issue_actions_container .admin { background-color:"+AdminColor+"!important; }",
"div#issue_actions_container .reporter { background-color:"+ReporterColor+"!important; }",
"div#issue_actions_container .assignee { background-color:"+AssigneeColor+"!important; }",
"div#issue_actions_container .reporter.assignee { background-color:"+ReporterIsAssigneeColor+"!important; }",
"div#issue_actions_container .me { background-color:"+MeColor+"!important; }",
"div#issue_actions_container div#changehistory tr.critical { background-color:"+CriticalColor+"; }",
"div#issue_actions_container div#changehistory tr.interest { background-color:"+InterestingColor+"; }",
/*
"div#issue_actions_container .actionContainer { border-width: 0px 2px; border-style: solid; }",
"div#issue_actions_container .actionContainer { border-color: "+ChangeColor+"; }",
"div#issue_actions_container > div > .actionContainer { border-color: "+CommentColor+"; }",
*/
].join("\n\n"));
init();
const before = 'text()="';
const after = '"';
var Reporter = find("Reporter","/a");
var base = $X("//div[@id='issue_actions_container']");
if(Reporter && base)
{
//*
if(admins = domains.get("admins"))
run(admins, "admin", base);
var Assignee = find("Assignee","/a");
var Me = $X("//a[starts-with(@id,'user_nav_bar_')]");
if(Me) Me = Me.textContent;
if(Assignee && Reporter.textContent == Assignee.textContent)
{
if((!Me || Me != Assignee.textContent) && 0 < run(before+Assignee.textContent+after, "reporter assignee", base))
Assignee.style.backgroundColor = Reporter.style.backgroundColor = ReporterIsAssigneeColor;
}
else
{
if((!Me || Me.textContent != Reporter.textContent) && 0 < run(before+Reporter.textContent+after, "reporter", base))
Reporter.style.backgroundColor = ReporterColor;
if(Assignee && (!Me || Me != Assignee.textContent) && 0 < run(before+Assignee.textContent+after, "assignee", base))
Assignee.style.backgroundColor = AssigneeColor;
}
if(Me && 0 < run(before+Me+after, "me" , base))
{
if(Me == Reporter.textContent)
Reporter.style.backgroundColor = MeColor;
if(Assignee && Me == Assignee.textContent)
Assignee.style.backgroundColor = MeColor;
}
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("interest", interest), "')", "]]]",
function(r,i,p){r.className = (r.className + " interest").trim(); return 1;}, base);
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("critical", critical), "')", "]]]",
function(r,i,p){r.className = (r.className + " critical").trim(); return 1;}, base);//*/
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("comment", comment), "')", "]]]",
function(r,i,p){
r.className = (r.className + " comment").trim();
var td = r.cells[1];
td.innerHTML=td.textContent.replace(/^\s*\[\s*/g,"").replace(/\s*\]\s*$/g,"");
});//*/
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("CombinedDiff", CombinedDiff).concat(domains.getArrayWith("comment", comment)), "')", "]]]",
function(r,i,p){
// debug("CombinedDiff", r);
r.className = (r.className + " diff-combined").trim();
var c = diffString(r.cells[1], r.cells[2]);
td = r.cells[2];
td.className="diffed";
td.colSpan="2";
td.width="80%";
remove(r.cells[1]);
td.innerHTML = c;
return diffCleanup(td);
}, base);
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("SideBySideDiff", SideBySideDiff), "')", "]]]",
function(r,i,p){
// debug("SideBySideDiff", r);
r.className = (r.className + " diff-sidebyside").trim();
var c = diffString(r.cells[1], r.cells[2]);
r.cells[1].className="diffed-left";
r.cells[2].className="diffed-right";
r.cells[1].innerHTML = r.cells[2].innerHTML = c;
return diffCleanup(r.cells[1]);
}, base);
//*
go(".//table[starts-with(@id,'changehistory_')]//tr[td[position()=1 and b[", "contains(text(),'", domains.getArrayWith("collapse", collapse), "')", "]]]",
function(r,i,p){
//TODO disable when initial.scrollHeight < window.getComputedStyle(.collapse).height
r.className = (r.className + " collapsable").trim();
var td = document.createElement("td"); td.colSpan="2"; td.width="80%"; td.style.paddingBottom=td.style.paddingRight="0";
var div = document.createElement("div"); div.className="collapse";
td.appendChild(div);
while(r.cells[1])
div.appendChild(r.cells[1]);
sizer = document.createElement("div"); sizer.className="sizer";
td.appendChild(sizer);
r.appendChild(td);
addEvent(sizer, "mousedown", down);
addEvent(sizer, "click", stop);
addEvent(sizer, "dblclick", auto);
addEvent(r.cells[0], "dblclick", auto);
var b = $X("./b", r.cells[0]);
b.innerHTML = b.innerHTML.trim();
//addEvent(div, "dblclick", auto);
if(CollapsedAtStart)
auto({originalTarget:sizer}, true);
return 1;
}, base);//*/
}
function debug(t, r)
{
var u = $X("./div[@class='action-details']/a[@id and @href]",GetParentNodeBy(r, function(n){return n.className.split(" ").indexOf("actionContainer") != -1;})).textContent.trim();
var n = $X("./b", r.cells[0]).textContent.trim();
return log({type:t, user:u, name:n});
}
//function GetNextSiblingNode(child, bad) {while((child = child.nextSibling) && child.nodeType != 1);return child?child:bad;}
function find(feild, extra){return $X("//table[@id='issuedetails']/tbody/tr[td[position()=1 and b[text()='"+feild+":']]]/td[2]" + extra );}
function $X(_xpath, node){return document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);}
function $Y(_xpath, node){return document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);}
function $Z(_xpath, func, node, payload){
// log(_xpath);
var res = document.evaluate(_xpath, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var i, j;
for (i = j = 0; link = res.snapshotItem(i); ++i)
j += func(link, i, payload);
return j;
}
function merge(group, b, a) {
return b+group.join(a+" or "+b)+a;
}
function diffCleanup(td) {
if(document.evaluate("./del or ./ins", td, null, XPathResult.BOOLEAN_TYPE , null).booleanValue)
return 1;
var tr = GetParentNodeByTag(td, "tr");
var tbody = GetParentNodeByTag(td, "tbody");
tr.parentNode.removeChild(tr);
if(tbody.rows.length <= 0)
if(d = GetParentNodeBy(tbody, function(r){ return r.tagName == "div" && r.className == "actionContainer"; }))
d.parentNode.removeChild(d);
return 0;
}
function run(group, payload, base) {
if(group && typeof(group) == "object" && group.length)
{
if(group.length > 0)
group = merge(group, before, after);
else
group = null;
}
return group?$Z('.//div[starts-with(@class,"actionContainer") and div[starts-with(@class,"action-details") and a['+ group +']]]',
function(r,i,p){r.className = (r.className + " "+payload).trim(); return 1;}, base):0;
}
function go(left, mleft, array, mright, right, func, base) {
//*
if(array && array.length > 0)
$Z(left+merge(array, mleft, mright)+right, func);
/*/ // I don't remember what the purpouse of this choice.
if(array && array.length && array.length > 0)
$Z(left+merge(array, mleft, mright)+right, function(r,i,p){ if(i == 1) return func(r,i,p); return 0;});
//*/
}
function log()
{
var arg;
switch(arguments.length)
{
case 1:
arg = arguments[0];
break;
case 0:
arg = null;
break;
default:
arg = arguments;
break;
}
var f = JSON.stringify(arg);
if(typeof(unsafeWindow.console) != "undefined" && typeof(unsafeWindow.console.log) != "undefined")
unsafeWindow.console.log(f);
else
GM_log(f);
return arg;
}
function remove(node) {
return node.parentNode.removeChild(node);
}
function outerHTML(node) {
var attrs = node.attributes;
var str = "<" + node.tagName;
for (var i = 0; i < attrs.length; i++)
str += " " + attrs[i].name + "=\"" + attrs[i].value.escapeHTML() + "\"";
return str + (node.innerHTML?">" + node.innerHTML + "</" + node.tagName + ">":"/>");
}
function pairs(str) {
str = str.replace(/ /g, "\u00A0").escapeHTML();
var resplit = /([\\\/!=.,{}]+|[^\s\u2060(){}@%\^\*~|+\-:;\[\]\\\/!=.,]*(?:[()\[\]]+|[@%\^\*~|+\-]+|[:;]+)?)([\s\u2060]*)/y;
resplit.lastIndex = 0;
var text = [], whitespace = [], m;
while((m = resplit.exec(str)) && m[0] != "")
{
text = text.concat(m[1]);
whitespace = whitespace.concat(m[2]);
}
var out = { characters: text, spaces: whitespace, string:str, lastmatch:m};
if(str != "" && text == []) unsafeWindow.console.log(out);
return text.length > 0 ?out:null;
}
function split(node, p) {
if(typeof(node) == "string"){
return pairs(node);
}
else
{
var i = 0;
var os = { type: p, characters: [], spaces: [] };
for(e = node.lastChild, d = c = node.firstChild; c; c = c.nextSibling)
{
if(c.nodeType == c.TEXT_NODE)
{
var a = c.nodeValue;
var f = (c == d);
var g = (c == e);
if(f && g)
a = a.replace(/^\s*|\s*$/g,"");
else if(f)
a = a.replace(/^\s*/,"");
else if(g)
a = a.replace(/\s*$/,"");
var t = pairs(a);
if(t)
{
os.characters = os.characters.concat(t.characters);
os.spaces = os.spaces.concat(t.spaces);
}
}
else if(c.nodeType == c.ELEMENT_NODE)
{
if(c.nodeName == "BR")
{
os.characters = os.characters.concat("");
os.spaces = os.spaces.concat("<span class='beforebr'></span><br/>");
}
else
{
os.characters = os.characters.concat(outerHTML(c));
os.spaces = os.spaces.concat("");
}
}
else
unsafeWindow.console.log("Unknown node type: " + c.nodeType);
}
return os;
}
}
function init(){
String.prototype.escapeHTML = function () { return this.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<').replace(/"/g,'"'); };
if(!String.prototype.trim)
String.prototype.trim = function trim(){return this.replace(/^\s*|\s*$/g,"");}
Buffer.prototype.toString = function (){return this.running?this.str + "</"+this.running+">":this.str;};
Buffer.prototype.append = function (tag, text) {
if(text != "" || (Debug_diffString & 1))
{
var tagging = "";
if(this.running != tag || (Debug_diffString & 2))
{
if(this.running) tagging += "</"+this.running+">";
if(this.running = tag) tagging += "<"+this.running+" class='diff'>";
}
this.str += tagging + text;
}
return this;
};
}
function Buffer() { this.running = ""; this.str=""; }
//{
/*
* Javascript Diff Algorithm
* By John Resig (http://ejohn.org/)
* Modified by Chu Alan "sprite"
*
* Released under the MIT license.
*
* More Info:
* http://ejohn.org/projects/javascript-diff-algorithm/
*/
//The diff alg is ok but the diffString is a pain in the butt.
//these functions have been tweaked and rewritten for this context.
function diffString( o, n ) {
var buffer = new Buffer();
var os = split(o, "old");
var ns = split(n, "new");
var out = diff(os.characters, ns.characters);
if (out.n.length == 0) {
for(i = 0; i < out.o.length; i++)
buffer.append("del", out.o[i] + os.spaces[i]);
} else {
if (out.n[0].text == null)
for (n = 0; n < out.o.length && out.o[n].text == null; n++)
buffer.append("del", out.o[n] + os.spaces[n]);
for ( var i = 0; i < out.n.length; i++ ) {
if(out.n[i].text == null)
buffer.append("ins", out.n[i] + ns.spaces[i]);
else
{
buffer.append("", out.n[i].text);
n = out.n[i].row;
if(ns.spaces[i] != os.spaces[n])
buffer.append("ins", ns.spaces[i]).append("del", os.spaces[n]);
else
buffer.append("", ns.spaces[i]);
for(n++; n < out.o.length && out.o[n].text == null; n++)
buffer.append("del", out.o[n] + os.spaces[n]);
}
}
}
return buffer.toString();
}
function diff( o, n ) {
var ns = new Object();
var os = new Object();
for ( var i = 0; i < n.length; i++ ) {
var name = n[i];
var t = ns[ name ];
if ( t == null || t.rows == null )
ns[ name ] = t = { rows: new Array(), o: null };
t.rows.push( i );
}
for ( var i = 0; i < o.length; i++ ) {
var name = o[i];
var t = os[ name ];
if ( t == null || t.rows == null )
os[ name ] = t = { rows: new Array(), n: null };
t.rows.push( i );
}
for ( var i in ns ) {
if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows != null && os[i].rows.length == 1 ) {
n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
}
}
for ( var i = 0; i < n.length - 1; i++ ) {
if ( n[i].text != null && n[i+1].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
n[i+1] == o[ n[i].row + 1 ] ) {
n[i+1] = { text: n[i+1], row: n[i].row + 1 };
o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
}
}
for ( var i = n.length - 1; i > 0; i-- ) {
if ( n[i].text != null && n[i-1].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
n[i-1] == o[ n[i].row - 1 ] ) {
n[i-1] = { text: n[i-1], row: n[i].row - 1 };
o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
}
}
return { o: o, n: n };
}
//}
//{
function stop(event){
if (event)
{
if (typeof event.stopPropagation == "function")
event.stopPropagation();
if (typeof event.preventDefault == "function")
event.preventDefault();
}
return false;
}
function down(event){
addEvent(document, "mouseup", up);
addEvent(document, "mousemove", movemouse);
var e = event.originalTarget;
var t = $X("./../div[@class='collapse']", e);
var h = parseInt(unsafeWindow.getComputedStyle(t, null).height.replace(/^(\d+)(?:\.\d*)?px$/, "$1"));
startdrag = {y: event.pageY, h: h, t: t};
return stop(event);
}
function up(event){
removeEvent(document, "mousemove", movemouse);
removeEvent(document, "mouseup", up);
if(startdrag)
{
startdrag=null;
return stop(event);
}
}
function movemouse(event){
if (startdrag)
{
var p = event.pageY + startdrag.h - startdrag.y;
startdrag.t.style.height = (p<0?0:p) + "px";
return stop(event);
}
}
function auto(event, trick){
var link = $X(".//div[@class='collapse']", tr = GetParentNodeByTag(event.originalTarget, "TR"));
if(link.style.height != "")
{
link.style.height = "";
}
else
{
var pt = link;
addEvent(pt, "click", function poke() { removeEvent(pt, "click", poke); if(link.style.height == "1px" || link.style.height == "0px") link.style.height = "";});
if(trick == true)
{
link.style.height = "0px";
}
else
{
link.style.height = "1px";
addEvent(link, "mouseout", function out(){ removeEvent(link, "mouseout", out); if(link.style.height == "1px") link.style.height = "0px"; });
}
}
if(trick != true)
return stop(event);
}
function addEvent( obj, type, fn, capture ) {
obj.addEventListener( type, fn, capture?true:false );
}
function removeEvent( obj, type, fn, capture ) {
obj.removeEventListener( type, fn, capture?true:false );
}
function GetParentNodeByTag(child, tag, bad) {
tag = tag.toUpperCase();
while((child = child.parentNode) && child.tagName != tag);
return child?child:bad;
}
function GetParentNodeBy(child, func, bad) {
var i = 0;
while((child = child.parentNode) && !func(child, i)) i++;
return child?child:bad;
}
//}
