Browse Source
* jn/gitweb-js: gitweb: Make JavaScript ability to adjust timezones configurable gitweb.js: Add UI for selecting common timezone to display dates gitweb: JavaScript ability to adjust time based on timezone gitweb: Unify the way long timestamp is displayed gitweb: Refactor generating of long dates into format_timestamp_html gitweb.js: Provide getElementsByClassName method (if it not exists) gitweb.js: Introduce code to handle cookies from JavaScript gitweb.js: Extract and improve datetime handling gitweb.js: Provide default values for padding in padLeftStr and padLeft gitweb.js: Update and improve comments in JavaScript files gitweb: Split JavaScript for maintability, combining on buildmaint

11 changed files with 1023 additions and 241 deletions
@ -0,0 +1,20 @@
@@ -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. |
@ -0,0 +1,330 @@
@@ -0,0 +1,330 @@
|
||||
// Copyright (C) 2011, John 'Warthog9' Hawley <warthog9@eaglescrag.net> |
||||
// 2011, Jakub Narebski <jnareb@gmail.com> |
||||
|
||||
/** |
||||
* @fileOverview Manipulate dates in gitweb output, adjusting timezone |
||||
* @license GPLv2 or later |
||||
*/ |
||||
|
||||
/** |
||||
* 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 {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, 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 |
||||
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, nochange) { |
||||
// 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]; |
||||
|
||||
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; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/* ...................................................................... */ |
||||
/* 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'; |
||||
|
||||
/* '<div class="close-button" title="(click on this box to close)">X</div>' */ |
||||
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: <br clear="all">' */ |
||||
div.appendChild(document.createTextNode('Select timezone: ')); |
||||
var br = document.createElement('br'); |
||||
br.clear = 'all'; |
||||
div.appendChild(br); |
||||
|
||||
/* '<select name="tzoffset"> |
||||
* ... |
||||
* <option value="-0700">UTC-07:00</option> |
||||
* <option value="-0600">UTC-06:00</option> |
||||
* ... |
||||
* </select>' */ |
||||
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 <options> 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 */ |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> |
||||
// 2007, Petr Baudis <pasky@suse.cz> |
||||
// 2008-2011, Jakub Narebski <jnareb@gmail.com> |
||||
|
||||
/** |
||||
* @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 */ |
@ -0,0 +1,224 @@
@@ -0,0 +1,224 @@
|
||||
// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> |
||||
// 2007, Petr Baudis <pasky@suse.cz> |
||||
// 2008-2011, Jakub Narebski <jnareb@gmail.com> |
||||
|
||||
/** |
||||
* @fileOverview Generic JavaScript code (helper functions) |
||||
* @license GPLv2 or later |
||||
*/ |
||||
|
||||
|
||||
/* ============================================================ */ |
||||
/* ............................................................ */ |
||||
/* Padding */ |
||||
|
||||
/** |
||||
* 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, 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) { |
||||
prefix += str; |
||||
width--; |
||||
} |
||||
return prefix + input; |
||||
} |
||||
|
||||
/** |
||||
* Pad INPUT on the left to WIDTH, using given padding character CH, |
||||
* 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, 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; |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
|
||||
/* ............................................................ */ |
||||
/* Handling browser incompatibilities */ |
||||
|
||||
/** |
||||
* 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; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 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 */ |
||||
|
||||
/** |
||||
* 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 */ |
||||
|
||||
/**#@+ |
||||
* @constant |
||||
*/ |
||||
var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; |
||||
var octEscRe = /^[0-7]{1,3}$/; |
||||
var maybeQuotedRe = /^\"(.*)\"$/; |
||||
/**#@-*/ |
||||
|
||||
/** |
||||
* 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 |
||||
* @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 */ |
@ -0,0 +1,114 @@
@@ -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 */ |
@ -0,0 +1,176 @@
@@ -0,0 +1,176 @@
|
||||
// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> |
||||
// 2007, Petr Baudis <pasky@suse.cz> |
||||
// 2008-2011, Jakub Narebski <jnareb@gmail.com> |
||||
|
||||
/** |
||||
* @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'); |
||||
} |
||||
|
||||
/** |
||||
* 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' |
||||
* |
||||
* @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 */ |
Loading…
Reference in new issue