diff --git a/src/main/twirl/gitbucket/core/helper/diff.scala.html b/src/main/twirl/gitbucket/core/helper/diff.scala.html index 2877060..917f5c9 100644 --- a/src/main/twirl/gitbucket/core/helper/diff.scala.html +++ b/src/main/twirl/gitbucket/core/helper/diff.scala.html @@ -42,15 +42,16 @@ } @diffs.zipWithIndex.map { case (diff, i) => - +
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @diff.oldPath -> @diff.newPath @if(newCommitId.isDefined){ } } @@ -62,8 +63,9 @@ } @diff.newPath @if(newCommitId.isDefined){ } } @@ -72,7 +74,7 @@ @if(oldCommitId.isDefined){ } } @@ -81,7 +83,7 @@
@if(diff.newContent != None || diff.oldContent != None){ -
+
} else { @@ -92,7 +94,6 @@
} - diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index 6888dfd..26fb424 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -1027,16 +1027,25 @@ width: 50%; } +table.diff td.body{ + position: relative; +} + +table.diff th.line-num{ + min-width: 20px; +} + table.diff .add-comment { position: absolute; - background: blue; + background: #4183c4; top: 0; left: -7px; color: white; padding: 2px 4px; - border: solid 1px blue; + border: solid 1px #4183c4; border-radius: 3px; z-index: 99; + cursor: pointer; } table.diff .add-comment:hover { @@ -1044,6 +1053,24 @@ top: -1px; } +table.diff tr td.body b.add-comment{ + display: none; +} + +table.diff tr:hover td.body b.add-comment{ + display: inline-block; +} + +.container-wide table.diff tr:hover td.body b.add-comment{ + display: none; +} + +.container-wide table.diff tr:hover td.body:hover b.add-comment, +.container-wide table.diff tr:hover th.line-num:hover + td b.add-comment{ + display: inline-block; +} + + table.diff tbody tr.not-diff { font-family: '"Helvetica Neue", Helvetica, Arial, sans-serif'; } diff --git a/src/main/webapp/assets/common/js/gitbucket.js b/src/main/webapp/assets/common/js/gitbucket.js index 6ce9086..6c1e959 100644 --- a/src/main/webapp/assets/common/js/gitbucket.js +++ b/src/main/webapp/assets/common/js/gitbucket.js @@ -76,41 +76,240 @@ * @param newTextId {String} element id of new text * @param outputId {String} element id of output element * @param viewType {Number} 0: split, 1: unified + * @param ignoreSpace {Number} 0: include, 1: ignore */ -function diffUsingJS(oldTextId, newTextId, outputId, viewType) { - // get the baseText and newText values from the two textboxes, and split them into lines - var oldText = document.getElementById(oldTextId).value; - var oldLines = []; - if(oldText !== ''){ - oldLines = difflib.stringAsLines(oldText); - } - - var newText = document.getElementById(newTextId).value; - var newLines = []; - if(newText !== ''){ - newLines = difflib.stringAsLines(newText); - } - - // create a SequenceMatcher instance that diffs the two sets of lines - var sm = new difflib.SequenceMatcher(oldLines, newLines); - - // get the opcodes from the SequenceMatcher instance - // opcodes is a list of 3-tuples describing what changes should be made to the base text - // in order to yield the new text - var opcodes = sm.get_opcodes(); - var diffoutputdiv = document.getElementById(outputId); - while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild); - - // build the diff view and add it to the current DOM - diffoutputdiv.appendChild(diffview.buildView({ - baseTextLines: oldLines, - newTextLines: newLines, - opcodes: opcodes, - contextSize: 4, - viewType: viewType - })); +function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) { + var render = new JsDiffRender({ + baseText: document.getElementById(oldTextId).value, + newText: document.getElementById(newTextId).value, + ignoreSpace: ignoreSpace, + contextSize: 4 + }); + var diff = render[viewType==1 ? "unified" : "split"](); + diff.appendTo($('#'+outputId).html("")); } + + function jqSelectorEscape(val) { return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); } + +function JsDiffRender(params){ + var baseTextLines = (params.baseText==="")?[]:params.baseText.split(/\r\n|\r|\n/); + var headTextLines = (params.headText==="")?[]:params.newText.split(/\r\n|\r|\n/); + var sm, ctx; + if(params.ignoreSpace){ + var ignoreSpace = function(a){ return a.replace(/\s+/,' ').replace(/^\s+|\s+$/,''); }; + sm = new difflib.SequenceMatcher( + $.map(baseTextLines, ignoreSpace), + $.map(headTextLines, ignoreSpace)); + ctx = this.flatten(sm.get_opcodes(), headTextLines, baseTextLines, function(text){ return ignoreSpace(text) === ""; }); + }else{ + sm = new difflib.SequenceMatcher(baseTextLines, headTextLines); + ctx = this.flatten(sm.get_opcodes(), headTextLines, baseTextLines, function(){ return false; }); + } + var oplines = this.fold(ctx, params.contextSize); + + function prettyDom(text){ + var dom = null; + return function(ln){ + if(dom===null){ + dom = prettyPrintOne(text.replace(/&/g,'&').replace(//g,'>'), null, true); + } + return (new RegExp('
  • ]*>(.*?)
  • ').exec(dom) || [])[1]; + }; + } + return this.renders(oplines, prettyDom(params.baseText), prettyDom(params.newText)); +} +$.extend(JsDiffRender.prototype,{ + renders: function(oplines, baseTextDom, headTextDom){ + return { + split:function(){ + var table = $(''); + var tbody = $('').appendTo(table); + for(var i=0;i').html('').appendTo(tbody); + break; + case 'delete': + case 'insert': + case 'equal': + $('').append( + lineNum('old',o.base), + $('').append( + lineNum('old',o.base), + $('
    ...
    ').html(o.base ? baseTextDom(o.base): "").addClass(o.change), + lineNum('old',o.head), + $('').html(o.head ? headTextDom(o.head): "").addClass(o.change) + ).appendTo(tbody); + break; + case 'replace': + var ld = lineDiff(baseTextDom(o.base), headTextDom(o.head)); + $('
    ').append(ld.base).addClass('delete'), + lineNum('old',o.head), + $('').append(ld.head).addClass('insert') + ).appendTo(tbody); + break; + } + } + return table; + }, + unified:function(){ + var table = $(''); + var tbody = $('').appendTo(table); + for(var i=0;i').html('')); + break; + case 'delete': + case 'insert': + case 'equal': + tbody.append($('').append( + lineNum('old',o.base), + lineNum('new',o.head), + $('').append(lineNum('old',oplines[i].base),'').append('').append(lineNum('old',oplines[i].base),'').append('
    ').addClass(o.change).html(o.head ? headTextDom(o.head) : baseTextDom(o.base)))); + break; + case 'replace': + var deletes = []; + while(oplines[i] && oplines[i].change == 'replace'){ + if(oplines[i].base && oplines[i].head){ + var ld = lineDiff(baseTextDom(oplines[i].base), headTextDom(oplines[i].head)); + tbody.append($('
    ',$('').append(ld.base))); + deletes.push($('
    ',lineNum('new',oplines[i].head),$('').append(ld.head))); + }else if(oplines[i].base){ + tbody.append($('
    ',$('').html(baseTextDom(oplines[i].base)))); + }else if(oplines[i].head){ + deletes.push($('
    ',lineNum('new',oplines[i].head),$('').html(headTextDom(oplines[i].head)))); + } + i++; + } + tbody.append(deletes); + i--; + break; + } + } + return table; + } + }; + function lineNum(type,num){ + var cell = $('').addClass(type+'line'); + if(num){ + cell.attr('line-number',num); + } + return cell; + } + function lineDiff(b,n){ + var bc = $('').html(b).children(); + var nc = $('').html(n).children(); + var textE = function(){ return $(this).text(); }; + var sm = new difflib.SequenceMatcher(bc.map(textE), nc.map(textE)); + var op = sm.get_opcodes(); + if(op.length==1 || sm.ratio()<0.5){ + return {base:bc,head:nc}; + } + var ret = { base : [], head: []}; + for(var i=0;i').append(bc.slice(o[1],o[2]))); + } + if(o[4]!=o[3]){ + ret.head.push($('').append(nc.slice(o[3],o[4]))); + } + break; + } + } + return ret; + } + }, + flatten: function(opcodes, headTextLines, baseTextLines, isIgnoreLine){ + var ret = []; + for (var idx = 0; idx < opcodes.length; idx++) { + var code = opcodes[idx]; + var change = code[0]; + var b = code[1]; + var n = code[3]; + var rowcnt = Math.max(code[2] - b, code[4] - n); + for (var i = 0; i < rowcnt; i++) { + switch(change){ + case 'insert': + ret.push({ + change:(isIgnoreLine(headTextLines[n]) ? 'equal' : change), + head: ++n + }); + break; + case 'delete': + ret.push({ + change: (isIgnoreLine(baseTextLines[b]) ? 'equal' : change), + base: ++b + }); + break; + case 'replace': + var r = {change: change}; + if(n contextSize){ + ret.push({ + change:'skip', + start:skips[0], + end:skips[skips.length-contextSize] + }); + } + ret = ret.concat(skips.splice(- contextSize)); + ret.push(o); + skips = []; + bskip = 0; + } + } + if(skips.length > contextSize){ + ret.push({ + change:'skip', + start:skips[0], + end:skips[skips.length-contextSize] + }); + } + return ret; + } +}); diff --git a/src/main/webapp/assets/vendors/jsdifflib/diffview.css b/src/main/webapp/assets/vendors/jsdifflib/diffview.css index 399a6c7..09454b1 100644 --- a/src/main/webapp/assets/vendors/jsdifflib/diffview.css +++ b/src/main/webapp/assets/vendors/jsdifflib/diffview.css @@ -77,7 +77,7 @@ background-color:#FD8 } table.diff .delete { - background-color:#FFDDDD; + background-color:#ffecec; } table.diff .skip { background-color: #F8F8FF; @@ -86,10 +86,19 @@ content: " ..."; } table.diff .insert { - background-color:#DDFFDD + background-color:#eaffea } table.diff th.author { text-align:right; border-top:1px solid #BBC; background:#EFEFEF } + +table.diff ins{ + background-color: #a6f3a6; + text-decoration: none; +} +table.diff del{ + background-color: #f8cbcb; + text-decoration: none; +}