PageSourceSearch

https://forum.rccombat.ru/assets/javascript/core.js?assets_version=9

js rccombat.ru collected 2026-10-05 00:11:33 UTC 44,894 bytes, 1,668 lines download raw bytes

1/* global bbfontstyle */
2
3var phpbb = {};
4phpbb.alertTime = 100;
5
6(function($) {  // Avoid conflicts with other libraries
7
8'use strict';
9
10// define a couple constants for keydown functions.
11var keymap = {
12	TAB: 9,
13	ENTER: 13,
14	ESC: 27
15};
16
17var $dark = $('#darkenwrapper');
18var $loadingIndicator;
19var phpbbAlertTimer = null;
20
21phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined');
22
23/**
24 * Display a loading screen
25 *
26 * @returns {object} Returns loadingIndicator.
27 */
28phpbb.loadingIndicator = function() {
29	if (!$loadingIndicator) {
30		$loadingIndicator = $('<div />', { id: 'loading_indicator' });
31		$loadingIndicator.appendTo('#page-footer');
32	}
33
34	if (!$loadingIndicator.is(':visible')) {
35		$loadingIndicator.fadeIn(phpbb.alertTime);
36		// Wait fifteen seconds and display an error if nothing has been returned by then.
37		phpbb.clearLoadingTimeout();
38		phpbbAlertTimer = setTimeout(function() {
39			var $alert = $('#phpbb_alert');
40
41			if ($loadingIndicator.is(':visible')) {
42				phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req'));
43			}
44		}, 15000);
45	}
46
47	return $loadingIndicator;
48};
49
50/**
51 * Clear loading alert timeout
52*/
53phpbb.clearLoadingTimeout = function() {
54	if (phpbbAlertTimer !== null) {
55		clearTimeout(phpbbAlertTimer);
56		phpbbAlertTimer = null;
57	}
58};
59
60
61/**
62* Close popup alert after a specified delay
63*
64* @param {int} delay Delay in ms until darkenwrapper's click event is triggered
65*/
66phpbb.closeDarkenWrapper = function(delay) {
67	phpbbAlertTimer = setTimeout(function() {
68		$('#darkenwrapper').trigger('click');
69	}, delay);
70};
71
72/**
73 * Display a simple alert similar to JSs native alert().
74 *
75 * You can only call one alert or confirm box at any one time.
76 *
77 * @param {string} title Title of the message, eg "Information" (HTML).
78 * @param {string} msg Message to display (HTML).
79 *
80 * @returns {object} Returns the div created.
81 */
82phpbb.alert = function(title, msg) {
83	var $alert = $('#phpbb_alert');
84	$alert.find('.alert_title').html(title);
85	$alert.find('.alert_text').html(msg);
86
87	$(document).on('keydown.phpbb.alert', function(e) {
88		if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
89			phpbb.alert.close($alert, true);
90			e.preventDefault();
91			e.stopPropagation();
92		}
93	});
94	phpbb.alert.open($alert);
95
96	return $alert;
97};
98
99/**
100* Handler for opening an alert box.
101*
102* @param {jQuery} $alert jQuery object.
103*/
104phpbb.alert.open = function($alert) {
105	if (!$dark.is(':visible')) {
106		$dark.fadeIn(phpbb.alertTime);
107	}
108
109	if ($loadingIndicator && $loadingIndicator.is(':visible')) {
110		$loadingIndicator.fadeOut(phpbb.alertTime, function() {
111			$dark.append($alert);
112			$alert.fadeIn(phpbb.alertTime);
113		});
114	} else if ($dark.is(':visible')) {
115		$dark.append($alert);
116		$alert.fadeIn(phpbb.alertTime);
117	} else {
118		$dark.append($alert);
119		$alert.show();
120		$dark.fadeIn(phpbb.alertTime);
121	}
122
123	$alert.on('click', function(e) {
124		e.stopPropagation();
125	});
126
127	$dark.one('click', function(e) {
128		phpbb.alert.close($alert, true);
129		e.preventDefault();
130		e.stopPropagation();
131	});
132
133	$alert.find('.alert_close').one('click', function(e) {
134		phpbb.alert.close($alert, true);
135		e.preventDefault();
136	});
137};
138
139/**
140* Handler for closing an alert box.
141*
142* @param {jQuery} $alert jQuery object.
143* @param {bool} fadedark Whether to remove dark background.
144*/
145phpbb.alert.close = function($alert, fadedark) {
146	var $fade = (fadedark) ? $dark : $alert;
147
148	$fade.fadeOut(phpbb.alertTime, function() {
149		$alert.hide();
150	});
151
152	$alert.find('.alert_close').off('click');
153	$(document).off('keydown.phpbb.alert');
154};
155
156/**
157 * Display a simple yes / no box to the user.
158 *
159 * You can only call one alert or confirm box at any one time.
160 *
161 * @param {string} msg Message to display (HTML).
162 * @param {function} callback Callback. Bool param, whether the user pressed
163 *     yes or no (or whatever their language is).
164 * @param {bool} fadedark Remove the dark background when done? Defaults
165 *     to yes.
166 *
167 * @returns {object} Returns the div created.
168 */
169phpbb.confirm = function(msg, callback, fadedark) {
170	var $confirmDiv = $('#phpbb_confirm');
171	$confirmDiv.find('.alert_text').html(msg);
172	fadedark = fadedark || true;
173
174	$(document).on('keydown.phpbb.alert', function(e) {
175		if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
176			var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel';
177
178			$('input[name="' + name + '"]').trigger('click');
179			e.preventDefault();
180			e.stopPropagation();
181		}
182	});
183
184	$confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) {
185		var confirmed = this.name === 'confirm';
186
187		if (confirmed) {
188			callback(true);
189		}
190		$confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox');
191		phpbb.alert.close($confirmDiv, fadedark || !confirmed);
192
193		e.preventDefault();
194		e.stopPropagation();
195	});
196
197	phpbb.alert.open($confirmDiv);
198
199	return $confirmDiv;
200};
201
202/**
203 * Turn a querystring into an array.
204 *
205 * @argument {string} string The querystring to parse.
206 * @returns {object} The object created.
207 */
208phpbb.parseQuerystring = function(string) {
209	var params = {}, i, split;
210
211	string = string.split('&');
212	for (i = 0; i < string.length; i++) {
213		split = string[i].split('=');
214		params[split[0]] = decodeURIComponent(split[1]);
215	}
216	return params;
217};
218
219
220/**
221 * Makes a link use AJAX instead of loading an entire page.
222 *
223 * This function will work for links (both standard links and links which
224 * invoke confirm_box) and forms. It will be called automatically for links
225 * and forms with the data-ajax attribute set, and will call the necessary
226 * callback.
227 *
228 * For more info, view the following page on the phpBB wiki:
229 * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify
230 *
231 * @param {object} options Options.
232 */
233phpbb.ajaxify = function(options) {
234	var $elements = $(options.selector),
235		refresh = options.refresh,
236		callback = options.callback,
237		overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true,
238		isForm = $elements.is('form'),
239		isText = $elements.is('input[type="text"], textarea'),
240		eventName;
241
242	if (isForm) {
243		eventName = 'submit';
244	} else if (isText) {
245		eventName = 'keyup';
246	} else {
247		eventName = 'click';
248	}
249
250	$elements.on(eventName, function(event) {
251		var action, method, data, submit, that = this, $this = $(this);
252
253		if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') {
254			return;
255		}
256
257		/**
258		 * Handler for AJAX errors
259		 */
260		function errorHandler(jqXHR, textStatus, errorThrown) {
261			if (typeof console !== 'undefined' && console.log) {
262				console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown);
263			}
264			phpbb.clearLoadingTimeout();
265			var responseText, errorText = false;
266			try {
267				responseText = JSON.parse(jqXHR.responseText);
268				responseText = responseText.message;
269			} catch (e) {}
270			if (typeof responseText === 'string' && responseText.length > 0) {
271				errorText = responseText;
272			} else if (typeof errorThrown === 'string' && errorThrown.length > 0) {
273				errorText = errorThrown;
274			} else {
275				errorText = $dark.attr('data-ajax-error-text-' + textStatus);
276				if (typeof errorText !== 'string' || !errorText.length) {
277					errorText = $dark.attr('data-ajax-error-text');
278				}
279			}
280			phpbb.alert($dark.attr('data-ajax-error-title'), errorText);
281		}
282
283		/**
284		 * This is a private function used to handle the callbacks, refreshes
285		 * and alert. It calls the callback, refreshes the page if necessary, and
286		 * displays an alert to the user and removes it after an amount of time.
287		 *
288		 * It cannot be called from outside this function, and is purely here to
289		 * avoid repetition of code.
290		 *
291		 * @param {object} res The object sent back by the server.
292		 */
293		function returnHandler(res) {
294			var alert;
295
296			phpbb.clearLoadingTimeout();
297
298			// Is a confirmation required?
299			if (typeof res.S_CONFIRM_ACTION === 'undefined') {
300				// If a confirmation is not required, display an alert and call the
301				// callbacks.
302				if (typeof res.MESSAGE_TITLE !== 'undefined') {
303					alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT);
304				} else {
305					$dark.fadeOut(phpbb.alertTime);
306
307					if ($loadingIndicator) {
308						$loadingIndicator.fadeOut(phpbb.alertTime);
309					}
310				}
311
312				if (typeof phpbb.ajaxCallbacks[callback] === 'function') {
313					phpbb.ajaxCallbacks[callback].call(that, res);
314				}
315
316				// If the server says to refresh the page, check whether the page should
317				// be refreshed and refresh page after specified time if required.
318				if (res.REFRESH_DATA) {
319					if (typeof refresh === 'function') {
320						refresh = refresh(res.REFRESH_DATA.url);
321					} else if (typeof refresh !== 'boolean') {
322						refresh = false;
323					}
324
325					phpbbAlertTimer = setTimeout(function() {
326						if (refresh) {
327							window.location = res.REFRESH_DATA.url;
328						}
329
330						// Hide the alert even if we refresh the page, in case the user
331						// presses the back button.
332						$dark.fadeOut(phpbb.alertTime, function() {
333							if (typeof alert !== 'undefined') {
334								alert.hide();
335							}
336						});
337					}
337, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds
338				}
339			} else {
340				// If confirmation is required, display a dialog to the user.
341				phpbb.confirm(res.MESSAGE_BODY, function(del) {
342					if (!del) {
343						return;
344					}
345
346					phpbb.loadingIndicator();
347					data =  $('<form>' + res.S_HIDDEN_FIELDS + '</form>').serialize();
348					$.ajax({
349						url: res.S_CONFIRM_ACTION,
350						type: 'POST',
351						data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(),
352						success: returnHandler,
353						error: errorHandler
354					});
355				}, false);
356			}
357		}
358
359		// If the element is a form, POST must be used and some extra data must
360		// be taken from the form.
361		var runFilter = (typeof options.filter === 'function');
362		data = {};
363
364		if (isForm) {
365			action = $this.attr('action').replace('&amp;', '&');
366			data = $this.serializeArray();
367			method = $this.attr('method') || 'GET';
368
369			if ($this.find('input[type="submit"][data-clicked]')) {
370				submit = $this.find('input[type="submit"][data-clicked]');
371				data.push({
372					name: submit.attr('name'),
373					value: submit.val()
374				});
375			}
376		} else if (isText) {
377			var name = $this.attr('data-name') || this.name;
378			action = $this.attr('data-url').replace('&amp;', '&');
379			data[name] = this.value;
380			method = 'POST';
381		} else {
382			action = this.href;
383			data = null;
384			method = 'GET';
385		}
386
387		var sendRequest = function() {
388			var dataOverlay = $this.attr('data-overlay');
389			if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) {
390				phpbb.loadingIndicator();
391			}
392
393			var request = $.ajax({
394				url: action,
395				type: method,
396				data: data,
397				success: returnHandler,
398				error: errorHandler,
399				cache: false
400			});
401
402			request.always(function() {
403				if ($loadingIndicator && $loadingIndicator.is(':visible')) {
404					$loadingIndicator.fadeOut(phpbb.alertTime);
405				}
406			});
407		};
408
409		// If filter function returns false, cancel the AJAX functionality,
410		// and return true (meaning that the HTTP request will be sent normally).
411		if (runFilter && !options.filter.call(this, data, event, sendRequest)) {
412			return;
413		}
414
415		sendRequest();
416		event.preventDefault();
417	});
418
419	if (isForm) {
420		$elements.find('input:submit').click(function () {
421			var $this = $(this);
422
423			// Remove data-clicked attribute from any submit button of form
424			$this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked');
425
426			$this.attr('data-clicked', 'true');
427		});
428	}
429
430	return this;
431};
432
433phpbb.search = {
434	cache: {
435		data: []
436	},
437	tpl: [],
438	container: []
439};
440
441/**
442 * Get cached search data.
443 *
444 * @param {string} id Search ID.
445 * @returns {bool|object} Cached data object. Returns false if no data exists.
446 */
447phpbb.search.cache.get = function(id) {
448	if (this.data[id]) {
449		return this.data[id];
450	}
451	return false;
452};
453
454/**
455 * Set search cache data value.
456 *
457 * @param {string} id		Search ID.
458 * @param {string} key		Data key.
459 * @param {string} value	Data value.
460 */
461phpbb.search.cache.set = function(id, key, value) {
462	if (!this.data[id]) {
463		this.data[id] = { results: [] };
464	}
465	this.data[id][key] = value;
466};
467
468/**
469 * Cache search result.
470 *
471 * @param {string} id		Search ID.
472 * @param {string} keyword	Keyword.
473 * @param {Array} results	Search results.
474 */
475phpbb.search.cache.setResults = function(id, keyword, results) {
476	this.data[id].results[keyword] = results;
477};
478
479/**
480 * Trim spaces from keyword and lower its case.
481 *
482 * @param {string} keyword Search keyword to clean.
483 * @returns {string} Cleaned string.
484 */
485phpbb.search.cleanKeyword = function(keyword) {
486	return $.trim(keyword).toLowerCase();
487};
488
489/**
490 * Get clean version of search keyword. If textarea supports several keywords
491 * (one per line), it fetches the current keyword based on the caret position.
492 *
493 * @param {jQuery} $input	Search input|textarea.
494 * @param {string} keyword	Input|textarea value.
495 * @param {bool} multiline	Whether textarea supports multiple search keywords.
496 *
497 * @returns string Clean string.
498 */
499phpbb.search.getKeyword = function($input, keyword, multiline) {
500	if (multiline) {
501		var line = phpbb.search.getKeywordLine($input);
502		keyword = keyword.split('\n').splice(line, 1);
503	}
504	return phpbb.search.cleanKeyword(keyword);
505};
506
507/**
508 * Get the textarea line number on which the keyword resides - for textareas
509 * that support multiple keywords (one per line).
510 *
511 * @param {jQuery} $textarea Search textarea.
512 * @returns {int} The line number.
513 */
514phpbb.search.getKeywordLine = function ($textarea) {
515	var selectionStart = $textarea.get(0).selectionStart;
516	return $textarea.val().substr(0, selectionStart).split('\n').length - 1;
517};
518
519/**
520 * Set the value on the input|textarea. If textarea supports multiple
521 * keywords, only the active keyword is replaced.
522 *
523 * @param {jQuery} $input	Search input|textarea.
524 * @param {string} value	Value to set.
525 * @param {bool} multiline	Whether textarea supports multiple search keywords.
526 */
527phpbb.search.setValue = function($input, value, multiline) {
528	if (multiline) {
529		var line = phpbb.search.getKeywordLine($input),
530			lines = $input.val().split('\n');
531		lines[line] = value;
532		value = lines.join('\n');
533	}
534	$input.val(value);
535};
536
537/**
538 * Sets the onclick event to set the value on the input|textarea to the
539 * selected search result.
540 *
541 * @param {jQuery} $input		Search input|textarea.
542 * @param {object} value		Result object.
543 * @param {jQuery} $row			Result element.
544 * @param {jQuery} $container	jQuery object for the search container.
545 */
546phpbb.search.setValueOnClick = function($input, value, $row, $container) {
547	$row.click(function() {
548		phpbb.search.setValue($input, value.result, $input.attr('data-multiline'));
549		$container.hide();
550	});
551};
552
553/**
554 * Runs before the AJAX search request is sent and determines whether
555 * there is a need to contact the server. If there are cached results
556 * already, those are displayed instead. Executes the AJAX request function
557 * itself due to the need to use a timeout to limit the number of requests.
558 *
559 * @param {Array} data				Data to be sent to the server.
560 * @param {object} event			Onkeyup event object.
561 * @param {function} sendRequest	Function to execute AJAX request.
562 *
563 * @returns {bool} Returns false.
564 */
565phpbb.search.filter = function(data, event, sendRequest) {
566	var $this = $(this),
567		dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'),
568		minLength = parseInt($this.attr('data-min-length'), 10),
569		searchID = $this.attr('data-results'),
570		keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')),
571		cache = phpbb.search.cache.get(searchID),
572		proceed = true;
573	data[dataName] = keyword;
574
575	if (cache.timeout) {
576		clearTimeout(cache.timeout);
577	}
578
579	var timeout = setTimeout(function() {
580		// Check min length and existence of cache.
581		if (minLength > keyword.length) {
582			proceed = false;
583		} else if (cache.lastSearch) {
584			// Has the keyword actually changed?
585			if (cache.lastSearch === keyword) {
586				proceed = false;
587			} else {
588				// Do we already have results for this?
589				if (cache.results[keyword]) {
590					var response = {
591						keyword: keyword,
592						results: cache.results[keyword]
593					};
594					phpbb.search.handleResponse(response, $this, true);
595					proceed = false;
596				}
597
598				// If the previous search didn't yield results and the string only had characters added to it,
599				// then we won't bother sending a request.
600				if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) {
601					phpbb.search.cache.set(searchID, 'lastSearch', keyword);
602					phpbb.search.cache.setResults(searchID, keyword, []);
603					proceed = false;
604				}
605			}
606		}
607
608		if (proceed) {
609			sendRequest.call(this);
610		}
611	}, 350);
612	phpbb.search.cache.set(searchID, 'timeout', timeout);
613
614	return false;
615};
616
617/**
618 * Handle search result response.
619 *
620 * @param {object} res			Data received from server.
621 * @param {jQuery} $input		Search input|textarea.
622 * @param {bool} fromCache		Whether the results are from the cache.
623 * @param {function} callback	Optional callback to run when assigning each search result.
624 */
625phpbb.search.handleResponse = function(res, $input, fromCache, callback) {
626	if (typeof res !== 'object') {
627		return;
628	}
629
630	var searchID = $input.attr('data-results'),
631		$container = $(searchID);
632
633	if (this.cache.get(searchID).callback) {
634		callback = this.cache.get(searchID).callback;
635	} else if (typeof callback === 'function') {
636		this.cache.set(searchID, 'callback', callback);
637	}
638
639	if (!fromCache) {
640		this.cache.setResults(searchID, res.keyword, res.results);
641	}
642
643	this.cache.set(searchID, 'lastSearch', res.keyword);
644	this.showResults(res.results, $input, $container, callback);
645};
646
647/**
648 * Show search results.
649 *
650 * @param {Array} results		Search results.
651 * @param {jQuery} $input		Search input|textarea.
652 * @param {jQuery} $container	Search results container element.
653 * @param {function} callback	Optional callback to run when assigning each search result.
654 */
655phpbb.search.showResults = function(results, $input, $container, callback) {
656	var $resultContainer = $('.search-results', $container);
657	this.clearResults($resultContainer);
658
659	if (!results.length) {
660		$container.hide();
661		return;
662	}
663
664	var searchID = $container.attr('id'),
665		tpl,
666		row;
667
668	if (!this.tpl[searchID]) {
669		tpl = $('.search-result-tpl', $container);
670		this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl');
671		tpl.remove();
672	}
673	tpl = this.tpl[searchID];
674
675	$.each(results, function(i, item) {
676		row = tpl.clone();
677		row.find('.search-result').html(item.display);
678
679		if (typeof callback === 'function') {
680			callback.call(this, $input, item, row, $container);
681		}
682		row.appendTo($resultContainer).show();
683	});
684	$container.show();
685};
686
687/**
688 * Clear search results.
689 *
690 * @param {jQuery} $container Search results container.
691 */
692phpbb.search.clearResults = function($container) {
693	$container.children(':not(.search-result-tpl)').remove();
694};
695
696$('#phpbb').click(function() {
697	var $this = $(this);
698
699	if (!$this.is('.live-search') && !$this.parents().is('.live-search')) {
700		$('.live-search').hide();
701	}
702});
703
704phpbb.history = {};
705
706/**
707* Check whether a method in the native history object is supported.
708*
709* @param {string} fn Method name.
710* @returns {bool} Returns true if the method is supported.
711*/
712phpbb.history.isSupported = function(fn) {
713	return !(typeof history === 'undefined' || typeof history[fn] === 'undefined');
714};
715
716/**
717* Wrapper for the pushState and replaceState methods of the
718* native history object.
719*
720* @param {string} mode		Mode. Either push or replace.
721* @param {string} url		New URL.
722* @param {string} [title]	Optional page title.
723* @param {object} [obj]		Optional state object.
724*/
725phpbb.history.alterUrl = function(mode, url, title, obj) {
726	var fn = mode + 'State';
727
728	if (!url || !phpbb.history.isSupported(fn)) {
729		return;
730	}
731	if (!title) {
732		title = document.title;
733	}
734	if (!obj) {
735		obj = null;
736	}
737
738	history[fn](obj, title, url);
739};
740
741/**
742* Wrapper for the native history.replaceState method.
743*
744* @param {string} url		New URL.
745* @param {string} [title]	Optional page title.
746* @param {object} [obj]		Optional state object.
747*/
748phpbb.history.replaceUrl = function(url, title, obj) {
749	phpbb.history.alterUrl('replace', url, title, obj);
750};
751
752/**
753* Wrapper for the native history.pushState method.
754*
755* @param {string} url		New URL.
756* @param {string} [title]	Optional page title.
757* @param {object} [obj]		Optional state object.
758*/
759phpbb.history.pushUrl = function(url, title, obj) {
760	phpbb.history.alterUrl('push', url, title, obj);
761};
762
763/**
764* Hide the optgroups that are not the selected timezone
765*
766* @param {bool} keepSelection Shall we keep the value selected, or shall the
767* 	user be forced to repick one.
768*/
769phpbb.timezoneSwitchDate = function(keepSelection) {
770	var $timezoneCopy = $('#timezone_copy');
771	var $timezone = $('#timezone');
772	var $tzDate = $('#tz_date');
773	var $tzSelectDateSuggest = $('#tz_select_date_suggest');
774
775	if ($timezoneCopy.length === 0) {
776		// We make a backup of the original dropdown, so we can remove optgroups
777		// instead of setting display to none, because IE and chrome will not
778		// hide options inside of optgroups and selects via css
779		$timezone.clone()
780			.attr('id', 'timezone_copy')
781			.css('display', 'none')
782			.attr('name', 'tz_copy')
783			.insertAfter('#timezone');
784	} else {
785		// Copy the content of our backup, so we can remove all unneeded options
786		$timezone.html($timezoneCopy.html());
787	}
788
789	if ($tzDate.val() !== '') {
790		$timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])');
791	}
792
793	if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) {
794		$tzSelectDateSuggest.css('display', 'none');
795	} else {
796		$tzSelectDateSuggest.css('display', 'inline');
797	}
798
799	var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option');
800
801	if ($tzOptions.length === 1) {
802		// If there is only one timezone for the selected date, we just select that automatically.
803		$tzOptions.prop('selected', true);
804		keepSelection = true;
805	}
806
807	if (typeof keepSelection !== 'undefined' && !keepSelection) {
808		var $timezoneOptions = $timezone.find('optgroup option');
809		if ($timezoneOptions.filter(':selected').length <= 0) {
810			$timezoneOptions.filter(':first').prop('selected', true);
811		}
812	}
813};
814
815/**
816* Display the date/time select
817*/
818phpbb.timezoneEnableDateSelection = function() {
819	$('#tz_select_date').css('display', 'block');
820};
821
822/**
823* Preselect a date/time or suggest one, if it is not picked.
824*
825* @param {bool} forceSelector Shall we select the suggestion?
826*/
827phpbb.timezonePreselectSelect = function(forceSelector) {
828
829	// The offset returned here is in minutes and negated.
830	var offset = (new Date()).getTimezoneOffset();
831	var sign = '-';
832
833	if (offset < 0) {
834		sign = '+';
835		offset = -offset;
836	}
837
838	var minutes = offset % 60;
839	var hours = (offset - minutes) / 60;
840
841	if (hours < 10) {
842		hours = '0' + hours.toString();
843	} else {
844		hours = hours.toString();
845	}
846
847	if (minutes < 10) {
848		minutes = '0' + minutes.toString();
849	} else {
850		minutes = minutes.toString();
851	}
852
853	var prefix = 'UTC' + sign + hours + ':' + minutes;
854	var prefixLength = prefix.length;
855	var selectorOptions = $('option', '#tz_date');
856	var i;
857
858	var $tzSelectDateSuggest = $('#tz_select_date_suggest');
859
860	for (i = 0; i < selectorOptions.length; ++i) {
861		var option = selectorOptions[i];
862
863		if (option.value.substring(0, prefixLength) === prefix) {
864			if ($('#tz_date').val() !== option.value && !forceSelector) {
865				// We do not select the option for the user, but notify him,
866				// that we would suggest a different setting.
867				phpbb.timezoneSwitchDate(true);
868				$tzSelectDateSuggest.css('display', 'inline');
869			} else {
870				option.selected = true;
871				phpbb.timezoneSwitchDate(!forceSelector);
872				$tzSelectDateSuggest.css('display', 'none');
873			}
874
875			var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion');
876
877			$tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML));
878			$tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9)));
879			$tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML);
880
881			// Found the suggestion, there cannot be more, so return from here.
882			return;
883		}
884	}
885};
886
887phpbb.ajaxCallbacks = {};
888
889/**
890 * Adds an AJAX callback to be used by phpbb.ajaxify.
891 *
892 * See the phpbb.ajaxify comments for information on stuff like parameters.
893 *
894 * @param {string} id The name of the callback.
895 * @param {function} callback The callback to be called.
896 */
897phpbb.addAjaxCallback = function(id, callback) {
898	if (typeof callback === 'function') {
899		phpbb.ajaxCallbacks[id] = callback;
900	}
901	return this;
902};
903
904/**
905 * This callback handles live member searches.
906 */
907phpbb.addAjaxCallback('member_search', function(res) {
908	phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick'));
909});
910
911/**
912 * This callback alternates text - it replaces the current text with the text in
913 * the alt-text data attribute, and replaces the text in the attribute with the
914 * current text so that the process can be repeated.
915 */
916phpbb.addAjaxCallback('alt_text', function() {
917	var $anchor,
918		updateAll = $(this).data('update-all'),
919		altText;
920
921	if (updateAll !== undefined && updateAll.length) {
922		$anchor = $(updateAll);
923	} else {
924		$anchor = $(this);
925	}
926
927	$anchor.each(function() {
928		var $this = $(this);
929		altText = $this.attr('data-alt-text');
930		$this.attr('data-alt-text', $this.text());
931		$this.attr('title', $.trim(altText));
932		$this.text(altText);
933	});
934});
935
936/**
937 * This callback is based on the alt_text callback.
938 *
939 * It replaces the current text with the text in the alt-text data attribute,
940 * and replaces the text in the attribute with the current text so that the
941 * process can be repeated.
942 * Additionally it replaces the class of the link's parent
943 * and changes the link itself.
944 */
945phpbb.addAjaxCallback('toggle_link', function() {
946	var $anchor,
947		updateAll = $(this).data('update-all') ,
948		toggleText,
949		toggleUrl,
950		toggleClass;
951
952	if (updateAll !== undefined && updateAll.length) {
953		$anchor = $(updateAll);
954	} else {
955		$anchor = $(this);
956	}
957
958	$anchor.each(function() {
959		var $this = $(this);
960
961		// Toggle link text
962		toggleText = $this.attr('data-toggle-text');
963		$this.attr('data-toggle-text', $this.text());
964		$this.attr('title', $.trim(toggleText));
965		$this.text(toggleText);
966
967		// Toggle link url
968		toggleUrl = $this.attr('data-toggle-url');
969		$this.attr('data-toggle-url', $this.attr('href'));
970		$this.attr('href', toggleUrl);
971
972		// Toggle class of link parent
973		toggleClass = $this.attr('data-toggle-class');
974		$this.attr('data-toggle-class', $this.parent().attr('class'));
975		$this.parent().attr('class', toggleClass);
976	});
977});
978
979/**
980* Automatically resize textarea
981*
982* This function automatically resizes textarea elements when user
983* types text.
984*
985* @param {jQuery} $items jQuery object(s) to resize
986* @param {object} [options] Optional parameter that adjusts default
987* 	configuration. See configuration variable
988*
989* Optional parameters:
990*	minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500
991*	minHeight {number} Minimum height of textarea. Default = 200
992*	maxHeight {number} Maximum height of textarea. Default = 500
993*	heightDiff {number} Minimum difference between window and textarea height. Default = 200
994*	resizeCallback {function} Function to call after resizing textarea
995*	resetCallback {function} Function to call when resize has been canceled
996
997*		Callback function format: function(item) {}
998*			this points to DOM object
999*			item is a jQuery object, same as this
1000*/
1001phpbb.resizeTextArea = function($items, options) {
1002	// Configuration
1003	var configuration = {
1004		minWindowHeight: 500,
1005		minHeight: 200,
1006		maxHeight: 500,
1007		heightDiff: 200,
1008		resizeCallback: function() {},
1009		resetCallback: function() {}
1010	};
1011
1012	if (phpbb.isTouch) {
1013		return;
1014	}
1015
1016	if (arguments.length > 1) {
1017		configuration = $.extend(configuration, options);
1018	}
1019
1020	function resetAutoResize(item) {
1021		var $item = $(item);
1022		if ($item.hasClass('auto-resized')) {
1023			$(item)
1024				.css({ height: '', resize: '' })
1025				.removeClass('auto-resized');
1026			configuration.resetCallback.call(item, $item);
1027		}
1028	}
1029
1030	function autoResize(item) {
1031		function setHeight(height) {
1032			height += parseInt($item.css('height'), 10) - $item.innerHeight();
1033			$item
1034				.css({ height: height + 'px', resize: 'none' })
1035				.addClass('auto-resized');
1036			configuration.resizeCallback.call(item, $item);
1037		}
1038
1039		var windowHeight = $(window).height();
1040
1041		if (windowHeight < configuration.minWindowHeight) {
1042			resetAutoResize(item);
1043			return;
1044		}
1045
1046		var maxHeight = Math.min(
1047				Math.max(windowHeight - configuration.heightDiff, configuration.minHeight),
1048				configuration.maxHeight
1049			),
1050			$item = $(item),
1051			height = parseInt($item.innerHeight(), 10),
1052			scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0;
1053
1054		if (height < 0) {
1055			return;
1056		}
1057
1058		if (height > maxHeight) {
1059			setHeight(maxHeight);
1060		} else if (scrollHeight > (height + 5)) {
1061			setHeight(Math.min(maxHeight, scrollHeight));
1062		}
1063	}
1064
1065	$items.on('focus change keyup', function() {
1066		$(this).each(function() {
1067			autoResize(this);
1068		});
1069	}).change();
1070
1071	$(window).resize(function() {
1072		$items.each(function() {
1073			if ($(this).hasClass('auto-resized')) {
1074				autoResize(this);
1075			}
1076		});
1077	});
1078};
1079
1080/**
1081* Check if cursor in textarea is currently inside a bbcode tag
1082*
1083* @param {object} textarea Textarea DOM object
1084* @param {Array} startTags List of start tags to look for
1085*		For example, Array('[code]', '[code=')
1086* @param {Array} endTags List of end tags to look for
1087*		For example, Array('[/code]')
1088*
1089* @returns {boolean} True if cursor is in bbcode tag
1090*/
1091phpbb.inBBCodeTag = function(textarea, startTags, endTags) {
1092	var start = textarea.selectionStart,
1093		lastEnd = -1,
1094		lastStart = -1,
1095		i, index, value;
1096
1097	if (typeof start !== 'number') {
1098		return false;
1099	}
1100
1101	value = textarea.value.toLowerCase();
1102
1103	for (i = 0; i < startTags.length; i++) {
1104		var tagLength = startTags[i].length;
1105		if (start >= tagLength) {
1106			index = value.lastIndexOf(startTags[i], start - tagLength);
1107			lastStart = Math.max(lastStart, index);
1108		}
1109	}
1110	if (lastStart === -1) {
1111		return false;
1112	}
1113
1114	if (start > 0) {
1115		for (i = 0; i < endTags.length; i++) {
1116			index = value.lastIndexOf(endTags[i], start - 1);
1117			lastEnd = Math.max(lastEnd, index);
1118		}
1119	}
1120
1121	return (lastEnd < lastStart);
1122};
1123
1124
1125/**
1126* Adjust textarea to manage code bbcode
1127*
1128* This function allows to use tab characters when typing code
1129* and keeps indentation of previous line of code when adding new
1130* line while typing code.
1131*
1132* Editor's functionality is changed only when cursor is between
1133* [code] and [/code] bbcode tags.
1134*
1135* @param {object} textarea Textarea DOM object to apply editor to
1136*/
1137phpbb.applyCodeEditor = function(textarea) {
1138	// list of allowed start and end bbcode code tags, in lower case
1139	var startTags = ['[code]', '[code='],
1140		startTagsEnd = ']',
1141		endTags = ['[/code]'];
1142
1143	if (!textarea || typeof textarea.selectionStart !== 'number') {
1144		return;
1145	}
1146
1147	if ($(textarea).data('code-editor') === true) {
1148		return;
1149	}
1150
1151	function inTag() {
1152		return phpbb.inBBCodeTag(textarea, startTags, endTags);
1153	}
1154
1155	/**
1156	* Get line of text before cursor
1157	*
1158	* @param {boolean} stripCodeStart If true, only part of line
1159	*		after [code] tag will be returned.
1160	*
1161	* @returns {string} Line of text
1162	*/
1163	function getLastLine(stripCodeStart) {
1164		var start = textarea.selectionStart,
1165			value = textarea.value,
1166			index = value.lastIndexOf('\n', start - 1);
1167
1168		value = value.substring(index + 1, start);
1169
1170		if (stripCodeStart) {
1171			for (var i = 0; i < startTags.length; i++) {
1172				index = value.lastIndexOf(startTags[i]);
1173				if (index >= 0) {
1174					var tagLength = startTags[i].length;
1175
1176					value = value.substring(index + tagLength);
1177					if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) {
1178						index = value.indexOf(startTagsEnd);
1179
1180						if (index >= 0) {
1181							value = value.substr(index + 1);
1182						}
1183					}
1184				}
1185			}
1186		}
1187
1188		return value;
1189	}
1190
1191	/**
1192	* Append text at cursor position
1193	*
1194	* @param {string} text Text to append
1195	*/
1196	function appendText(text) {
1197		var start = textarea.selectionStart,
1198			end = textarea.selectionEnd,
1199			value = textarea.value;
1200
1201		textarea.value = value.substr(0, start) + text + value.substr(end);
1202		textarea.selectionStart = textarea.selectionEnd = start + text.length;
1203	}
1204
1205	$(textarea).data('code-editor', true).on('keydown', function(event) {
1206		var key = event.keyCode || event.which;
1207
1208		// intercept tabs
1209		if (key === keymap.TAB	&&
1210			!event.ctrlKey		&&
1211			!event.shiftKey		&&
1212			!event.altKey		&&
1213			!event.metaKey) {
1214			if (inTag()) {
1215				appendText('\t');
1216				event.preventDefault();
1217				return;
1218			}
1219		}
1220
1221		// intercept new line characters
1222		if (key === keymap.ENTER) {
1223			if (inTag()) {
1224				var lastLine = getLastLine(true),
1225					code = '' + /^\s*/g.exec(lastLine);
1226
1227				if (code.length > 0) {
1228					appendText('\n' + code);
1229					event.preventDefault();
1230				}
1231			}
1232		}
1233	});
1234};
1235
1236/**
1237 * Show drag and drop animation when textarea is present
1238 *
1239 * This function will enable the drag and drop animation for a specified
1240 * textarea.
1241 *
1242 * @param {HTMLElement} textarea Textarea DOM object to apply editor to
1243 */
1244phpbb.showDragNDrop = function(textarea) {
1245	if (!textarea) {
1246		return;
1247	}
1248
1249	$('body').on('dragenter dragover', function () {
1250		$(textarea).addClass('drag-n-drop');
1251	}).on('dragleave dragout dragend drop', function() {
1252		$(textarea).removeClass('drag-n-drop');
1253	});
1254	$(textarea).on('dragenter dragover', function () {
1255		$(textarea).addClass('drag-n-drop-highlight');
1256	}).on('dragleave dragout dragend drop', function() {
1257		$(textarea).removeClass('drag-n-drop-highlight');
1258	});
1259};
1260
1261/**
1262* List of classes that toggle dropdown menu,
1263* list of classes that contain visible dropdown menu
1264*
1265* Add your own classes to strings with comma (probably you
1266* will never need to do that)
1267*/
1268phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle';
1269phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible';
1270
1271/**
1272* Dropdown toggle event handler
1273* This handler is used by phpBB.registerDropdown() and other functions
1274*/
1275phpbb.toggleDropdown = function() {
1276	var $this = $(this),
1277		options = $this.data('dropdown-options'),
1278		parent = options.parent,
1279		visible = parent.hasClass('dropdown-visible'),
1280		direction;
1281
1282	if (!visible) {
1283		// Hide other dropdown menus
1284		$(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1285
1286		// Figure out direction of dropdown
1287		direction = options.direction;
1288		var verticalDirection = options.verticalDirection,
1289			offset = $this.offset();
1290
1291		if (direction === 'auto') {
1292			if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) {
1293				direction = 'right';
1294			} else {
1295				direction = 'left';
1296			}
1297		}
1298		parent.toggleClass(options.leftClass, direction === 'left')
1299			.toggleClass(options.rightClass, direction === 'right');
1300
1301		if (verticalDirection === 'auto') {
1302			var height = $(window).height(),
1303				top = offset.top - $(window).scrollTop();
1304
1305			verticalDirection = (top < height * 0.7) ? 'down' : 'up';
1306		}
1307		parent.toggleClass(options.upClass, verticalDirection === 'up')
1308			.toggleClass(options.downClass, verticalDirection === 'down');
1309	}
1310
1311	options.dropdown.toggle();
1312	parent.toggleClass(options.visibleClass, !visible)
1313		.toggleClass('dropdown-visible', !visible);
1314
1315	// Check dimensions when showing dropdown
1316	// !visible because variable shows state of dropdown before it was toggled
1317	if (!visible) {
1318		var windowWidth = $(window).width();
1319
1320		options.dropdown.find('.dropdown-contents').each(function() {
1321			var $this = $(this);
1322
1323			$this.css({
1324				marginLeft: 0,
1325				left: 0,
1326				maxWidth: (windowWidth - 4) + 'px'
1327			});
1328
1329			var offset = $this.offset().left,
1330				width = $this.outerWidth(true);
1331
1332			if (offset < 2) {
1333				$this.css('left', (2 - offset) + 'px');
1334			} else if ((offset + width + 2) > windowWidth) {
1335				$this.css('margin-left', (windowWidth - offset - width - 2) + 'px');
1336			}
1337
1338			// Check whether the vertical scrollbar is present.
1339			$this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight());
1340
1341		});
1342		var freeSpace = parent.offset().left - 4;
1343
1344		if (direction === 'left') {
1345			options.dropdown.css('margin-left', '-' + freeSpace + 'px');
1346
1347			// Try to position the notification dropdown correctly in RTL-responsive mode
1348			if (options.dropdown.hasClass('dropdown-extended')) {
1349				var contentWidth,
1350					fullFreeSpace = freeSpace + parent.outerWidth();
1351
1352				options.dropdown.find('.dropdown-contents').each(function() {
1353					contentWidth = parseInt($(this).outerWidth(), 10);
1354					$(this).css({ marginLeft: 0, left: 0 });
1355				});
1356
1357				var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px';
1358				options.dropdown.css({
1359					width: maxOffset,
1360					marginLeft: -maxOffset
1361				});
1362			}
1363		} else {
1364			options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px');
1365		}
1366	}
1367
1368	// Prevent event propagation
1369	if (arguments.length > 0) {
1370		try {
1371			var e = arguments[0];
1372			e.preventDefault();
1373			e.stopPropagation();
1374		} catch (error) { }
1375	}
1376	return false;
1377};
1378
1379/**
1380* Toggle dropdown submenu
1381*/
1382phpbb.toggleSubmenu = function(e) {
1383	$(this).siblings('.dropdown-submenu').toggle();
1384	e.preventDefault();
1385};
1386
1387/**
1388* Register dropdown menu
1389* Shows/hides dropdown, decides which side to open to
1390*
1391* @param {jQuery} toggle Link that toggles dropdown.
1392* @param {jQuery} dropdown Dropdown menu.
1393* @param {Object} options List of options. Optional.
1394*/
1395phpbb.registerDropdown = function(toggle, dropdown, options) {
1396	var ops = {
1397			parent: toggle.parent(), // Parent item to add classes to
1398			direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right
1399			verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down
1400			visibleClass: 'visible', // Class to add to parent item when dropdown is visible
1401			leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side
1402			rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side
1403			upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item
1404			downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item
1405		};
1406	if (options) {
1407		ops = $.extend(ops, options);
1408	}
1409	ops.dropdown = dropdown;
1410
1411	ops.parent.addClass('dropdown-container');
1412	toggle.addClass('dropdown-toggle');
1413
1414	toggle.data('dropdown-options', ops);
1415
1416	toggle.click(phpbb.toggleDropdown);
1417	$('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu);
1418};
1419
1420/**
1421* Get the HTML for a color palette table.
1422*
1423* @param {string} dir Palette direction - either v or h
1424* @param {int} width Palette cell width.
1425* @param {int} height Palette cell height.
1426*/
1427phpbb.colorPalette = function(dir, width, height) {
1428	var r, g, b,
1429		numberList = new Array(6),
1430		color = '',
1431		html = '';
1432
1433	numberList[0] = '00';
1434	numberList[1] = '40';
1435	numberList[2] = '80';
1436	numberList[3] = 'BF';
1437	numberList[4] = 'FF';
1438
1439	var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette';
1440	html += '<table class="not-responsive colour-palette ' + tableClass + '" style="width: auto;">';
1441
1442	for (r = 0; r < 5; r++) {
1443		if (dir === 'h') {
1444			html += '<tr>';
1445		}
1446
1447		for (g = 0; g < 5; g++) {
1448			if (dir === 'v') {
1449				html += '<tr>';
1450			}
1451
1452			for (b = 0; b < 5; b++) {
1453				color = '' + numberList[r] + numberList[g] + numberList[b];
1454				html += '<td style="background-color: #' + color + '; width: ' + width + 'px; height: ' +
1455					height + 'px;"><a href="#" data-color="' + color + '" style="display: block; width: ' +
1456					width + 'px; height: ' + height + 'px; " alt="#' + color + '" title="#' + color + '"></a>';
1457				html += '</td>';
1458			}
1459
1460			if (dir === 'v') {
1461				html += '</tr>';
1462			}
1463		}
1464
1465		if (dir === 'h') {
1466			html += '</tr>';
1467		}
1468	}
1469	html += '</table>';
1470	return html;
1471};
1472
1473/**
1474* Register a color palette.
1475*
1476* @param {jQuery} el jQuery object for the palette container.
1477*/
1478phpbb.registerPalette = function(el) {
1479	var	orientation	= el.attr('data-orientation'),
1480		height		= el.attr('data-height'),
1481		width		= el.attr('data-width'),
1482		target		= el.attr('data-target'),
1483		bbcode		= el.attr('data-bbcode');
1484
1485	// Insert the palette HTML into the container.
1486	el.html(phpbb.colorPalette(orientation, width, height));
1487
1488	// Add toggle control.
1489	$('#color_palette_toggle').click(function(e) {
1490		el.toggle();
1491		e.preventDefault();
1492	});
1493
1494	// Attach event handler when a palette cell is clicked.
1495	$(el).on('click', 'a', function(e) {
1496		var color = $(this).attr('data-color');
1497
1498		if (bbcode) {
1499			bbfontstyle('[color=#' + color + ']', '[/color]');
1500		} else {
1501			$(target).val(color);
1502		}
1503		e.preventDefault();
1504	});
1505};
1506
1507/**
1508* Set display of page element
1509*
1510* @param {string} id The ID of the element to change
1511* @param {int} action Set to 0 if element display should be toggled, -1 for
1512*			hiding the element, and 1 for showing it.
1513* @param {string} type Display type that should be used, e.g. inline, block or
1514*			other CSS "display" types
1515*/
1516phpbb.toggleDisplay = function(id, action, type) {
1517	if (!type) {
1518		type = 'block';
1519	}
1520
1521	var $element = $('#' + id);
1522
1523	var display = $element.css('display');
1524	if (!action) {
1525		action = (display === '' || display === type) ? -1 : 1;
1526	}
1527	$element.css('display', ((action === 1) ? type : 'none'));
1528};
1529
1530/**
1531* Toggle additional settings based on the selected
1532* option of select element.
1533*
1534* @param {jQuery} el jQuery select element object.
1535*/
1536phpbb.toggleSelectSettings = function(el) {
1537	el.children().each(function() {
1538		var $this = $(this),
1539			$setting = $($this.data('toggle-setting'));
1540		$setting.toggle($this.is(':selected'));
1541
1542		// Disable any input elements that are not visible right now
1543		if ($this.is(':selected')) {
1544			$($this.data('toggle-setting') + ' input').prop('disabled', false);
1545		} else {
1546			$($this.data('toggle-setting') + ' input').prop('disabled', true);
1547		}
1548	});
1549};
1550
1551/**
1552* Get function from name.
1553* Based on http://stackoverflow.com/a/359910
1554*
1555* @param {string} functionName Function to get.
1556* @returns function
1557*/
1558phpbb.getFunctionByName = function (functionName) {
1559	var namespaces = functionName.split('.'),
1560		func = namespaces.pop(),
1561		context = window;
1562
1563	for (var i = 0; i < namespaces.length; i++) {
1564		context = context[namespaces[i]];
1565	}
1566	return context[func];
1567};
1568
1569/**
1570* Register page dropdowns.
1571*/
1572phpbb.registerPageDropdowns = function() {
1573	var $body = $('body');
1574
1575	$body.find('.dropdown-container').each(function() {
1576		var $this = $(this),
1577			$trigger = $this.find('.dropdown-trigger:first'),
1578			$contents = $this.find('.dropdown'),
1579			options = {
1580				direction: 'auto',
1581				verticalDirection: 'auto'
1582			},
1583			data;
1584
1585		if (!$trigger.length) {
1586			data = $this.attr('data-dropdown-trigger');
1587			$trigger = data ? $this.children(data) : $this.children('a:first');
1588		}
1589
1590		if (!$contents.length) {
1591			data = $this.attr('data-dropdown-contents');
1592			$contents = data ? $this.children(data) : $this.children('div:first');
1593		}
1594
1595		if (!$trigger.length || !$contents.length) {
1596			return;
1597		}
1598
1599		if ($this.hasClass('dropdown-up')) {
1600			options.verticalDirection = 'up';
1601		}
1602		if ($this.hasClass('dropdown-down')) {
1603			options.verticalDirection = 'down';
1604		}
1605		if ($this.hasClass('dropdown-left')) {
1606			options.direction = 'left';
1607		}
1608		if ($this.hasClass('dropdown-right')) {
1609			options.direction = 'right';
1610		}
1611
1612		phpbb.registerDropdown($trigger, $contents, options);
1613	});
1614
1615	// Hide active dropdowns when click event happens outside
1616	$body.click(function(e) {
1617		var $parents = $(e.target).parents();
1618		if (!$parents.is(phpbb.dropdownVisibleContainers)) {
1619			$(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1620		}
1621	});
1622};
1623
1624/**
1625 * Handle avatars to be lazy loaded.
1626 */
1627phpbb.lazyLoadAvatars = function loadAvatars() {
1628	$('.avatar[data-src]').each(function () {
1629		var $avatar = $(this);
1630
1631		$avatar
1632			.attr('src', $avatar.data('src'))
1633			.removeAttr('data-src');
1634	});
1635};
1636
1637$(window).load(phpbb.lazyLoadAvatars);
1638
1639/**
1640* Apply code editor to all textarea elements with data-bbcode attribute
1641*/
1642$(function() {
1643	$('textarea[data-bbcode]').each(function() {
1644		phpbb.applyCodeEditor(this);
1645	});
1646
1647	phpbb.registerPageDropdowns();
1648
1649	$('#color_palette_placeholder').each(function() {
1650		phpbb.registerPalette($(this));
1651	});
1652
1653	// Update browser history URL to point to specific post in viewtopic.php
1654	// when using view=unread#unread link.
1655	phpbb.history.replaceUrl($('#unread[data-url]').data('url'));
1656
1657	// Hide settings that are not selected via select element.
1658	$('select[data-togglable-settings]').each(function() {
1659		var $this = $(this);
1660
1661		$this.change(function() {
1662			phpbb.toggleSelectSettings($this);
1663		});
1664		phpbb.toggleSelectSettings($this);
1665	});
1666});
1667
1668})(jQuery); // Avoid conflicts with other libraries

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.