77 */
88
99/**
10- * Get common timezone and adjust dates to use this common timezone.
10+ * Get common timezone, add UI for changing timezones, and adjust
11+ * dates to use requested common timezone.
1112 *
1213 * This function is called during onload event (added to window.onload).
1314 *
1415 * @param {String } tzDefault: default timezone, if there is no cookie
15- * @param {String } tzCookieName: name of cookie to store timezone
16+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
17+ * @param {String } tzCookieInfo.name: name of cookie to store timezone
1618 * @param {String } tzClassName: denotes elements with date to be adjusted
1719 */
18- function onloadTZSetup ( tzDefault , tzCookieName , tzClassName ) {
19- var tzCookie = getCookie ( tzCookieName ) ;
20- var tz = tzCookie ? tzCookie : tzDefault ;
20+ function onloadTZSetup ( tzDefault , tzCookieInfo , tzClassName ) {
21+ var tzCookieTZ = getCookie ( tzCookieInfo . name , tzCookieInfo ) ;
22+ var tz = tzDefault ;
23+
24+ if ( tzCookieTZ ) {
25+ // set timezone to value saved in a cookie
26+ tz = tzCookieTZ ;
27+ // refresh cookie, so its expiration counts from last use of gitweb
28+ setCookie ( tzCookieInfo . name , tzCookieTZ , tzCookieInfo ) ;
29+ }
30+
31+ // add UI for changing timezone
32+ addChangeTZ ( tz , tzCookieInfo , tzClassName ) ;
2133
2234 // server-side of gitweb produces datetime in UTC,
2335 // so if tz is 'utc' there is no need for changes
24- if ( tz !== 'utc' ) {
25- fixDatetimeTZ ( tz , tzClassName ) ;
26- }
36+ var nochange = tz === 'utc' ;
37+
38+ // adjust dates to use specified common timezone
39+ fixDatetimeTZ ( tz , tzClassName , nochange ) ;
2740}
2841
2942
43+ /* ...................................................................... */
44+ /* Changing dates to use requested timezone */
45+
3046/**
3147 * Replace RFC-2822 dates contained in SPAN elements with tzClassName
3248 * CSS class with equivalent dates in given timezone.
3349 *
3450 * @param {String } tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local'
3551 * @param {String } tzClassName: specifies elements to be changed
52+ * @param {Boolean } nochange: markup for timezone change, but don't change it
3653 */
37- function fixDatetimeTZ ( tz , tzClassName ) {
54+ function fixDatetimeTZ ( tz , tzClassName , nochange ) {
3855 // sanity check, method should be ensured by common-lib.js
3956 if ( ! document . getElementsByClassName ) {
4057 return ;
@@ -48,13 +65,266 @@ function fixDatetimeTZ(tz, tzClassName) {
4865 for ( var i = 0 , len = classesFound . length ; i < len ; i ++ ) {
4966 var curElement = classesFound [ i ] ;
5067
51- // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
52- // as the latter doesn't always work everywhere in every browser
53- var epoch = parseRFC2822Date ( curElement . firstChild . data ) ;
54- var adjusted = formatDateRFC2882 ( epoch , tz ) ;
68+ curElement . title = 'Click to change timezone' ;
69+ if ( ! nochange ) {
70+ // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
71+ // as the latter doesn't always work everywhere in every browser
72+ var epoch = parseRFC2822Date ( curElement . firstChild . data ) ;
73+ var adjusted = formatDateRFC2882 ( epoch , tz ) ;
74+
75+ curElement . firstChild . data = adjusted ;
76+ }
77+ }
78+ }
79+
80+
81+ /* ...................................................................... */
82+ /* Adding triggers, generating timezone menu, displaying and hiding */
83+
84+ /**
85+ * Adds triggers for UI to change common timezone used for dates in
86+ * gitweb output: it marks up and/or creates item to click to invoke
87+ * timezone change UI, creates timezone UI fragment to be attached,
88+ * and installs appropriate onclick trigger (via event delegation).
89+ *
90+ * @param {String } tzSelected: pre-selected timezone,
91+ * 'utc' or 'local' or '(-|+)HHMM'
92+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
93+ * @param {String } tzClassName: specifies elements to install trigger
94+ */
95+ function addChangeTZ ( tzSelected , tzCookieInfo , tzClassName ) {
96+ // make link to timezone UI discoverable
97+ addCssRule ( '.' + tzClassName + ':hover' ,
98+ 'text-decoration: underline; cursor: help;' ) ;
99+
100+ // create form for selecting timezone (to be saved in a cookie)
101+ var tzSelectFragment = document . createDocumentFragment ( ) ;
102+ tzSelectFragment = createChangeTZForm ( tzSelectFragment ,
103+ tzSelected , tzCookieInfo , tzClassName ) ;
104+
105+ // event delegation handler for timezone selection UI (clicking on entry)
106+ // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/
107+ // assumes that there is no existing document.onclick handler
108+ document . onclick = function onclickHandler ( event ) {
109+ //IE doesn't pass in the event object
110+ event = event || window . event ;
111+
112+ //IE uses srcElement as the target
113+ var target = event . target || event . srcElement ;
114+
115+ switch ( target . className ) {
116+ case tzClassName :
117+ // don't display timezone menu if it is already displayed
118+ if ( tzSelectFragment . childNodes . length > 0 ) {
119+ displayChangeTZForm ( target , tzSelectFragment ) ;
120+ }
121+ break ;
122+ } // end switch
123+ } ;
124+ }
125+
126+ /**
127+ * Create DocumentFragment with UI for changing common timezone in
128+ * which dates are shown in.
129+ *
130+ * @param {DocumentFragment } documentFragment: where attach UI
131+ * @param {String } tzSelected: default (pre-selected) timezone
132+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
133+ * @returns {DocumentFragment }
134+ */
135+ function createChangeTZForm ( documentFragment , tzSelected , tzCookieInfo , tzClassName ) {
136+ var div = document . createElement ( "div" ) ;
137+ div . className = 'popup' ;
138+
139+ /* '<div class="close-button" title="(click on this box to close)">X</div>' */
140+ var closeButton = document . createElement ( 'div' ) ;
141+ closeButton . className = 'close-button' ;
142+ closeButton . title = '(click on this box to close)' ;
143+ closeButton . appendChild ( document . createTextNode ( 'X' ) ) ;
144+ closeButton . onclick = closeTZFormHandler ( documentFragment , tzClassName ) ;
145+ div . appendChild ( closeButton ) ;
146+
147+ /* 'Select timezone: <br clear="all">' */
148+ div . appendChild ( document . createTextNode ( 'Select timezone: ' ) ) ;
149+ var br = document . createElement ( 'br' ) ;
150+ br . clear = 'all' ;
151+ div . appendChild ( br ) ;
152+
153+ /* '<select name="tzoffset">
154+ * ...
155+ * <option value="-0700">UTC-07:00</option>
156+ * <option value="-0600">UTC-06:00</option>
157+ * ...
158+ * </select>' */
159+ var select = document . createElement ( "select" ) ;
160+ select . name = "tzoffset" ;
161+ //select.style.clear = 'all';
162+ select . appendChild ( generateTZOptions ( tzSelected ) ) ;
163+ select . onchange = selectTZHandler ( documentFragment , tzCookieInfo , tzClassName ) ;
164+ div . appendChild ( select ) ;
165+
166+ documentFragment . appendChild ( div ) ;
167+
168+ return documentFragment ;
169+ }
170+
171+
172+ /**
173+ * Hide (remove from DOM) timezone change UI, ensuring that it is not
174+ * garbage collected and that it can be re-enabled later.
175+ *
176+ * @param {DocumentFragment } documentFragment: contains detached UI
177+ * @param {HTMLSelectElement } target: select element inside of UI
178+ * @param {String } tzClassName: specifies element where UI was installed
179+ * @returns {DocumentFragment } documentFragment
180+ */
181+ function removeChangeTZForm ( documentFragment , target , tzClassName ) {
182+ // find containing element, where we appended timezone selection UI
183+ // `target' is somewhere inside timezone menu
184+ var container = target . parentNode , popup = target ;
185+ while ( container &&
186+ container . className !== tzClassName ) {
187+ popup = container ;
188+ container = container . parentNode ;
189+ }
190+ // safety check if we found correct container,
191+ // and if it isn't deleted already
192+ if ( ! container || ! popup ||
193+ container . className !== tzClassName ||
194+ popup . className !== 'popup' ) {
195+ return documentFragment ;
196+ }
55197
56- curElement . firstChild . data = adjusted ;
198+ // timezone selection UI was appended as last child
199+ // see also displayChangeTZForm function
200+ var removed = popup . parentNode . removeChild ( popup ) ;
201+ if ( documentFragment . firstChild !== removed ) { // the only child
202+ // re-append it so it would be available for next time
203+ documentFragment . appendChild ( removed ) ;
57204 }
205+ // all of inline style was added by this script
206+ // it is not really needed to remove it, but it is a good practice
207+ container . removeAttribute ( 'style' ) ;
208+
209+ return documentFragment ;
210+ }
211+
212+
213+ /**
214+ * Display UI for changing common timezone for dates in gitweb output.
215+ * To be used from 'onclick' event handler.
216+ *
217+ * @param {HTMLElement } target: where to install/display UI
218+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
219+ */
220+ function displayChangeTZForm ( target , tzSelectFragment ) {
221+ // for absolute positioning to be related to target element
222+ target . style . position = 'relative' ;
223+ target . style . display = 'inline-block' ;
224+
225+ // show/display UI for changing timezone
226+ target . appendChild ( tzSelectFragment ) ;
227+ }
228+
229+
230+ /* ...................................................................... */
231+ /* List of timezones for timezone selection menu */
232+
233+ /**
234+ * Generate list of timezones for creating timezone select UI
235+ *
236+ * @returns {Object[] } list of e.g. { value: '+0100', descr: 'GMT+01:00' }
237+ */
238+ function generateTZList ( ) {
239+ var timezones = [
240+ { value : "utc" , descr : "UTC/GMT" } ,
241+ { value : "local" , descr : "Local (per browser)" }
242+ ] ;
243+
244+ // generate all full hour timezones (no fractional timezones)
245+ for ( var x = - 12 , idx = timezones . length ; x <= + 14 ; x ++ , idx ++ ) {
246+ var hours = ( x >= 0 ? '+' : '-' ) + padLeft ( x >= 0 ? x : - x , 2 ) ;
247+ timezones [ idx ] = { value : hours + '00' , descr : 'UTC' + hours + ':00' } ;
248+ if ( x === 0 ) {
249+ timezones [ idx ] . descr = 'UTC\u00B100:00' ; // 'UTC±00:00'
250+ }
251+ }
252+
253+ return timezones ;
254+ }
255+
256+ /**
257+ * Generate <options> elements for timezone select UI
258+ *
259+ * @param {String } tzSelected: default timezone
260+ * @returns {DocumentFragment } list of options elements to appendChild
261+ */
262+ function generateTZOptions ( tzSelected ) {
263+ var elems = document . createDocumentFragment ( ) ;
264+ var timezones = generateTZList ( ) ;
265+
266+ for ( var i = 0 , len = timezones . length ; i < len ; i ++ ) {
267+ var tzone = timezones [ i ] ;
268+ var option = document . createElement ( "option" ) ;
269+ if ( tzone . value === tzSelected ) {
270+ option . defaultSelected = true ;
271+ }
272+ option . value = tzone . value ;
273+ option . appendChild ( document . createTextNode ( tzone . descr ) ) ;
274+
275+ elems . appendChild ( option ) ;
276+ }
277+
278+ return elems ;
279+ }
280+
281+
282+ /* ...................................................................... */
283+ /* Event handlers and/or their generators */
284+
285+ /**
286+ * Create event handler that select timezone and closes timezone select UI.
287+ * To be used as $('select[name="tzselect"]').onchange handler.
288+ *
289+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
290+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
291+ * @param {String } tzCookieInfo.name: name of cookie to save result of selection
292+ * @param {String } tzClassName: specifies element where UI was installed
293+ * @returns {Function } event handler
294+ */
295+ function selectTZHandler ( tzSelectFragment , tzCookieInfo , tzClassName ) {
296+ //return function selectTZ(event) {
297+ return function ( event ) {
298+ event = event || window . event ;
299+ var target = event . target || event . srcElement ;
300+
301+ var selected = target . options . item ( target . selectedIndex ) ;
302+ removeChangeTZForm ( tzSelectFragment , target , tzClassName ) ;
303+
304+ if ( selected ) {
305+ selected . defaultSelected = true ;
306+ setCookie ( tzCookieInfo . name , selected . value , tzCookieInfo ) ;
307+ fixDatetimeTZ ( selected . value , tzClassName ) ;
308+ }
309+ } ;
310+ }
311+
312+ /**
313+ * Create event handler that closes timezone select UI.
314+ * To be used e.g. as $('.closebutton').onclick handler.
315+ *
316+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
317+ * @param {String } tzClassName: specifies element where UI was installed
318+ * @returns {Function } event handler
319+ */
320+ function closeTZFormHandler ( tzSelectFragment , tzClassName ) {
321+ //return function closeTZForm(event) {
322+ return function ( event ) {
323+ event = event || window . event ;
324+ var target = event . target || event . srcElement ;
325+
326+ removeChangeTZForm ( tzSelectFragment , target , tzClassName ) ;
327+ } ;
58328}
59329
60330/* end of adjust-timezone.js */
0 commit comments