').append(
+ lineNum('old', o.base, 'delete', fileHash),
+ $('| ').append(ld.base).addClass('delete'),
+ lineNum('new', o.head, 'insert', fileHash),
+ $(' | ').append(ld.head).addClass('insert')
+ ).appendTo(tbody);
+ break;
+ }
+ }
+ return table;
+ },
+ unified: function(){
+ const table = $('');
+ table.attr({ add: oplines.add, del: oplines.del });
+ const tbody = $('').appendTo(table);
+ for (let i = 0; i < oplines.length; i++) {
+ const o = oplines[i];
+ switch (o.change) {
+ case 'skip':
+ tbody.append($('').html(' | | '));
+ break;
+ case 'delete':
+ case 'insert':
+ case 'equal':
+ tbody.append($(' ').append(
+ lineNum('old', o.base, o.change, fileHash),
+ lineNum('new', o.head, o.change, fileHash),
+ $('| ').addClass(o.change).html(o.head ? headTextDom(o.head) : baseTextDom(o.base))));
+ break;
+ case 'replace':
+ const deletes = [];
+ while (oplines[i] && oplines[i].change == 'replace') {
+ if (oplines[i].base && oplines[i].head) {
+ const ld = lineDiff(baseTextDom(oplines[i].base), headTextDom(oplines[i].head));
+ tbody.append($(' | ').append(lineNum('old', oplines[i].base, 'delete', fileHash), '| ', $(' | ').append(ld.base)));
+ deletes.push($(' | ').append('| ',lineNum('new', oplines[i].head, 'insert', fileHash),$(' | ').append(ld.head)));
+ } else if(oplines[i].base) {
+ tbody.append($(' | ').append(lineNum('old', oplines[i].base, 'delete', fileHash), '| ', $(' | ').html(baseTextDom(oplines[i].base))));
+ } else if(oplines[i].head) {
+ deletes.push($(' | ').append('| ',lineNum('new', oplines[i].head, 'insert', fileHash), $(' | ').html(headTextDom(oplines[i].head))));
+ }
+ i++;
+ }
+ tbody.append(deletes);
+ i--;
+ break;
+ }
+ }
+ return table;
+ }
+ };
+ function lineNum(type, num, klass, hash) {
+ const cell = $(' | ').addClass(type + 'line').addClass(klass);
+ if (num) {
+ cell.attr('line-number', num);
+ cell.attr('id', hash + '-' + (type == 'old' ? 'L' : 'R') + num);
+ }
+ return cell;
+ }
+ function lineDiff(b, n) {
+ const bc = $('').html(b).children();
+ const nc = $('').html(n).children();
+ const textE = function(){ return $(this).text(); };
+ const sm = new difflib.SequenceMatcher(bc.map(textE), nc.map(textE));
+ const op = sm.get_opcodes();
+ if (op.length == 1 || sm.ratio() < 0.5) {
+ return { base:bc, head:nc };
+ }
+ const ret = { base : [], head: []};
+ for (let i = 0; i < op.length; i++) {
+ const o = op[i];
+ switch (o[0]) {
+ case 'equal':
+ ret.base = ret.base.concat(bc.slice(o[1], o[2]));
+ ret.head = ret.head.concat(nc.slice(o[3], o[4]));
+ break;
+ case 'delete':
+ case 'insert':
+ case 'replace':
+ if(o[2] != o[1]){
+ ret.base.push($('').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){
+ let ret = [], add = 0, del = 0;
+ for (let idx = 0; idx < opcodes.length; idx++) {
+ const code = opcodes[idx];
+ const change = code[0];
+ let b = code[1];
+ let n = code[3];
+ const rowcnt = Math.max(code[2] - b, code[4] - n);
+ for (let i = 0; i < rowcnt; i++) {
+ switch(change){
+ case 'insert':
+ add++;
+ ret.push({
+ change:(isIgnoreLine(headTextLines[n]) ? 'equal' : change),
+ head: ++n
+ });
+ break;
+ case 'delete':
+ del++;
+ ret.push({
+ change: (isIgnoreLine(baseTextLines[b]) ? 'equal' : change),
+ base: ++b
+ });
+ break;
+ case 'replace':
+ add++;
+ del++;
+ const 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]
+ });
+ }
+ ret.add = oplines.add;
+ ret.del = oplines.del;
+ return ret;
+ }
+});
+
+/**
+ * scroll target into view ( on bottom edge, or on top edge)
+ */
+function scrollIntoView(target){
+ target = $(target);
+ const $window = $(window);
+ const docViewTop = $window.scrollTop();
+ const docViewBottom = docViewTop + $window.height();
+
+ const elemTop = target.offset().top;
+ const elemBottom = elemTop + target.height();
+
+ if(elemBottom > docViewBottom){
+ $('html, body').scrollTop(elemBottom - $window.height());
+ }else if(elemTop < docViewTop){
+ $('html, body').scrollTop(elemTop);
+ }
+}
+
+/**
+* escape html
+*/
+function escapeHtml(text){
+ return text.replace(/&/g,'&').replace(//g,'>');
+}
+
+/**
+ * calculate string ranking for path.
+ * Original ported from:
+ * http://joshaven.com/string_score
+ * https://github.com/joshaven/string_score
+ *
+ * Copyright (C) 2009-2011 Joshaven Potter
+ * Special thanks to all of the contributors listed here https://github.com/joshaven/string_score
+ * MIT license: http://www.opensource.org/licenses/mit-license.php
+ */
+function string_score(string, word) {
+ 'use strict';
+ var zero = {score:0,matchingPositions:[]};
+
+ // If the string is equal to the word, perfect match.
+ if (string === word || word === "") { return {score:1, matchingPositions:[]}; }
+
+ var lString = string.toUpperCase(),
+ strLength = string.length,
+ lWord = word.toUpperCase(),
+ wordLength = word.length;
+
+ return calc(zero, 0, 0, 0, 0, []);
+ function calc(score, startAt, skip, runningScore, i, matchingPositions){
+ if( i < wordLength) {
+ var charScore = 0;
+
+ // Find next first case-insensitive match of a character.
+ var idxOf = lString.indexOf(lWord[i], skip);
+
+ if (-1 === idxOf) { return score; }
+ score = calc(score, startAt, idxOf+1, runningScore, i, matchingPositions);
+ if (startAt === idxOf) {
+ // Consecutive letter & start-of-string Bonus
+ charScore = 0.8;
+ } else {
+ charScore = 0.1;
+
+ // Acronym Bonus
+ // Weighing Logic: Typing the first character of an acronym is as if you
+ // preceded it with two perfect character matches.
+ if (/^[^A-Za-z0-9]/.test(string[idxOf - 1])){
+ charScore += 0.7;
+ }else if(string[idxOf]==lWord[i]) {
+ // Upper case bonus
+ charScore += 0.2;
+ // Camel case bonus
+ if(/^[a-z]/.test(string[idxOf - 1])){
+ charScore += 0.5;
+ }
+ }
+ }
+
+ // Same case bonus.
+ if (string[idxOf] === word[i]) { charScore += 0.1; }
+
+ // next round
+ return calc(score, idxOf + 1, idxOf + 1, runningScore + charScore, i+1, matchingPositions.concat(idxOf));
+ }else{
+ // skip non match folder
+ var effectiveLength = strLength;
+ if(matchingPositions.length){
+ var lastSlash = string.lastIndexOf('/',matchingPositions[0]);
+ if(lastSlash!==-1){
+ effectiveLength = strLength-lastSlash;
+ }
+ }
+ // Reduce penalty for longer strings.
+ var finalScore = 0.5 * (runningScore / effectiveLength + runningScore / wordLength);
+
+ if ((lWord[0] === lString[0]) && (finalScore < 0.85)) {
+ finalScore += 0.15;
+ }
+ if(score.score >= finalScore){
+ return score;
+ }
+ return {score:finalScore, matchingPositions:matchingPositions};
+ }
+ }
+}
+/**
+ * sort by string_score.
+ * @param word {String} search word
+ * @param strings {Array[String]} search targets
+ * @param limit {Integer} result limit
+ * @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Integer] matching positions"}]}
+ */
+function string_score_sort(word, strings, limit){
+ var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
+ for(; i < l; i++){
+ var score = string_score(strings[i],word);
+ if(score.score){
+ score.string = strings[i];
+ ret.push(score);
+ }
+ }
+ ret.sort(function(a,b){
+ var s = b.score - a.score;
+ if(s === 0){
+ return a.string > b.string ? 1 : -1;
+ }
+ return s;
+ });
+ ret = ret.slice(0,limit);
+ return ret;
+}
+/**
+ * highlight by result.
+ * @param score {string:"string target string", matchingPositions:"Array[Integer] matching positions"}
+ * @param highlight tag ex: ''
+ * @return array of highlighted html elements.
+ */
+function string_score_highlight(result, tag){
+ var str = result.string, msp=0;
+ return hilight([], 0, result.matchingPositions[msp]);
+ function hilight(html, c, mpos){
+ if(mpos === undefined){
+ return html.concat(document.createTextNode(str.substr(c)));
+ }else{
+ return hilight(html.concat([
+ document.createTextNode(str.substring(c,mpos)),
+ $(tag).text(str[mpos])]),
+ mpos+1, result.matchingPositions[++msp]);
+ }
+ }
+}
+
+/****************************************************************************/
+/* Diff */
+/****************************************************************************/
+// add naturalWidth and naturalHeight for ie 8
+function setNatural(img) {
+ if(typeof img.naturalWidth == 'undefined'){
+ var tmp = new Image();
+ tmp.src = img.src;
+ img.naturalWidth = tmp.width;
+ img.naturalHeight = tmp.height;
+ }
+}
+/**
+ * onload handler
+ * @param img
+ */
+function onLoadedDiffImages(img){
+ setNatural(img);
+ img = $(img);
+ img.show();
+ var tb = img.parents(".diff-image-render");
+ // Find images. If the image has not loaded yet, value is undefined.
+ var old = tb.find(".diff-old img.diff-image:visible")[0];
+ var neo = tb.find(".diff-new img.diff-image:visible")[0];
+ imageDiff.appendImageMeta(tb, old, neo);
+ if(old && neo){
+ imageDiff.createToolSelector(old, neo).appendTo(tb.parent());
+ }
+}
+var imageDiff ={
+ /** append image meta div after image nodes.
+ * @param tb
+ * @param old ![]() ||undefined
+ * @param neo ![]() ||undefined
+ */
+ appendImageMeta:function(tb, old, neo){
+ old = old || {};
+ neo = neo || {};
+ tb.find(".diff-meta").remove();
+ // before loaded, image is not visible.
+ tb.find("img.diff-image:visible").each(function(){
+ var div = $(' W: | W: ');
+ div.find('.w').text(this.naturalWidth+"px").toggleClass("diff", old.naturalWidth != neo.naturalWidth);
+ div.find('.h').text(this.naturalHeight+"px").toggleClass("diff", old.naturalHeight != neo.naturalHeight);
+ div.appendTo(this.parentNode);
+ });
+ },
+ /** check this browser can use canvas tag.
+ */
+ hasCanvasSupport:function(){
+ if(!this.hasCanvasSupport.hasOwnProperty('resultCache')){
+ this.hasCanvasSupport.resultCache = (typeof $(' | |