From 9a86dd571058a511e60af35206a31017d873e54a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:01 +0200 Subject: [PATCH 01/11] gitweb: Split JavaScript for maintability, combining on build Split originally single gitweb.js file into smaller files, each dealing with single issue / area of responsibility. This move should make gitweb's JavaScript code easier to maintain. For better webapp performance it is recommended[1][2][3] to combine JavaScript files. Do it during build time (in gitweb/Makefile), by straight concatenation of files into gitweb.js file (which is now ignored as being generated). This means that there are no changes to gitweb script itself - it still uses gitweb.js or gitweb.min.js, but now generated. [1]: http://developer.yahoo.com/performance/rules.html "Minimize HTTP Requests" section [2]: http://code.google.com/speed/articles/include-scripts-properly.html "1. Combine external JavaScript files" [3]: http://javascript-reference.info/speed-up-your-javascript-load-time.htm "Combine Your Files" section. See also new gitweb/static/js/README file. Inspired-by-patch-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- .gitignore | 1 + gitweb/Makefile | 16 +- gitweb/static/js/README | 20 ++ .../{gitweb.js => js/blame_incremental.js} | 208 +----------------- gitweb/static/js/javascript-detection.js | 43 ++++ gitweb/static/js/lib/common-lib.js | 187 ++++++++++++++++ 6 files changed, 269 insertions(+), 206 deletions(-) create mode 100644 gitweb/static/js/README rename gitweb/static/{gitweb.js => js/blame_incremental.js} (76%) create mode 100644 gitweb/static/js/javascript-detection.js create mode 100644 gitweb/static/js/lib/common-lib.js diff --git a/.gitignore b/.gitignore index 2cf3ca5808..9e9500540e 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,7 @@ /gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi +/gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime /test-ctype diff --git a/gitweb/Makefile b/gitweb/Makefile index 0a6ac00631..0baa9df9f6 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -86,7 +86,7 @@ ifndef V endif endif -all:: gitweb.cgi +all:: gitweb.cgi static/gitweb.js GITWEB_PROGRAMS = gitweb.cgi @@ -112,6 +112,15 @@ endif GITWEB_FILES += static/git-logo.png static/git-favicon.png +# JavaScript files that are composed (concatenated) to form gitweb.js +# +# js/lib/common-lib.js should be always first, then js/lib/*.js, +# then the rest of files; js/gitweb.js should be last (if it exists) +GITWEB_JSLIB_FILES += static/js/lib/common-lib.js +GITWEB_JSLIB_FILES += static/js/javascript-detection.js +GITWEB_JSLIB_FILES += static/js/blame_incremental.js + + GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ @@ -146,6 +155,11 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +static/gitweb.js: $(GITWEB_JSLIB_FILES) + $(QUIET_GEN)$(RM) $@ $@+ && \ + cat $^ >$@+ && \ + mv $@+ $@ + ### Testing rules test: diff --git a/gitweb/static/js/README b/gitweb/static/js/README new file mode 100644 index 0000000000..f8460ed32f --- /dev/null +++ b/gitweb/static/js/README @@ -0,0 +1,20 @@ +GIT web interface (gitweb) - JavaScript +======================================= + +This directory holds JavaScript code used by gitweb (GIT web interface). +Scripts from there would be concatenated together in the order specified +by gitweb/Makefile into gitweb/static/gitweb.js, during building of +gitweb/gitweb.cgi (during gitweb building). The resulting file (or its +minification) would then be installed / deployed together with gitweb. + +Scripts in 'lib/' subdirectory compose generic JavaScript library, +providing features required by gitweb but in no way limited to gitweb +only. In the future those scripts could be replaced by some JavaScript +library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo, +ExtJS, Script.aculo.us or SproutCore. + +All scripts that manipulate gitweb output should be put outside 'lib/', +directly in this directory ('gitweb/static/js/'). Those scripts would +have to be rewritten if gitweb moves to using some JavaScript library. + +See also comments in gitweb/Makefile. diff --git a/gitweb/static/gitweb.js b/gitweb/static/js/blame_incremental.js similarity index 76% rename from gitweb/static/gitweb.js rename to gitweb/static/js/blame_incremental.js index 40ec08440b..f63f78b9ec 100644 --- a/gitweb/static/gitweb.js +++ b/gitweb/static/js/blame_incremental.js @@ -1,44 +1,12 @@ // Copyright (C) 2007, Fredrik Kuivinen // 2007, Petr Baudis -// 2008-2009, Jakub Narebski +// 2008-2011, Jakub Narebski /** - * @fileOverview JavaScript code for gitweb (git web interface). + * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb * @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 @@ -58,71 +26,6 @@ function fixLinks() { */ -/* ============================================================ */ -/* generic utility functions */ - - -/** - * pad number N with nonbreakable spaces on the left, to WIDTH characters - * 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. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR - */ -function padLeftStr(input, width, str) { - var prefix = ''; - - width -= input.toString().length; - while (width > 0) { - prefix += str; - width--; - } - return prefix + input; -} - -/** - * Pad INPUT on the left to SIZE width, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. - * - * @param {String} input: input value converted to string. - * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. - * - * @returns {String} Modified string, at least SIZE length. - */ -function padLeft(input, width, ch) { - var s = input + ""; - while (s.length < width) { - s = ch + s; - } - return s; -} - -/** - * Create XMLHttpRequest object in cross-browser way - * @returns XMLHttpRequest object, or null - */ -function createRequestObject() { - try { - return new XMLHttpRequest(); - } catch (e) {} - try { - return window.createRequest(); - } catch (e) {} - try { - return new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) {} - try { - return new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) {} - - return null; -} - - /* ============================================================ */ /* utility/helper functions (and variables) */ @@ -392,111 +295,6 @@ function fixColorsAndGroups() { } } -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; - -/** - * convert numeric timezone +/-ZZZZ to offset from UTC in seconds - * - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {Number} offset from UTC in seconds for timezone - * - * @globals tzRe - */ -function timezoneOffset(timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - var tz_sign = (match[1] === '-' ? -1 : +1); - var tz_hour = parseInt(match[2],10); - var tz_min = parseInt(match[3],10); - - return tz_sign*(((tz_hour*60) + tz_min)*60); -} - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - */ -function formatDateISOLocal(epoch, timezoneInfo) { - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - timezoneOffset(timezoneInfo))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - -/* ............................................................ */ -/* unquoting/unescaping filenames */ - -/**#@+ - * @constant - */ -var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; -var octEscRe = /^[0-7]{1,3}$/; -var maybeQuotedRe = /^\"(.*)\"$/; -/**#@-*/ - -/** - * unquote maybe git-quoted filename - * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' - * - * @param {String} str: git-quoted string - * @returns {String} Unquoted and unescaped string - * - * @globals escCodeRe, octEscRe, maybeQuotedRe - */ -function unquote(str) { - function unq(seq) { - var es = { - // character escape codes, aka escape sequences (from C) - // replacements are to some extent JavaScript specific - t: "\t", // tab (HT, TAB) - n: "\n", // newline (NL) - r: "\r", // return (CR) - f: "\f", // form feed (FF) - b: "\b", // backspace (BS) - a: "\x07", // alarm (bell) (BEL) - e: "\x1B", // escape (ESC) - v: "\v" // vertical tab (VT) - }; - - if (seq.search(octEscRe) !== -1) { - // octal char sequence - return String.fromCharCode(parseInt(seq, 8)); - } else if (seq in es) { - // C escape sequence, aka character escape code - return es[seq]; - } - // quoted ordinary character - return seq; - } - - var match = str.match(maybeQuotedRe); - if (match) { - str = match[1]; - // perhaps str = eval('"'+str+'"'); would be enough? - str = str.replace(escCodeRe, - function (substr, p1, offset, s) { return unq(p1); }); - } - return str; -} /* ============================================================ */ /* main part: parsing response */ @@ -886,4 +684,4 @@ function startBlame(blamedataUrl, bUrl) { pollTimer = setInterval(xhr.onreadystatechange, 1000); } -// end of gitweb.js +/* end of blame_incremental.js */ diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js new file mode 100644 index 0000000000..93dd2bdd91 --- /dev/null +++ b/gitweb/static/js/javascript-detection.js @@ -0,0 +1,43 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Detect if JavaScript is enabled, and pass it to server-side + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* Manipulating links */ + +/** + * 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') + * + * To be used as `window.onload` handler + * + * @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'; + } + } +} + +/* end of javascript-detection.js */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js new file mode 100644 index 0000000000..38f3b9e144 --- /dev/null +++ b/gitweb/static/js/lib/common-lib.js @@ -0,0 +1,187 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Generic JavaScript code (helper functions) + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* ............................................................ */ +/* Padding */ + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * 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. '\u00A0' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 0) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to SIZE width, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a'. + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + while (s.length < width) { + s = ch + s; + } + return s; +} + + +/* ............................................................ */ +/* Ajax */ + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + + +/* ............................................................ */ +/* time and data */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + */ +function formatDateISOLocal(epoch, timezoneInfo) { + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe git-quoted filename + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* end of common-lib.js */ From e2895de4d8efef52e835731aaaaa9467e2110e62 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:02 +0200 Subject: [PATCH 02/11] gitweb.js: Update and improve comments in JavaScript files This consists of adding a few extra explanation, fixing descriptions of functions to match names of parameters in code, adding a few separators, and fixing spelling -- while at it spell 'neighbor' using American spelling (and not as 'neighbour'). This is post-split cleanup. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/blame_incremental.js | 22 +++++++++++----------- gitweb/static/js/lib/common-lib.js | 12 ++++++++---- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js index f63f78b9ec..676da6b590 100644 --- a/gitweb/static/js/blame_incremental.js +++ b/gitweb/static/js/blame_incremental.js @@ -7,7 +7,7 @@ * @license GPLv2 or later */ - +/* ============================================================ */ /* * This code uses DOM methods instead of (nonstandard) innerHTML * to modify page. @@ -26,7 +26,7 @@ */ -/* ============================================================ */ +/* ............................................................ */ /* utility/helper functions (and variables) */ var xhr; // XMLHttpRequest object @@ -132,7 +132,7 @@ function writeTimeInterval() { } /** - * show an error message alert to user within page (in prohress info area) + * show an error message alert to user within page (in progress info area) * @param {String} str: plain text error message (no HTML) * * @globals div_progress_info @@ -182,7 +182,7 @@ function getColorNo(tr) { var colorsFreq = [0, 0, 0]; /** - * return one of given possible colors (curently least used one) + * return one of given possible colors (currently least used one) * example: chooseColorNoFrom(2, 3) returns 2 or 3 * * @param {Number[]} arguments: one or more numbers @@ -203,8 +203,8 @@ function chooseColorNoFrom() { } /** - * given two neigbour elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table + * given two neighbor elements, find color which would be different + * from color of both of neighbors; used to 3-color blame table * * @param {HTMLElement} tr_prev * @param {HTMLElement} tr_next @@ -216,14 +216,14 @@ function findColorNo(tr_prev, tr_next) { var color_next = getColorNo(tr_next); - // neither of neighbours has color set + // neither of neighbors 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 + // either both neighbors have the same color, + // or only one of neighbors have color set // THEN we can use any color except given var color; if (color_prev === color_next) { @@ -237,7 +237,7 @@ function findColorNo(tr_prev, tr_next) { return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); } - // neighbours have different colors + // neighbors have different colors // THEN there is only one color left return (3 - ((color_prev + color_next) % 3)); } @@ -258,7 +258,7 @@ function isStartOfGroup(tr) { /** * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit + * concatenate neighbor commit groups belonging to the same commit * * @globals colorRe */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index 38f3b9e144..6a6d2000c4 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -13,14 +13,17 @@ /* Padding */ /** - * pad number N with nonbreakable spaces on the left, to WIDTH characters + * pad INPUT on the left with STR that is assumed to have visible + * width of single character (for example nonbreakable spaces), + * to WIDTH characters + * * 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. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) */ function padLeftStr(input, width, str) { var prefix = ''; @@ -34,7 +37,7 @@ function padLeftStr(input, width, str) { } /** - * Pad INPUT on the left to SIZE width, using given padding character CH, + * Pad INPUT on the left to WIDTH, using given padding character CH, * for example padLeft('a', 3, '_') is '__a'. * * @param {String} input: input value converted to string. @@ -140,7 +143,8 @@ var maybeQuotedRe = /^\"(.*)\"$/; /**#@-*/ /** - * unquote maybe git-quoted filename + * unquote maybe C-quoted filename (as used by git, i.e. it is + * in double quotes '"' if there is any escape character used) * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' * * @param {String} str: git-quoted string From 4dfa207eec44e0d8869c4978cf3d9861aaf40091 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:03 +0200 Subject: [PATCH 03/11] gitweb.js: Provide default values for padding in padLeftStr and padLeft This means that one can use padLeft(4, 2) and it would be equivalent to runing padLeft(4, 2, '0'), and it would return '04' i.e. '4' padded with '0' to width 2, to be used e.g. in formatting date and time. This should make those functions easier to use. Current code doesn't yet make use of this feature. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/lib/common-lib.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index 6a6d2000c4..c45454ead2 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -22,11 +22,14 @@ * * @param {Number|String} input: number to pad * @param {Number} width: visible width of output - * @param {String} str: string to prefix to string, e.g. '\u00A0' + * @param {String} str: string to prefix to string, defaults to '\u00A0' * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) */ function padLeftStr(input, width, str) { var prefix = ''; + if (typeof str === 'undefined') { + ch = '\u00A0'; // using ' ' doesn't work in all browsers + } width -= input.toString().length; while (width > 0) { @@ -38,16 +41,21 @@ function padLeftStr(input, width, str) { /** * Pad INPUT on the left to WIDTH, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. + * for example padLeft('a', 3, '_') is '__a' + * padLeft(4, 2) is '04' (same as padLeft(4, 2, '0')) * * @param {String} input: input value converted to string. * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. + * @param {String} ch: single character to prefix to string, defaults to '0'. * * @returns {String} Modified string, at least SIZE length. */ function padLeft(input, width, ch) { var s = input + ""; + if (typeof ch === 'undefined') { + ch = '0'; + } + while (s.length < width) { s = ch + s; } From 54b1479a770b240d71bbd000fb40e470987bac59 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:04 +0200 Subject: [PATCH 04/11] gitweb.js: Extract and improve datetime handling Move formatDateISOLocal(epoch, timezone) function (and also helper timezoneOffset(timezoneInfo) function it requires) from common-lib.js to datetime.js Add new functions: * localTimezoneOffset - to get browser timezone offset in seconds * localTimezoneInfo - to get browser timezone in '(+|-)HHMM' format * formatTimezoneInfo - turn offset in hours and minutes into '(+|-)HHMM' * parseRFC2822Date - to parse RFC-2822 dates that gitweb uses into epoch * formatDateRFC2882 - like formatDateISOLocal, only RFC-2822 format All those functions are meant to be used in future commit 'gitweb: javascript ability to adjust time based on timezone' An alternative would be to use e.g. Datejs (http://www.datejs.com) library, or JavaScript framework that has date formatting (perhaps as a plugin). While at it escape '-' in character class inside tzRe regexp, as recommended by JSLint (http://www.jslint.com). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/static/js/lib/common-lib.js | 51 --------- gitweb/static/js/lib/datetime.js | 161 +++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 gitweb/static/js/lib/datetime.js diff --git a/gitweb/Makefile b/gitweb/Makefile index 0baa9df9f6..403265a9b2 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -117,6 +117,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # js/lib/common-lib.js should be always first, then js/lib/*.js, # then the rest of files; js/gitweb.js should be last (if it exists) GITWEB_JSLIB_FILES += static/js/lib/common-lib.js +GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index c45454ead2..d6b0c0d46a 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -88,57 +88,6 @@ function createRequestObject() { } -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; - -/** - * convert numeric timezone +/-ZZZZ to offset from UTC in seconds - * - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {Number} offset from UTC in seconds for timezone - * - * @globals tzRe - */ -function timezoneOffset(timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - var tz_sign = (match[1] === '-' ? -1 : +1); - var tz_hour = parseInt(match[2],10); - var tz_min = parseInt(match[3],10); - - return tz_sign*(((tz_hour*60) + tz_min)*60); -} - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - */ -function formatDateISOLocal(epoch, timezoneInfo) { - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - timezoneOffset(timezoneInfo))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - - /* ............................................................ */ /* unquoting/unescaping filenames */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js new file mode 100644 index 0000000000..b3dcedb141 --- /dev/null +++ b/gitweb/static/js/lib/datetime.js @@ -0,0 +1,161 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Datetime manipulation: parsing and formatting + * @license GPLv2 or later + */ + + +/* ............................................................ */ +/* parsing and retrieving datetime related information */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+\-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} + +/** + * return local (browser) timezone as offset from UTC in seconds + * + * @returns {Number} offset from UTC in seconds for local timezone + */ +function localTimezoneOffset() { + // getTimezoneOffset returns the time-zone offset from UTC, + // in _minutes_, for the current locale + return ((new Date()).getTimezoneOffset() * -60); +} + +/** + * return local (browser) timezone as numeric timezone '(+|-)HHMM' + * + * @returns {String} locat timezone as -/+ZZZZ + */ +function localTimezoneInfo() { + var tzOffsetMinutes = (new Date()).getTimezoneOffset() * -1; + + return formatTimezoneInfo(0, tzOffsetMinutes); +} + + +/** + * Parse RFC-2822 date into a Unix timestamp (into epoch) + * + * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC' + */ +function parseRFC2822Date(date) { + // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere) + // date syntax, which is defined in RFC 2822 (obsoletes RFC 822) + // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC + return Date.parse(date) / 1000; +} + + +/* ............................................................ */ +/* formatting date */ + +/** + * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM' + * + * @param {Number} hours: offset in hours, e.g. 2 for '+0200' + * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030'; + * it is split into hours if not 0 <= minutes < 60, + * for example 1200 would give '+0100'; + * defaults to 0 + * @param {String} [sep] separator between hours and minutes part, + * default is '', might be ':' for W3CDTF (rfc-3339) + * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format + */ +function formatTimezoneInfo(hours, minutes, sep) { + minutes = minutes || 0; // to be able to use formatTimezoneInfo(hh) + sep = sep || ''; // default format is +/-ZZZZ + + if (minutes < 0 || minutes > 59) { + hours = minutes > 0 ? Math.floor(minutes / 60) : Math.ceil(minutes / 60); + minutes = Math.abs(minutes - 60*hours); // sign of minutes is sign of hours + // NOTE: this works correctly because there is no UTC-00:30 timezone + } + + var tzSign = hours >= 0 ? '+' : '-'; + if (hours < 0) { + hours = -hours; // sign is stored in tzSign + } + + return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0'); +} + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + */ +function formatDateISOLocal(epoch, timezoneInfo) { + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/** + * return date in local time formatted in rfc-2822 format + * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise + * @returns {String} date in local time in rfc-2822 format + */ +function formatDateRFC2882(epoch, timezoneInfo, padDay) { + // A short textual representation of a month, three letters + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + // A textual representation of a day, three letters + var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005' + days[localDate.getUTCDay()] + ', ' + + (padDay ? padLeft(localDate.getUTCDate(),2,'0') : localDate.getUTCDate()) + ' ' + + months[localDate.getUTCMonth()] + ' ' + + localDate.getUTCFullYear(); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* end of datetime.js */ From fcce886bfb4671c4c17a3f99cb7caf0f7ab94e16 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:05 +0200 Subject: [PATCH 05/11] gitweb.js: Introduce code to handle cookies from JavaScript Introduced gitweb/static/js/cookies.js file provides functions for setting, getting and deleting cookies. Code taken from subsection "Cookies in JavaScript" of "Professional JavaScript for Web Developers" by Nicholas C. Zakas and from cookie plugin for jQuery (dual licensed under the MIT and GPL licenses). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/static/js/lib/cookies.js | 114 ++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 gitweb/static/js/lib/cookies.js diff --git a/gitweb/Makefile b/gitweb/Makefile index 403265a9b2..7dd1dee55a 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -118,6 +118,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # then the rest of files; js/gitweb.js should be last (if it exists) GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js +GITWEB_JSLIB_FILES += static/js/lib/cookies.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js new file mode 100644 index 0000000000..72b51cd1b4 --- /dev/null +++ b/gitweb/static/js/lib/cookies.js @@ -0,0 +1,114 @@ +/** + * @fileOverview Accessing cookies from JavaScript + * @license GPLv2 or later + */ + +/* + * Based on subsection "Cookies in JavaScript" of "Professional + * JavaScript for Web Developers" by Nicholas C. Zakas and cookie + * plugin from jQuery (dual licensed under the MIT and GPL licenses) + */ + + +/** + * Create a cookie with the given name and value, + * and other optional parameters. + * + * @example + * setCookie('foo', 'bar'); // will be deleted when browser exits + * setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) }); + * setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week + * setCookie('foo', 'bar', { expires: 14, path: '/' }); + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores). + * @param {String} sValue: The string value stored in a cookie. + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires, + * or an integer specifying the expiration date from now on in days, + * or a Date object to be used as cookie expiration date. + * If a negative value is specified or a date in the past), + * the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie + * and will not be retained when the the browser exits. + * @param {String} [options.path] Restrict access of a cookie to particular directory + * (default: path of page that created the cookie). + * @param {String} [options.domain] Override what web sites are allowed to access cookie + * (default: domain of page that created the cookie). + * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set + * and the cookie would be accessible only from secure sites + * (cookie transmission will require secure protocol like HTTPS). + */ +function setCookie(sName, sValue, options) { + options = options || {}; + if (sValue === null) { + sValue = ''; + option.expires = 'delete'; + } + + var sCookie = sName + '=' + encodeURIComponent(sValue); + + if (options.expires) { + var oExpires = options.expires, sDate; + if (oExpires === 'delete') { + sDate = 'Thu, 01 Jan 1970 00:00:00 GMT'; + } else if (typeof oExpires === 'string') { + sDate = oExpires; + } else { + var oDate; + if (typeof oExpires === 'number') { + oDate = new Date(); + oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms + } else { + oDate = oExpires; + } + sDate = oDate.toGMTString(); + } + sCookie += '; expires=' + sDate; + } + + if (options.path) { + sCookie += '; path=' + (options.path); + } + if (options.domain) { + sCookie += '; domain=' + (options.domain); + } + if (options.secure) { + sCookie += '; secure'; + } + document.cookie = sCookie; +} + +/** + * Get the value of a cookie with the given name. + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @returns {String|null} The string value stored in a cookie + */ +function getCookie(sName) { + var sRE = '(?:; )?' + sName + '=([^;]*);?'; + var oRE = new RegExp(sRE); + if (oRE.test(document.cookie)) { + return decodeURIComponent(RegExp['$1']); + } else { + return null; + } +} + +/** + * Delete cookie with given name + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String} [options.path] Must be the same as when setting a cookie + * @param {String} [options.domain] Must be the same as when setting a cookie + */ +function deleteCookie(sName, options) { + options = options || {}; + options.expires = 'delete'; + + setCookie(sName, '', options); +} + +/* end of cookies.js */ From 1cae3ee70dd9e389a83c494fbc95aa63c01128a1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:06 +0200 Subject: [PATCH 06/11] gitweb.js: Provide getElementsByClassName method (if it not exists) The code is simplified and does not support full specification of native getElementsByClassName method, but implements just subset that would be enough for gitweb, supporting only single class name. Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/lib/common-lib.js | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index d6b0c0d46a..b37139152d 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -88,6 +88,57 @@ function createRequestObject() { } +/* ............................................................ */ +/* Support for legacy browsers */ + +/** + * Provides getElementsByClassName method, if there is no native + * implementation of this method. + * + * NOTE that there are limits and differences compared to native + * getElementsByClassName as defined by e.g.: + * https://developer.mozilla.org/en/DOM/document.getElementsByClassName + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname + * + * Namely, this implementation supports only single class name as + * argument and not set of space-separated tokens representing classes, + * it returns Array of nodes rather than live NodeList, and has + * additional optional argument where you can limit search to given tags + * (via getElementsByTagName). + * + * Based on + * http://code.google.com/p/getelementsbyclassname/ + * http://www.dustindiaz.com/getelementsbyclass/ + * http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript + * + * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/ + * + * @param {String} class: name of _single_ class to find + * @param {String} [taghint] limit search to given tags + * @returns {Node[]} array of matching elements + */ +if (!('getElementsByClassName' in document)) { + document.getElementsByClassName = function (classname, taghint) { + taghint = taghint || "*"; + var elements = (taghint === "*" && document.all) ? + document.all : + document.getElementsByTagName(taghint); + var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)"); + var matches= []; + for (var i = 0, j = 0, n = elements.length; i < n; i++) { + var el= elements[i]; + if (el.className && pattern.test(el.className)) { + // matches.push(el); + matches[j] = el; + j++; + } + } + return matches; + }; +} // end if + + /* ............................................................ */ /* unquoting/unescaping filenames */ From 256b7b4883231ae9c7f24f2cb4170f61e00704e1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:07 +0200 Subject: [PATCH 07/11] gitweb: Refactor generating of long dates into format_timestamp_html It is pure refactoring and doesn't change gitweb output, though this could potentially affect 'summary', 'log', and 'commit'-like views ('commit', 'commitdiff', 'tag'). Remove print_local_time and format_local_time, as their use is now replaced (indirectly) by using format_timestamp_html. While at it improve whitespace formatting. Inspired-by-code-by: Kevin Cernekee Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ee69ea683a..7329db289c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3938,22 +3938,21 @@ sub git_print_section { print $cgi->end_div; } -sub print_local_time { - print format_local_time(@_); -} +sub format_timestamp_html { + my ($date, %opts) = @_; + my $strtime = $date->{'rfc2822'}; -sub format_local_time { - my $localtime = ''; - my %date = @_; - if ($date{'hour_local'} < 6) { - $localtime .= sprintf(" (%02d:%02d %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); - } else { - $localtime .= sprintf(" (%02d:%02d %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); + return $strtime unless $opts{'-localtime'}; + + my $localtime_format = '(%02d:%02d %s)'; + if ($date->{'hour_local'} < 6) { + $localtime_format = '(%02d:%02d %s)'; } + $strtime .= ' ' . + sprintf($localtime_format, + $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'}); - return $localtime; + return $strtime; } # Outputs the author name and date in long form @@ -3966,10 +3965,9 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [$ad{'rfc2822'}"; - print_local_time(%ad) if ($opts{-localtime}); - print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) - . "\n"; + " [".format_timestamp_html(\%ad, %opts)."]". + git_get_avatar($co->{'author_email'}, -pad_before => 1) . + "\n"; } # Outputs table rows containing the full author or committer information, @@ -3986,16 +3984,16 @@ sub git_print_authorship_rows { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); print "$who" . format_search_author($co->{"${who}_name"}, $who, - esc_html($co->{"${who}_name"})) . " " . + esc_html($co->{"${who}_name"})) . " " . format_search_author($co->{"${who}_email"}, $who, - esc_html("<" . $co->{"${who}_email"} . ">")) . + esc_html("<" . $co->{"${who}_email"} . ">")) . "" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "\n" . "" . - " $wd{'rfc2822'}"; - print_local_time(%wd); - print "" . + "" . + format_timestamp_html(\%wd, -localtime=>1) . + "" . "\n"; } } @@ -5410,7 +5408,8 @@ sub git_summary { "description" . esc_html($descr) . "\n" . "owner" . esc_html($owner) . "\n"; if (defined $cd{'rfc2822'}) { - print "last change$cd{'rfc2822'}\n"; + print "last change" . + "".format_timestamp_html(\%cd)."\n"; } # use per project git URL list in $projectroot/$project/cloneurl From ce71b07632c71c2e6f4732abd3db6d59fb1bb924 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:08 +0200 Subject: [PATCH 08/11] gitweb: Unify the way long timestamp is displayed format_timestamp_html loses its "-localtime => 1" option, and now always print the local time (in author/comitter/tagger local timezone), with "atnight" warning if needed. This means that both 'summary' and 'log' views now display localtime. In the case of 'log' view this can be thought as an improvement, as now one can easily see which commits in a series are made "atnight" and should be examined closer. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7329db289c..67bcfe894e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3939,11 +3939,9 @@ sub git_print_section { } sub format_timestamp_html { - my ($date, %opts) = @_; + my $date = shift; my $strtime = $date->{'rfc2822'}; - return $strtime unless $opts{'-localtime'}; - my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) { $localtime_format = '(%02d:%02d %s)'; @@ -3965,7 +3963,7 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [".format_timestamp_html(\%ad, %opts)."]". + " [".format_timestamp_html(\%ad)."]". git_get_avatar($co->{'author_email'}, -pad_before => 1) . "\n"; } @@ -3992,7 +3990,7 @@ sub git_print_authorship_rows { "\n" . "" . "" . - format_timestamp_html(\%wd, -localtime=>1) . + format_timestamp_html(\%wd) . "" . "\n"; } From 291e52bd19877dc8fdd77079ba4c4326f114c461 Mon Sep 17 00:00:00 2001 From: John 'Warthog9' Hawley Date: Thu, 28 Apr 2011 21:04:09 +0200 Subject: [PATCH 09/11] gitweb: JavaScript ability to adjust time based on timezone This patch is based on Kevin Cernekee's patch series entitled "gitweb: introduce localtime feature". While Kevin's patch changed the server side output so that the timezone was output from gitweb itself, this has a number of drawbacks, in particular with respect to gitweb-caching. This patch takes the same basic goal, display the appropriate times in a given common timezone, and implements it in JavaScript. This requires adding / using a new class, "datetime", to be able to find elements to be adjusted from JavaScript. Appropriate dates are wrapped in a span with this class. Timezone to be used can be retrieved from "gitweb_tz" cookie, though currently there is no way to set / manipulate this cookie from gitweb; this is left for later commit. Valid timezones, currently, are: "utc", "local" (which means that timezone is taken from browser), and "+/-ZZZZ" numeric timezone as in RFC-2822. Default timezone is "local" (currently not configurable, left for later commit). Fallback (should JavaScript not be enabled) is to treat dates as they have been and display them, only, in UTC. Pages affected: * 'summary' view, "last change" field (commit time from latest change) * 'log' view, author time * 'commit' and 'commitdiff' views, author/committer time * 'tag' view, tagger time Based-on-code-from: Kevin Cernekee Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/gitweb.perl | 11 ++++-- gitweb/static/js/adjust-timezone.js | 60 +++++++++++++++++++++++++++++ gitweb/static/js/lib/datetime.js | 15 ++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 gitweb/static/js/adjust-timezone.js diff --git a/gitweb/Makefile b/gitweb/Makefile index 7dd1dee55a..5d20515fba 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -120,6 +120,7 @@ GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/lib/cookies.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js +GITWEB_JSLIB_FILES += static/js/adjust-timezone.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 67bcfe894e..6651946f54 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3732,9 +3732,14 @@ sub git_footer_html { qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!. qq! "!. href() .qq!");\n!. qq!\n!; - } elsif (gitweb_check_feature('javascript-actions')) { + } else { print qq!\n!; } @@ -3940,7 +3945,7 @@ sub git_print_section { sub format_timestamp_html { my $date = shift; - my $strtime = $date->{'rfc2822'}; + my $strtime = ''.$date->{'rfc2822'}.''; my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) { diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js new file mode 100644 index 0000000000..1577d780f0 --- /dev/null +++ b/gitweb/static/js/adjust-timezone.js @@ -0,0 +1,60 @@ +// Copyright (C) 2011, John 'Warthog9' Hawley +// 2011, Jakub Narebski + +/** + * @fileOverview Manipulate dates in gitweb output, adjusting timezone + * @license GPLv2 or later + */ + +/** + * Get common timezone and adjust dates to use this common timezone. + * + * This function is called during onload event (added to window.onload). + * + * @param {String} tzDefault: default timezone, if there is no cookie + * @param {String} tzCookieName: name of cookie to store timezone + * @param {String} tzClassName: denotes elements with date to be adjusted + */ +function onloadTZSetup(tzDefault, tzCookieName, tzClassName) { + var tzCookie = getCookie(tzCookieName); + var tz = tzCookie ? tzCookie : tzDefault; + + // server-side of gitweb produces datetime in UTC, + // so if tz is 'utc' there is no need for changes + if (tz !== 'utc') { + fixDatetimeTZ(tz, tzClassName); + } +} + + +/** + * Replace RFC-2822 dates contained in SPAN elements with tzClassName + * CSS class with equivalent dates in given timezone. + * + * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' + * @param {String} tzClassName: specifies elements to be changed + */ +function fixDatetimeTZ(tz, tzClassName) { + // sanity check, method should be ensured by common-lib.js + if (!document.getElementsByClassName) { + return; + } + + // translate to timezone in '(-|+)HHMM' format + tz = normalizeTimezoneInfo(tz); + + // NOTE: result of getElementsByClassName should probably be cached + var classesFound = document.getElementsByClassName(tzClassName, "span"); + for (var i = 0, len = classesFound.length; i < len; i++) { + var curElement = classesFound[i]; + + // we use *.firstChild.data (W3C DOM) instead of *.innerHTML + // as the latter doesn't always work everywhere in every browser + var epoch = parseRFC2822Date(curElement.firstChild.data); + var adjusted = formatDateRFC2882(epoch, tz); + + curElement.firstChild.data = adjusted; + } +} + +/* end of adjust-timezone.js */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js index b3dcedb141..f78c60a912 100644 --- a/gitweb/static/js/lib/datetime.js +++ b/gitweb/static/js/lib/datetime.js @@ -104,6 +104,21 @@ function formatTimezoneInfo(hours, minutes, sep) { return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0'); } +/** + * translate 'utc' and 'local' to numerical timezone + * @param {String} timezoneInfo: might be 'utc' or 'local' (browser) + */ +function normalizeTimezoneInfo(timezoneInfo) { + switch (timezoneInfo) { + case 'utc': + return '+0000'; + case 'local': // 'local' is browser timezone + return localTimezoneInfo(); + } + return timezoneInfo; +} + + /** * return date in local time formatted in iso-8601 like format * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' From 2ae8da2552f43802476676bb86b037e9028b7a7c Mon Sep 17 00:00:00 2001 From: John 'Warthog9' Hawley Date: Thu, 28 Apr 2011 21:04:10 +0200 Subject: [PATCH 10/11] gitweb.js: Add UI for selecting common timezone to display dates This will modify HTML, add CSS rules and add DOM event handlers so that clicking on any date (the common part, not the localtime part) will display a drop down menu to choose the timezone to change to. Currently menu displays only the following timezones: utc local -1200 -1100 ... +1100 +1200 +1300 +1400 In timezone selection menu each timezone is +1hr to the previous. The code is capable of handling fractional timezones, but those have not been added to the menu. All changes are saved to a cookie, so page changes and closing / reopening browser retains the last known timezone setting used. [jn: Changed from innerHTML to DOM, moved to event delegation for onclick to trigger menu, added close button and cookie refreshing] Helped-by: Kevin Cernekee Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +- gitweb/static/gitweb.css | 33 +++ gitweb/static/js/adjust-timezone.js | 298 ++++++++++++++++++++++++++-- gitweb/static/js/lib/common-lib.js | 27 ++- 4 files changed, 345 insertions(+), 16 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6651946f54..b1e80ef87b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3738,7 +3738,8 @@ sub git_footer_html { (gitweb_check_feature('javascript-actions') ? qq! fixLinks();\n! : ''). # last parameter to onloadTZSetup must be CSS class used by format_timestamp_html - qq! onloadTZSetup('local', 'gitweb_tz', 'datetime');\n!. + qq! var tz_cookie = { name: 'gitweb_tz', expires: 14, path: '/' };\n!. # in days + qq! onloadTZSetup('local', tz_cookie, 'datetime');\n!. qq!};\n!. qq!\n!; } diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 79d7eebba7..8dd093563e 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -579,6 +579,39 @@ div.remote { display: inline-block; } +/* JavaScript-based timezone manipulation */ + +.popup { /* timezone selection UI */ + position: absolute; + /* "top: 0; right: 0;" would be better, if not for bugs in browsers */ + top: 0; left: 0; + border: 1px solid; + padding: 2px; + background-color: #f0f0f0; + font-style: normal; + color: #000000; + cursor: auto; +} + +.close-button { /* close timezone selection UI without selecting */ + /* float doesn't work within absolutely positioned container, + * if width of container is not set explicitly */ + /* float: right; */ + position: absolute; + top: 0px; right: 0px; + border: 1px solid green; + margin: 1px 1px 1px 1px; + padding-bottom: 2px; + width: 12px; + height: 10px; + font-size: 9px; + font-weight: bold; + text-align: center; + background-color: #fff0f0; + cursor: pointer; +} + + /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ /* Highlighting theme definition: */ diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js index 1577d780f0..0c67779500 100644 --- a/gitweb/static/js/adjust-timezone.js +++ b/gitweb/static/js/adjust-timezone.js @@ -7,34 +7,51 @@ */ /** - * Get common timezone and adjust dates to use this common timezone. + * Get common timezone, add UI for changing timezones, and adjust + * dates to use requested common timezone. * * This function is called during onload event (added to window.onload). * * @param {String} tzDefault: default timezone, if there is no cookie - * @param {String} tzCookieName: name of cookie to store timezone + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to store timezone * @param {String} tzClassName: denotes elements with date to be adjusted */ -function onloadTZSetup(tzDefault, tzCookieName, tzClassName) { - var tzCookie = getCookie(tzCookieName); - var tz = tzCookie ? tzCookie : tzDefault; +function onloadTZSetup(tzDefault, tzCookieInfo, tzClassName) { + var tzCookieTZ = getCookie(tzCookieInfo.name, tzCookieInfo); + var tz = tzDefault; + + if (tzCookieTZ) { + // set timezone to value saved in a cookie + tz = tzCookieTZ; + // refresh cookie, so its expiration counts from last use of gitweb + setCookie(tzCookieInfo.name, tzCookieTZ, tzCookieInfo); + } + + // add UI for changing timezone + addChangeTZ(tz, tzCookieInfo, tzClassName); // server-side of gitweb produces datetime in UTC, // so if tz is 'utc' there is no need for changes - if (tz !== 'utc') { - fixDatetimeTZ(tz, tzClassName); - } + var nochange = tz === 'utc'; + + // adjust dates to use specified common timezone + fixDatetimeTZ(tz, tzClassName, nochange); } +/* ...................................................................... */ +/* Changing dates to use requested timezone */ + /** * Replace RFC-2822 dates contained in SPAN elements with tzClassName * CSS class with equivalent dates in given timezone. * * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' * @param {String} tzClassName: specifies elements to be changed + * @param {Boolean} nochange: markup for timezone change, but don't change it */ -function fixDatetimeTZ(tz, tzClassName) { +function fixDatetimeTZ(tz, tzClassName, nochange) { // sanity check, method should be ensured by common-lib.js if (!document.getElementsByClassName) { return; @@ -48,13 +65,266 @@ function fixDatetimeTZ(tz, tzClassName) { for (var i = 0, len = classesFound.length; i < len; i++) { var curElement = classesFound[i]; - // we use *.firstChild.data (W3C DOM) instead of *.innerHTML - // as the latter doesn't always work everywhere in every browser - var epoch = parseRFC2822Date(curElement.firstChild.data); - var adjusted = formatDateRFC2882(epoch, tz); + curElement.title = 'Click to change timezone'; + if (!nochange) { + // we use *.firstChild.data (W3C DOM) instead of *.innerHTML + // as the latter doesn't always work everywhere in every browser + var epoch = parseRFC2822Date(curElement.firstChild.data); + var adjusted = formatDateRFC2882(epoch, tz); - curElement.firstChild.data = adjusted; + curElement.firstChild.data = adjusted; + } } } + +/* ...................................................................... */ +/* Adding triggers, generating timezone menu, displaying and hiding */ + +/** + * Adds triggers for UI to change common timezone used for dates in + * gitweb output: it marks up and/or creates item to click to invoke + * timezone change UI, creates timezone UI fragment to be attached, + * and installs appropriate onclick trigger (via event delegation). + * + * @param {String} tzSelected: pre-selected timezone, + * 'utc' or 'local' or '(-|+)HHMM' + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzClassName: specifies elements to install trigger + */ +function addChangeTZ(tzSelected, tzCookieInfo, tzClassName) { + // make link to timezone UI discoverable + addCssRule('.'+tzClassName + ':hover', + 'text-decoration: underline; cursor: help;'); + + // create form for selecting timezone (to be saved in a cookie) + var tzSelectFragment = document.createDocumentFragment(); + tzSelectFragment = createChangeTZForm(tzSelectFragment, + tzSelected, tzCookieInfo, tzClassName); + + // event delegation handler for timezone selection UI (clicking on entry) + // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ + // assumes that there is no existing document.onclick handler + document.onclick = function onclickHandler(event) { + //IE doesn't pass in the event object + event = event || window.event; + + //IE uses srcElement as the target + var target = event.target || event.srcElement; + + switch (target.className) { + case tzClassName: + // don't display timezone menu if it is already displayed + if (tzSelectFragment.childNodes.length > 0) { + displayChangeTZForm(target, tzSelectFragment); + } + break; + } // end switch + }; +} + +/** + * Create DocumentFragment with UI for changing common timezone in + * which dates are shown in. + * + * @param {DocumentFragment} documentFragment: where attach UI + * @param {String} tzSelected: default (pre-selected) timezone + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @returns {DocumentFragment} + */ +function createChangeTZForm(documentFragment, tzSelected, tzCookieInfo, tzClassName) { + var div = document.createElement("div"); + div.className = 'popup'; + + /* '
X
' */ + var closeButton = document.createElement('div'); + closeButton.className = 'close-button'; + closeButton.title = '(click on this box to close)'; + closeButton.appendChild(document.createTextNode('X')); + closeButton.onclick = closeTZFormHandler(documentFragment, tzClassName); + div.appendChild(closeButton); + + /* 'Select timezone:
' */ + div.appendChild(document.createTextNode('Select timezone: ')); + var br = document.createElement('br'); + br.clear = 'all'; + div.appendChild(br); + + /* '' */ + var select = document.createElement("select"); + select.name = "tzoffset"; + //select.style.clear = 'all'; + select.appendChild(generateTZOptions(tzSelected)); + select.onchange = selectTZHandler(documentFragment, tzCookieInfo, tzClassName); + div.appendChild(select); + + documentFragment.appendChild(div); + + return documentFragment; +} + + +/** + * Hide (remove from DOM) timezone change UI, ensuring that it is not + * garbage collected and that it can be re-enabled later. + * + * @param {DocumentFragment} documentFragment: contains detached UI + * @param {HTMLSelectElement} target: select element inside of UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {DocumentFragment} documentFragment + */ +function removeChangeTZForm(documentFragment, target, tzClassName) { + // find containing element, where we appended timezone selection UI + // `target' is somewhere inside timezone menu + var container = target.parentNode, popup = target; + while (container && + container.className !== tzClassName) { + popup = container; + container = container.parentNode; + } + // safety check if we found correct container, + // and if it isn't deleted already + if (!container || !popup || + container.className !== tzClassName || + popup.className !== 'popup') { + return documentFragment; + } + + // timezone selection UI was appended as last child + // see also displayChangeTZForm function + var removed = popup.parentNode.removeChild(popup); + if (documentFragment.firstChild !== removed) { // the only child + // re-append it so it would be available for next time + documentFragment.appendChild(removed); + } + // all of inline style was added by this script + // it is not really needed to remove it, but it is a good practice + container.removeAttribute('style'); + + return documentFragment; +} + + +/** + * Display UI for changing common timezone for dates in gitweb output. + * To be used from 'onclick' event handler. + * + * @param {HTMLElement} target: where to install/display UI + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + */ +function displayChangeTZForm(target, tzSelectFragment) { + // for absolute positioning to be related to target element + target.style.position = 'relative'; + target.style.display = 'inline-block'; + + // show/display UI for changing timezone + target.appendChild(tzSelectFragment); +} + + +/* ...................................................................... */ +/* List of timezones for timezone selection menu */ + +/** + * Generate list of timezones for creating timezone select UI + * + * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' } + */ +function generateTZList() { + var timezones = [ + { value: "utc", descr: "UTC/GMT"}, + { value: "local", descr: "Local (per browser)"} + ]; + + // generate all full hour timezones (no fractional timezones) + for (var x = -12, idx = timezones.length; x <= +14; x++, idx++) { + var hours = (x >= 0 ? '+' : '-') + padLeft(x >=0 ? x : -x, 2); + timezones[idx] = { value: hours + '00', descr: 'UTC' + hours + ':00'}; + if (x === 0) { + timezones[idx].descr = 'UTC\u00B100:00'; // 'UTC±00:00' + } + } + + return timezones; +} + +/** + * Generate elements for timezone select UI + * + * @param {String} tzSelected: default timezone + * @returns {DocumentFragment} list of options elements to appendChild + */ +function generateTZOptions(tzSelected) { + var elems = document.createDocumentFragment(); + var timezones = generateTZList(); + + for (var i = 0, len = timezones.length; i < len; i++) { + var tzone = timezones[i]; + var option = document.createElement("option"); + if (tzone.value === tzSelected) { + option.defaultSelected = true; + } + option.value = tzone.value; + option.appendChild(document.createTextNode(tzone.descr)); + + elems.appendChild(option); + } + + return elems; +} + + +/* ...................................................................... */ +/* Event handlers and/or their generators */ + +/** + * Create event handler that select timezone and closes timezone select UI. + * To be used as $('select[name="tzselect"]').onchange handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to save result of selection + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function selectTZHandler(tzSelectFragment, tzCookieInfo, tzClassName) { + //return function selectTZ(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + var selected = target.options.item(target.selectedIndex); + removeChangeTZForm(tzSelectFragment, target, tzClassName); + + if (selected) { + selected.defaultSelected = true; + setCookie(tzCookieInfo.name, selected.value, tzCookieInfo); + fixDatetimeTZ(selected.value, tzClassName); + } + }; +} + +/** + * Create event handler that closes timezone select UI. + * To be used e.g. as $('.closebutton').onclick handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function closeTZFormHandler(tzSelectFragment, tzClassName) { + //return function closeTZForm(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + removeChangeTZForm(tzSelectFragment, target, tzClassName); + }; +} + /* end of adjust-timezone.js */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index b37139152d..018bbb7d4c 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -64,7 +64,7 @@ function padLeft(input, width, ch) { /* ............................................................ */ -/* Ajax */ +/* Handling browser incompatibilities */ /** * Create XMLHttpRequest object in cross-browser way @@ -88,6 +88,31 @@ function createRequestObject() { } +/** + * Insert rule giving specified STYLE to given SELECTOR at the end of + * first CSS stylesheet. + * + * @param {String} selector: CSS selector, e.g. '.class' + * @param {String} style: rule contents, e.g. 'background-color: red;' + */ +function addCssRule(selector, style) { + var stylesheet = document.styleSheets[0]; + + var theRules = []; + if (stylesheet.cssRules) { // W3C way + theRules = stylesheet.cssRules; + } else if (stylesheet.rules) { // IE way + theRules = stylesheet.rules; + } + + if (stylesheet.insertRule) { // W3C way + stylesheet.insertRule(selector + ' { ' + style + ' }', theRules.length); + } else if (stylesheet.addRule) { // IE way + stylesheet.addRule(selector, style); + } +} + + /* ............................................................ */ /* Support for legacy browsers */ From 2e987f92408f5ddd20246d7d9553b57d90767d54 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:11 +0200 Subject: [PATCH 11/11] gitweb: Make JavaScript ability to adjust timezones configurable Configure JavaScript-based ability to select common timezone for git dates via %feature mechanism, namely 'javascript-timezone' feature. The following settings are configurable: * default timezone (defaults to 'local' i.e. browser timezone); this also can function as a way to disable this ability, by setting it to false-ish value (undef or '') * name of cookie to store user's choice of timezone * class name to mark dates NOTE: This is a bit of abuse of %feature system, which can store only sequence of values, rather than dictionary (hash); usually but not always only a single value is used. Based-on-code-by: John 'Warthog9' Hawley Helped-by: Kevin Cernekee Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b1e80ef87b..ac335b6dea 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -480,6 +480,18 @@ our %feature = ( 'override' => 0, 'default' => [0]}, + # Enable and configure ability to change common timezone for dates + # in gitweb output via JavaScript. Enabled by default. + # Project specific override is not supported. + 'javascript-timezone' => { + 'override' => 0, + 'default' => [ + 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format, + # or undef to turn off this feature + 'gitweb_tz', # name of cookie where to store selected timezone + 'datetime', # CSS class used to mark up dates for manipulation + ]}, + # Syntax highlighting support. This is based on Daniel Svensson's # and Sham Chukoury's work in gitweb-xmms2.git. # It requires the 'highlight' program present in $PATH, @@ -3733,14 +3745,19 @@ sub git_footer_html { qq! "!. href() .qq!");\n!. qq!\n!; } else { + my ($jstimezone, $tz_cookie, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + print qq!\n!; } @@ -3946,7 +3963,13 @@ sub git_print_section { sub format_timestamp_html { my $date = shift; - my $strtime = ''.$date->{'rfc2822'}.''; + my $strtime = $date->{'rfc2822'}; + + my (undef, undef, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + if ($datetime_class) { + $strtime = qq!$strtime!; + } my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) {