Merge branch 'maint'
[gitweb.git] / gitweb / gitweb.js
index b15e2a7944158e6d3dce5e6ff3118cdddc47bb18..9c66928c4af8cf37b9c13a6a40936523e16fcc6f 100644 (file)
@@ -7,6 +7,39 @@
  * @license GPLv2 or later
  */
 
+/* ============================================================ */
+/* functions for generic gitweb actions and views */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * @globals jsExceptionsRe
+ */
+function fixLinks() {
+       var allLinks = document.getElementsByTagName("a") || document.links;
+       for (var i = 0, len = allLinks.length; i < len; i++) {
+               var link = allLinks[i];
+               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
+                       link.href +=
+                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+               }
+       }
+}
+
+
+/* ============================================================ */
+
 /*
  * This code uses DOM methods instead of (nonstandard) innerHTML
  * to modify page.
 
 /**
  * pad number N with nonbreakable spaces on the left, to WIDTH characters
- * example: padLeftStr(12, 3, '&nbsp;') == '&nbsp;12'
- *          ('&nbsp;' is nonbreakable space)
+ * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
+ *          ('\u00A0' is nonbreakable space)
  *
  * @param {Number|String} input: number to pad
  * @param {Number} width: visible width of output
- * @param {String} str: string to prefix to string, e.g. '&nbsp;'
+ * @param {String} str: string to prefix to string, e.g. '\u00A0'
  * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
  */
 function padLeftStr(input, width, str) {
        var prefix = '';
 
        width -= input.toString().length;
-       while (width > 1) {
+       while (width > 0) {
                prefix += str;
                width--;
        }
@@ -89,6 +122,7 @@ function createRequestObject() {
        return null;
 }
 
+
 /* ============================================================ */
 /* utility/helper functions (and variables) */
 
@@ -158,7 +192,7 @@ function updateProgressInfo() {
 
        if (div_progress_info) {
                div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
-                       ' (' + padLeftStr(percentage, 3, '&nbsp;') + '%)';
+                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
        }
 
        if (div_progress_bar) {
@@ -210,6 +244,101 @@ function errorInfo(str) {
        }
 }
 
+/* ............................................................ */
+/* coloring rows during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+       if (!tr) {
+               return null;
+       }
+       var className = tr.className;
+       if (className) {
+               var match = colorRe.exec(className);
+               if (match) {
+                       return parseInt(match[1], 10);
+               }
+       }
+       return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * return one of given possible colors (curently least used one)
+ * example: chooseColorNoFrom(2, 3) returns 2 or 3
+ *
+ * @param {Number[]} arguments: one or more numbers
+ *        assumes that  1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+       // choose the color which is least used
+       var colorNo = arguments[0];
+       for (var i = 1; i < arguments.length; i++) {
+               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+                       colorNo = arguments[i];
+               }
+       }
+       colorsFreq[colorNo-1]++;
+       return colorNo;
+}
+
+/**
+ * given two neigbour <tr> elements, find color which would be different
+ * from color of both of neighbours; used to 3-color blame table
+ *
+ * @param {HTMLElement} tr_prev
+ * @param {HTMLElement} tr_next
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+       var color_prev = getColorNo(tr_prev);
+       var color_next = getColorNo(tr_next);
+
+
+       // neither of neighbours has color set
+       // THEN we can use any of 3 possible colors
+       if (!color_prev && !color_next) {
+               return chooseColorNoFrom(1,2,3);
+       }
+
+       // either both neighbours have the same color,
+       // or only one of neighbours have color set
+       // THEN we can use any color except given
+       var color;
+       if (color_prev === color_next) {
+               color = color_prev; // = color_next;
+       } else if (!color_prev) {
+               color = color_next;
+       } else if (!color_next) {
+               color = color_prev;
+       }
+       if (color) {
+               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+       }
+
+       // neighbours have different colors
+       // THEN there is only one color left
+       return (3 - ((color_prev + color_next) % 3));
+}
+
 /* ............................................................ */
 /* coloring rows like 'blame' after 'blame_data' finishes */
 
@@ -224,8 +353,6 @@ function isStartOfGroup(tr) {
        return tr.firstChild.className === 'sha1';
 }
 
-var colorRe = /(?:light|dark)/;
-
 /**
  * change colors to use zebra coloring (2 colors) instead of 3 colors
  * concatenate neighbour commit groups belonging to the same commit
@@ -391,6 +518,12 @@ function handleLine(commit, group) {
                        formatDateISOLocal(commit.authorTime, commit.authorTimezone);
        }
 
+       // color depends on group of lines, not only on blamed commit
+       var colorNo = findColorNo(
+               document.getElementById('l'+(resline-1)),
+               document.getElementById('l'+(resline+group.numlines))
+       );
+
        // loop over lines in commit group
        for (var i = 0; i < group.numlines; i++, resline++) {
                var tr = document.getElementById('l'+resline);
@@ -409,7 +542,10 @@ function handleLine(commit, group) {
                var a_linenr = td_sha1.nextSibling.firstChild;
 
                /* <tr id="l123" class=""> */
-               var tr_class = 'light'; // or tr.className
+               var tr_class = '';
+               if (colorNo !== null) {
+                       tr_class = 'color'+colorNo;
+               }
                if (commit.boundary) {
                        tr_class += ' boundary';
                }
@@ -426,12 +562,20 @@ function handleLine(commit, group) {
                        td_sha1.rowSpan = group.numlines;
 
                        a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
-                       a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       if (a_sha1.firstChild) {
+                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       } else {
+                               a_sha1.appendChild(
+                                       document.createTextNode(commit.sha1.substr(0, 8)));
+                       }
                        if (group.numlines >= 2) {
                                var fragment = document.createDocumentFragment();
                                var br   = document.createElement("br");
-                               var text = document.createTextNode(
-                                       commit.author.match(/\b([A-Z])\B/g).join(''));
+                               var match = commit.author.match(/\b([A-Z])\B/g);
+                               if (match) {
+                                       var text = document.createTextNode(
+                                                       match.join(''));
+                               }
                                if (br && text) {
                                        var elem = fragment || td_sha1;
                                        elem.appendChild(br);
@@ -635,7 +779,12 @@ function handleResponse() {
        }
 
        // the server returned error
-       if (xhr.readyState === 3 && xhr.status !== 200) {
+       // try ... catch block is to work around bug in IE8
+       try {
+               if (xhr.readyState === 3 && xhr.status !== 200) {
+                       return;
+               }
+       } catch (e) {
                return;
        }
        if (xhr.readyState === 4 && xhr.status !== 200) {