|  | // 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 */ |