Skip to content

Instantly share code, notes, and snippets.

@locofocos
Last active August 21, 2020 21:59
Show Gist options
  • Save locofocos/bd791a6fa5fb70428e3efb9fdc2f6061 to your computer and use it in GitHub Desktop.
Save locofocos/bd791a6fa5fb70428e3efb9fdc2f6061 to your computer and use it in GitHub Desktop.
Jira comment history diff

This is a snippet of JS code that generates a diff on the Jira comment history tab. Currently, Jira generates a nice red/green diff when it sends you an email notification, but it only offers a before/after view of changes on the Jira website itself. Running this JS code will show a similar red/green diff on the Jira website.

Before: image

After: image

Instructions (Chrome):

If you want the full code, see full code.js. I ran that through https://javascript-minifier.com/ to generate bookmarklet.js. Note that it has some lines at the bottom that you can uncomment if you use a dark theme (like https://darkreader.org/).

javascript:function escape(e){var t=e;return t=(t=(t=(t=t.replace(/&/g,"&amp;")).replace(/</g,"&lt;")).replace(/>/g,"&gt;")).replace(/"/g,"&quot;")}function diffString(e,t){e=e.replace(/\s+$/,""),t=t.replace(/\s+$/,"");var n=diff(""==e?[]:e.split(/\s+/),""==t?[]:t.split(/\s+/)),r="",l=e.match(/\s+/g);null==l?l=["\n"]:l.push("\n");var o=t.match(/\s+/g);if(null==o?o=["\n"]:o.push("\n"),0==n.n.length)for(var a=0;a<n.o.length;a++)r+="<del>"+escape(n.o[a])+l[a]+"</del>";else{if(null==n.n[0].text)for(t=0;t<n.o.length&&null==n.o[t].text;t++)r+="<del>"+escape(n.o[t])+l[t]+"</del>";for(a=0;a<n.n.length;a++)if(null==n.n[a].text)r+="<ins>"+escape(n.n[a])+o[a]+"</ins>";else{var s="";for(t=n.n[a].row+1;t<n.o.length&&null==n.o[t].text;t++)s+="<del>"+escape(n.o[t])+l[t]+"</del>";r+=" "+n.n[a].text+o[a]+s}}return r}function randomColor(){return"rgb("+100*Math.random()+"%, "+100*Math.random()+"%, "+100*Math.random()+"%)"}function diffString2(e,t){e=e.replace(/\s+$/,""),t=t.replace(/\s+$/,"");var n=diff(""==e?[]:e.split(/\s+/),""==t?[]:t.split(/\s+/)),r=e.match(/\s+/g);null==r?r=["\n"]:r.push("\n");var l=t.match(/\s+/g);null==l?l=["\n"]:l.push("\n");for(var o="",a=new Array,s=0;s<n.o.length;s++)a[s]=randomColor(),null!=n.o[s].text?o+='<span style="background-color: '+a[s]+'">'+escape(n.o[s].text)+r[s]+"</span>":o+="<del>"+escape(n.o[s])+r[s]+"</del>";var i="";for(s=0;s<n.n.length;s++)null!=n.n[s].text?i+='<span style="background-color: '+a[n.n[s].row]+'">'+escape(n.n[s].text)+l[s]+"</span>":i+="<ins>"+escape(n.n[s])+l[s]+"</ins>";return{o:o,n:i}}function diff(e,t){for(var n=new Object,r=new Object,l=0;l<t.length;l++)null==n[t[l]]&&(n[t[l]]={rows:new Array,o:null}),n[t[l]].rows.push(l);for(l=0;l<e.length;l++)null==r[e[l]]&&(r[e[l]]={rows:new Array,n:null}),r[e[l]].rows.push(l);for(var l in n)1==n[l].rows.length&&void 0!==r[l]&&1==r[l].rows.length&&(t[n[l].rows[0]]={text:t[n[l].rows[0]],row:r[l].rows[0]},e[r[l].rows[0]]={text:e[r[l].rows[0]],row:n[l].rows[0]});for(l=0;l<t.length-1;l++)null!=t[l].text&&null==t[l+1].text&&t[l].row+1<e.length&&null==e[t[l].row+1].text&&t[l+1]==e[t[l].row+1]&&(t[l+1]={text:t[l+1],row:t[l].row+1},e[t[l].row+1]={text:e[t[l].row+1],row:l+1});for(l=t.length-1;l>0;l--)null!=t[l].text&&null==t[l-1].text&&t[l].row>0&&null==e[t[l].row-1].text&&t[l-1]==e[t[l].row-1]&&(t[l-1]={text:t[l-1],row:t[l].row-1},e[t[l].row-1]={text:e[t[l].row-1],row:l-1});return{o:e,n:t}}function createCssStyle(e){var t=document.createElement("style");t.type="text/css",t.innerHTML=e,document.getElementsByTagName("head")[0].appendChild(t)}for(var parentDivs=document.querySelectorAll('div[id^="changehistory"].issue-data-block'),i=0;i<parentDivs.length;i++){var parentDiv=parentDivs[i],changeHistory=parentDiv.querySelector(".changehistory");if(null!=changeHistory.querySelector(".activity-old-val")){var fieldName=changeHistory.querySelector(".activity-name").textContent,oldVal=changeHistory.querySelector(".activity-old-val").textContent,newVal=changeHistory.querySelector(".activity-new-val").textContent,diffDiv=parentDiv.querySelector(".changehistory.action-body");diffDiv.innerHTML="<strong>"+fieldName+"</strong><br /><br />"+diffString(oldVal,newVal).replace(/\n\n/g,"<br />")}}createCssStyle("ins { background-color: #BFB; }"),createCssStyle("del { background-color: #FAA; }");
// Display Jira history changes as colored diffs.
// Paste into the dev console or turn into a bookmarklet.
//From https://johnresig.com/files/jsdiff.js
/*
* 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/
*/
function escape(s) {
var n = s;
n = n.replace(/&/g, "&amp;");
n = n.replace(/</g, "&lt;");
n = n.replace(/>/g, "&gt;");
n = n.replace(/"/g, "&quot;");
return n;
}
function diffString( o, n ) {
o = o.replace(/\s+$/, '');
n = n.replace(/\s+$/, '');
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
var str = "";
var oSpace = o.match(/\s+/g);
if (oSpace == null) {
oSpace = ["\n"];
} else {
oSpace.push("\n");
}
var nSpace = n.match(/\s+/g);
if (nSpace == null) {
nSpace = ["\n"];
} else {
nSpace.push("\n");
}
if (out.n.length == 0) {
for (var i = 0; i < out.o.length; i++) {
str += '<del>' + escape(out.o[i]) + oSpace[i] + "</del>";
}
} else {
if (out.n[0].text == null) {
for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
str += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
}
}
for ( var i = 0; i < out.n.length; i++ ) {
if (out.n[i].text == null) {
str += '<ins>' + escape(out.n[i]) + nSpace[i] + "</ins>";
} else {
var pre = "";
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
pre += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
}
str += " " + out.n[i].text + nSpace[i] + pre;
}
}
}
return str;
}
function randomColor() {
return "rgb(" + (Math.random() * 100) + "%, " +
(Math.random() * 100) + "%, " +
(Math.random() * 100) + "%)";
}
function diffString2( o, n ) {
o = o.replace(/\s+$/, '');
n = n.replace(/\s+$/, '');
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
var oSpace = o.match(/\s+/g);
if (oSpace == null) {
oSpace = ["\n"];
} else {
oSpace.push("\n");
}
var nSpace = n.match(/\s+/g);
if (nSpace == null) {
nSpace = ["\n"];
} else {
nSpace.push("\n");
}
var os = "";
var colors = new Array();
for (var i = 0; i < out.o.length; i++) {
colors[i] = randomColor();
if (out.o[i].text != null) {
os += '<span style="background-color: ' +colors[i]+ '">' +
escape(out.o[i].text) + oSpace[i] + "</span>";
} else {
os += "<del>" + escape(out.o[i]) + oSpace[i] + "</del>";
}
}
var ns = "";
for (var i = 0; i < out.n.length; i++) {
if (out.n[i].text != null) {
ns += '<span style="background-color: ' +colors[out.n[i].row]+ '">' +
escape(out.n[i].text) + nSpace[i] + "</span>";
} else {
ns += "<ins>" + escape(out.n[i]) + nSpace[i] + "</ins>";
}
}
return { o : os , n : ns };
}
function diff( o, n ) {
var ns = new Object();
var os = new Object();
for ( var i = 0; i < n.length; i++ ) {
if ( ns[ n[i] ] == null )
ns[ n[i] ] = { rows: new Array(), o: null };
ns[ n[i] ].rows.push( i );
}
for ( var i = 0; i < o.length; i++ ) {
if ( os[ o[i] ] == null )
os[ o[i] ] = { rows: new Array(), n: null };
os[ o[i] ].rows.push( i );
}
for ( var i in ns ) {
if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && 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 };
}
/* -------------------------------
end https://johnresig.com/files/jsdiff.js
------------------------------- */
/* styleText like '.cssClass { color: #F00; }'
from https://stackoverflow.com/a/1720483/4176104 */
function createCssStyle(styleText){
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = styleText;
document.getElementsByTagName('head')[0].appendChild(style);
}
var parentDivs = document.querySelectorAll('div[id^="changehistory"].issue-data-block');
for (var i = 0; i < parentDivs.length; i++) {
var parentDiv = parentDivs[i];
var changeHistory = parentDiv.querySelector('.changehistory');
if (changeHistory.querySelector('.activity-old-val') == null) {
continue;
}
var fieldName = changeHistory.querySelector('.activity-name').textContent;
var oldVal = changeHistory.querySelector('.activity-old-val').textContent
var newVal = changeHistory.querySelector('.activity-new-val').textContent
var diffDiv = parentDiv.querySelector('.changehistory.action-body');
diffDiv.innerHTML = '<strong>' + fieldName + '</strong><br /><br />' +
diffString(oldVal, newVal).replace(/\n\n/g, "<br />");
}
createCssStyle('ins { background-color: #BFB; }');
createCssStyle('del { background-color: #FAA; }');
// if you use a dark theme
// createCssStyle('ins { background-color: #040; }');
// createCssStyle('del { background-color: #400; }');
@locofocos
Copy link
Author

locofocos commented Aug 21, 2020

comment for dumping screenshots:

image

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment