Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
core.js
0001 /* global bbfontstyle */
0002
0003 var phpbb = {};
0004 phpbb.alertTime = 100;
0005
0006 (function($) { // Avoid conflicts with other libraries
0007
0008 'use strict';
0009
0010 // define a couple constants for keydown functions.
0011 var keymap = {
0012 TAB: 9,
0013 ENTER: 13,
0014 ESC: 27
0015 };
0016
0017 var $dark = $('#darkenwrapper');
0018 var $loadingIndicator;
0019 var phpbbAlertTimer = null;
0020
0021 phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined');
0022
0023 /**
0024 * Display a loading screen
0025 *
0026 * @returns {object} Returns loadingIndicator.
0027 */
0028 phpbb.loadingIndicator = function() {
0029 if (!$loadingIndicator) {
0030 $loadingIndicator = $('<div />', {
0031 id: 'loading_indicator',
0032 class: 'loading_indicator',
0033 });
0034 $loadingIndicator.appendTo('#page-footer');
0035 }
0036
0037 if (!$loadingIndicator.is(':visible')) {
0038 $loadingIndicator.fadeIn(phpbb.alertTime);
0039 // Wait fifteen seconds and display an error if nothing has been returned by then.
0040 phpbb.clearLoadingTimeout();
0041 phpbbAlertTimer = setTimeout(function() {
0042 var $alert = $('#phpbb_alert');
0043
0044 if ($loadingIndicator.is(':visible')) {
0045 phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req'));
0046 }
0047 }, 15000);
0048 }
0049
0050 return $loadingIndicator;
0051 };
0052
0053 /**
0054 * Clear loading alert timeout
0055 */
0056 phpbb.clearLoadingTimeout = function() {
0057 if (phpbbAlertTimer !== null) {
0058 clearTimeout(phpbbAlertTimer);
0059 phpbbAlertTimer = null;
0060 }
0061 };
0062
0063
0064 /**
0065 * Close popup alert after a specified delay
0066 *
0067 * @param {int} delay Delay in ms until darkenwrapper's click event is triggered
0068 */
0069 phpbb.closeDarkenWrapper = function(delay) {
0070 phpbbAlertTimer = setTimeout(function() {
0071 $('#darkenwrapper').trigger('click');
0072 }, delay);
0073 };
0074
0075 /**
0076 * Display a simple alert similar to JSs native alert().
0077 *
0078 * You can only call one alert or confirm box at any one time.
0079 *
0080 * @param {string} title Title of the message, eg "Information" (HTML).
0081 * @param {string} msg Message to display (HTML).
0082 *
0083 * @returns {object} Returns the div created.
0084 */
0085 phpbb.alert = function(title, msg) {
0086 var $alert = $('#phpbb_alert');
0087 $alert.find('.alert_title').html(title);
0088 $alert.find('.alert_text').html(msg);
0089
0090 $(document).on('keydown.phpbb.alert', function(e) {
0091 if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
0092 phpbb.alert.close($alert, true);
0093 e.preventDefault();
0094 e.stopPropagation();
0095 }
0096 });
0097 phpbb.alert.open($alert);
0098
0099 return $alert;
0100 };
0101
0102 /**
0103 * Handler for opening an alert box.
0104 *
0105 * @param {jQuery} $alert jQuery object.
0106 */
0107 phpbb.alert.open = function($alert) {
0108 if (!$dark.is(':visible')) {
0109 $dark.fadeIn(phpbb.alertTime);
0110 }
0111
0112 if ($loadingIndicator && $loadingIndicator.is(':visible')) {
0113 $loadingIndicator.fadeOut(phpbb.alertTime, function() {
0114 $dark.append($alert);
0115 $alert.fadeIn(phpbb.alertTime);
0116 });
0117 } else if ($dark.is(':visible')) {
0118 $dark.append($alert);
0119 $alert.fadeIn(phpbb.alertTime);
0120 } else {
0121 $dark.append($alert);
0122 $alert.show();
0123 $dark.fadeIn(phpbb.alertTime);
0124 }
0125
0126 $alert.on('click', function(e) {
0127 e.stopPropagation();
0128 });
0129
0130 $dark.one('click', function(e) {
0131 phpbb.alert.close($alert, true);
0132 e.preventDefault();
0133 e.stopPropagation();
0134 });
0135
0136 $alert.find('.alert_close').one('click', function(e) {
0137 phpbb.alert.close($alert, true);
0138 e.preventDefault();
0139 });
0140 };
0141
0142 /**
0143 * Handler for closing an alert box.
0144 *
0145 * @param {jQuery} $alert jQuery object.
0146 * @param {bool} fadedark Whether to remove dark background.
0147 */
0148 phpbb.alert.close = function($alert, fadedark) {
0149 var $fade = (fadedark) ? $dark : $alert;
0150
0151 $fade.fadeOut(phpbb.alertTime, function() {
0152 $alert.hide();
0153 });
0154
0155 $alert.find('.alert_close').off('click');
0156 $(document).off('keydown.phpbb.alert');
0157 };
0158
0159 /**
0160 * Display a simple yes / no box to the user.
0161 *
0162 * You can only call one alert or confirm box at any one time.
0163 *
0164 * @param {string} msg Message to display (HTML).
0165 * @param {function} callback Callback. Bool param, whether the user pressed
0166 * yes or no (or whatever their language is).
0167 * @param {bool} fadedark Remove the dark background when done? Defaults
0168 * to yes.
0169 *
0170 * @returns {object} Returns the div created.
0171 */
0172 phpbb.confirm = function(msg, callback, fadedark) {
0173 var $confirmDiv = $('#phpbb_confirm');
0174 $confirmDiv.find('.alert_text').html(msg);
0175 fadedark = fadedark || true;
0176
0177 $(document).on('keydown.phpbb.alert', function(e) {
0178 if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
0179 var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel';
0180
0181 $('input[name="' + name + '"]').trigger('click');
0182 e.preventDefault();
0183 e.stopPropagation();
0184 }
0185 });
0186
0187 $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) {
0188 var confirmed = this.name === 'confirm';
0189
0190 if (confirmed) {
0191 callback(true);
0192 }
0193 $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox');
0194 phpbb.alert.close($confirmDiv, fadedark || !confirmed);
0195
0196 e.preventDefault();
0197 e.stopPropagation();
0198 });
0199
0200 phpbb.alert.open($confirmDiv);
0201
0202 return $confirmDiv;
0203 };
0204
0205 /**
0206 * Turn a querystring into an array.
0207 *
0208 * @argument {string} string The querystring to parse.
0209 * @returns {object} The object created.
0210 */
0211 phpbb.parseQuerystring = function(string) {
0212 var params = {}, i, split;
0213
0214 string = string.split('&');
0215 for (i = 0; i < string.length; i++) {
0216 split = string[i].split('=');
0217 params[split[0]] = decodeURIComponent(split[1]);
0218 }
0219 return params;
0220 };
0221
0222
0223 /**
0224 * Makes a link use AJAX instead of loading an entire page.
0225 *
0226 * This function will work for links (both standard links and links which
0227 * invoke confirm_box) and forms. It will be called automatically for links
0228 * and forms with the data-ajax attribute set, and will call the necessary
0229 * callback.
0230 *
0231 * For more info, view the following page on the phpBB wiki:
0232 * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify
0233 *
0234 * @param {object} options Options.
0235 */
0236 phpbb.ajaxify = function(options) {
0237 var $elements = $(options.selector),
0238 refresh = options.refresh,
0239 callback = options.callback,
0240 overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true,
0241 isForm = $elements.is('form'),
0242 isText = $elements.is('input[type="text"], textarea'),
0243 eventName;
0244
0245 if (isForm) {
0246 eventName = 'submit';
0247 } else if (isText) {
0248 eventName = 'keyup';
0249 } else {
0250 eventName = 'click';
0251 }
0252
0253 $elements.on(eventName, function(event) {
0254 var action, method, data, submit, that = this, $this = $(this);
0255
0256 if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') {
0257 return;
0258 }
0259
0260 /**
0261 * Handler for AJAX errors
0262 */
0263 function errorHandler(jqXHR, textStatus, errorThrown) {
0264 if (typeof console !== 'undefined' && console.log) {
0265 console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown);
0266 }
0267 phpbb.clearLoadingTimeout();
0268 var responseText, errorText = false;
0269 try {
0270 responseText = JSON.parse(jqXHR.responseText);
0271 responseText = responseText.message;
0272 } catch (e) {}
0273 if (typeof responseText === 'string' && responseText.length > 0) {
0274 errorText = responseText;
0275 } else if (typeof errorThrown === 'string' && errorThrown.length > 0) {
0276 errorText = errorThrown;
0277 } else {
0278 errorText = $dark.attr('data-ajax-error-text-' + textStatus);
0279 if (typeof errorText !== 'string' || !errorText.length) {
0280 errorText = $dark.attr('data-ajax-error-text');
0281 }
0282 }
0283 phpbb.alert($dark.attr('data-ajax-error-title'), errorText);
0284 }
0285
0286 /**
0287 * This is a private function used to handle the callbacks, refreshes
0288 * and alert. It calls the callback, refreshes the page if necessary, and
0289 * displays an alert to the user and removes it after an amount of time.
0290 *
0291 * It cannot be called from outside this function, and is purely here to
0292 * avoid repetition of code.
0293 *
0294 * @param {object} res The object sent back by the server.
0295 */
0296 function returnHandler(res) {
0297 var alert;
0298
0299 phpbb.clearLoadingTimeout();
0300
0301 // Is a confirmation required?
0302 if (typeof res.S_CONFIRM_ACTION === 'undefined') {
0303 // If a confirmation is not required, display an alert and call the
0304 // callbacks.
0305 if (typeof res.MESSAGE_TITLE !== 'undefined') {
0306 alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT);
0307 } else {
0308 $dark.fadeOut(phpbb.alertTime);
0309
0310 if ($loadingIndicator) {
0311 $loadingIndicator.fadeOut(phpbb.alertTime);
0312 }
0313 }
0314
0315 if (typeof phpbb.ajaxCallbacks[callback] === 'function') {
0316 phpbb.ajaxCallbacks[callback].call(that, res);
0317 }
0318
0319 // If the server says to refresh the page, check whether the page should
0320 // be refreshed and refresh page after specified time if required.
0321 if (res.REFRESH_DATA) {
0322 if (typeof refresh === 'function') {
0323 refresh = refresh(res.REFRESH_DATA.url);
0324 } else if (typeof refresh !== 'boolean') {
0325 refresh = false;
0326 }
0327
0328 phpbbAlertTimer = setTimeout(function() {
0329 if (refresh) {
0330 window.location = res.REFRESH_DATA.url;
0331 }
0332
0333 // Hide the alert even if we refresh the page, in case the user
0334 // presses the back button.
0335 $dark.fadeOut(phpbb.alertTime, function() {
0336 if (typeof alert !== 'undefined') {
0337 alert.hide();
0338 }
0339 });
0340 }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds
0341 }
0342 } else {
0343 // If confirmation is required, display a dialog to the user.
0344 phpbb.confirm(res.MESSAGE_BODY, function(del) {
0345 if (!del) {
0346 return;
0347 }
0348
0349 phpbb.loadingIndicator();
0350 data = $('<form>' + res.S_HIDDEN_FIELDS + '</form>').serialize();
0351 $.ajax({
0352 url: res.S_CONFIRM_ACTION,
0353 type: 'POST',
0354 data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(),
0355 success: returnHandler,
0356 error: errorHandler
0357 });
0358 }, false);
0359 }
0360 }
0361
0362 // If the element is a form, POST must be used and some extra data must
0363 // be taken from the form.
0364 var runFilter = (typeof options.filter === 'function');
0365 data = {};
0366
0367 if (isForm) {
0368 action = $this.attr('action').replace('&', '&');
0369 data = $this.serializeArray();
0370 method = $this.attr('method') || 'GET';
0371
0372 if ($this.find('input[type="submit"][data-clicked]')) {
0373 submit = $this.find('input[type="submit"][data-clicked]');
0374 data.push({
0375 name: submit.attr('name'),
0376 value: submit.val()
0377 });
0378 }
0379 } else if (isText) {
0380 var name = $this.attr('data-name') || this.name;
0381 action = $this.attr('data-url').replace('&', '&');
0382 data[name] = this.value;
0383 method = 'POST';
0384 } else {
0385 action = this.href;
0386 data = null;
0387 method = 'GET';
0388 }
0389
0390 var sendRequest = function() {
0391 var dataOverlay = $this.attr('data-overlay');
0392 if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) {
0393 phpbb.loadingIndicator();
0394 }
0395
0396 var request = $.ajax({
0397 url: action,
0398 type: method,
0399 data: data,
0400 success: returnHandler,
0401 error: errorHandler,
0402 cache: false
0403 });
0404
0405 request.always(function() {
0406 if ($loadingIndicator && $loadingIndicator.is(':visible')) {
0407 $loadingIndicator.fadeOut(phpbb.alertTime);
0408 }
0409 });
0410 };
0411
0412 // If filter function returns false, cancel the AJAX functionality,
0413 // and return true (meaning that the HTTP request will be sent normally).
0414 if (runFilter && !options.filter.call(this, data, event, sendRequest)) {
0415 return;
0416 }
0417
0418 sendRequest();
0419 event.preventDefault();
0420 });
0421
0422 if (isForm) {
0423 $elements.find('input:submit').click(function () {
0424 var $this = $(this);
0425
0426 // Remove data-clicked attribute from any submit button of form
0427 $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked');
0428
0429 $this.attr('data-clicked', 'true');
0430 });
0431 }
0432
0433 return this;
0434 };
0435
0436 phpbb.search = {
0437 cache: {
0438 data: []
0439 },
0440 tpl: [],
0441 container: []
0442 };
0443
0444 /**
0445 * Get cached search data.
0446 *
0447 * @param {string} id Search ID.
0448 * @returns {bool|object} Cached data object. Returns false if no data exists.
0449 */
0450 phpbb.search.cache.get = function(id) {
0451 if (this.data[id]) {
0452 return this.data[id];
0453 }
0454 return false;
0455 };
0456
0457 /**
0458 * Set search cache data value.
0459 *
0460 * @param {string} id Search ID.
0461 * @param {string} key Data key.
0462 * @param {string} value Data value.
0463 */
0464 phpbb.search.cache.set = function(id, key, value) {
0465 if (!this.data[id]) {
0466 this.data[id] = { results: [] };
0467 }
0468 this.data[id][key] = value;
0469 };
0470
0471 /**
0472 * Cache search result.
0473 *
0474 * @param {string} id Search ID.
0475 * @param {string} keyword Keyword.
0476 * @param {Array} results Search results.
0477 */
0478 phpbb.search.cache.setResults = function(id, keyword, results) {
0479 this.data[id].results[keyword] = results;
0480 };
0481
0482 /**
0483 * Trim spaces from keyword and lower its case.
0484 *
0485 * @param {string} keyword Search keyword to clean.
0486 * @returns {string} Cleaned string.
0487 */
0488 phpbb.search.cleanKeyword = function(keyword) {
0489 return $.trim(keyword).toLowerCase();
0490 };
0491
0492 /**
0493 * Get clean version of search keyword. If textarea supports several keywords
0494 * (one per line), it fetches the current keyword based on the caret position.
0495 *
0496 * @param {jQuery} $input Search input|textarea.
0497 * @param {string} keyword Input|textarea value.
0498 * @param {bool} multiline Whether textarea supports multiple search keywords.
0499 *
0500 * @returns string Clean string.
0501 */
0502 phpbb.search.getKeyword = function($input, keyword, multiline) {
0503 if (multiline) {
0504 var line = phpbb.search.getKeywordLine($input);
0505 keyword = keyword.split('\n').splice(line, 1);
0506 }
0507 return phpbb.search.cleanKeyword(keyword);
0508 };
0509
0510 /**
0511 * Get the textarea line number on which the keyword resides - for textareas
0512 * that support multiple keywords (one per line).
0513 *
0514 * @param {jQuery} $textarea Search textarea.
0515 * @returns {int} The line number.
0516 */
0517 phpbb.search.getKeywordLine = function ($textarea) {
0518 var selectionStart = $textarea.get(0).selectionStart;
0519 return $textarea.val().substr(0, selectionStart).split('\n').length - 1;
0520 };
0521
0522 /**
0523 * Set the value on the input|textarea. If textarea supports multiple
0524 * keywords, only the active keyword is replaced.
0525 *
0526 * @param {jQuery} $input Search input|textarea.
0527 * @param {string} value Value to set.
0528 * @param {bool} multiline Whether textarea supports multiple search keywords.
0529 */
0530 phpbb.search.setValue = function($input, value, multiline) {
0531 if (multiline) {
0532 var line = phpbb.search.getKeywordLine($input),
0533 lines = $input.val().split('\n');
0534 lines[line] = value;
0535 value = lines.join('\n');
0536 }
0537 $input.val(value);
0538 };
0539
0540 /**
0541 * Sets the onclick event to set the value on the input|textarea to the
0542 * selected search result.
0543 *
0544 * @param {jQuery} $input Search input|textarea.
0545 * @param {object} value Result object.
0546 * @param {jQuery} $row Result element.
0547 * @param {jQuery} $container jQuery object for the search container.
0548 */
0549 phpbb.search.setValueOnClick = function($input, value, $row, $container) {
0550 $row.click(function() {
0551 phpbb.search.setValue($input, value.result, $input.attr('data-multiline'));
0552 $container.hide();
0553 });
0554 };
0555
0556 /**
0557 * Runs before the AJAX search request is sent and determines whether
0558 * there is a need to contact the server. If there are cached results
0559 * already, those are displayed instead. Executes the AJAX request function
0560 * itself due to the need to use a timeout to limit the number of requests.
0561 *
0562 * @param {Array} data Data to be sent to the server.
0563 * @param {object} event Onkeyup event object.
0564 * @param {function} sendRequest Function to execute AJAX request.
0565 *
0566 * @returns {bool} Returns false.
0567 */
0568 phpbb.search.filter = function(data, event, sendRequest) {
0569 var $this = $(this),
0570 dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'),
0571 minLength = parseInt($this.attr('data-min-length'), 10),
0572 searchID = $this.attr('data-results'),
0573 keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')),
0574 cache = phpbb.search.cache.get(searchID),
0575 proceed = true;
0576 data[dataName] = keyword;
0577
0578 if (cache.timeout) {
0579 clearTimeout(cache.timeout);
0580 }
0581
0582 var timeout = setTimeout(function() {
0583 // Check min length and existence of cache.
0584 if (minLength > keyword.length) {
0585 proceed = false;
0586 } else if (cache.lastSearch) {
0587 // Has the keyword actually changed?
0588 if (cache.lastSearch === keyword) {
0589 proceed = false;
0590 } else {
0591 // Do we already have results for this?
0592 if (cache.results[keyword]) {
0593 var response = {
0594 keyword: keyword,
0595 results: cache.results[keyword]
0596 };
0597 phpbb.search.handleResponse(response, $this, true);
0598 proceed = false;
0599 }
0600
0601 // If the previous search didn't yield results and the string only had characters added to it,
0602 // then we won't bother sending a request.
0603 if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) {
0604 phpbb.search.cache.set(searchID, 'lastSearch', keyword);
0605 phpbb.search.cache.setResults(searchID, keyword, []);
0606 proceed = false;
0607 }
0608 }
0609 }
0610
0611 if (proceed) {
0612 sendRequest.call(this);
0613 }
0614 }, 350);
0615 phpbb.search.cache.set(searchID, 'timeout', timeout);
0616
0617 return false;
0618 };
0619
0620 /**
0621 * Handle search result response.
0622 *
0623 * @param {object} res Data received from server.
0624 * @param {jQuery} $input Search input|textarea.
0625 * @param {bool} fromCache Whether the results are from the cache.
0626 * @param {function} callback Optional callback to run when assigning each search result.
0627 */
0628 phpbb.search.handleResponse = function(res, $input, fromCache, callback) {
0629 if (typeof res !== 'object') {
0630 return;
0631 }
0632
0633 var searchID = $input.attr('data-results'),
0634 $container = $(searchID);
0635
0636 if (this.cache.get(searchID).callback) {
0637 callback = this.cache.get(searchID).callback;
0638 } else if (typeof callback === 'function') {
0639 this.cache.set(searchID, 'callback', callback);
0640 }
0641
0642 if (!fromCache) {
0643 this.cache.setResults(searchID, res.keyword, res.results);
0644 }
0645
0646 this.cache.set(searchID, 'lastSearch', res.keyword);
0647 this.showResults(res.results, $input, $container, callback);
0648 };
0649
0650 /**
0651 * Show search results.
0652 *
0653 * @param {Array} results Search results.
0654 * @param {jQuery} $input Search input|textarea.
0655 * @param {jQuery} $container Search results container element.
0656 * @param {function} callback Optional callback to run when assigning each search result.
0657 */
0658 phpbb.search.showResults = function(results, $input, $container, callback) {
0659 var $resultContainer = $('.search-results', $container);
0660 this.clearResults($resultContainer);
0661
0662 if (!results.length) {
0663 $container.hide();
0664 return;
0665 }
0666
0667 var searchID = $container.attr('id'),
0668 tpl,
0669 row;
0670
0671 if (!this.tpl[searchID]) {
0672 tpl = $('.search-result-tpl', $container);
0673 this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl');
0674 tpl.remove();
0675 }
0676 tpl = this.tpl[searchID];
0677
0678 $.each(results, function(i, item) {
0679 row = tpl.clone();
0680 row.find('.search-result').html(item.display);
0681
0682 if (typeof callback === 'function') {
0683 callback.call(this, $input, item, row, $container);
0684 }
0685 row.appendTo($resultContainer).show();
0686 });
0687 $container.show();
0688 };
0689
0690 /**
0691 * Clear search results.
0692 *
0693 * @param {jQuery} $container Search results container.
0694 */
0695 phpbb.search.clearResults = function($container) {
0696 $container.children(':not(.search-result-tpl)').remove();
0697 };
0698
0699 $('#phpbb').click(function() {
0700 var $this = $(this);
0701
0702 if (!$this.is('.live-search') && !$this.parents().is('.live-search')) {
0703 $('.live-search').hide();
0704 }
0705 });
0706
0707 phpbb.history = {};
0708
0709 /**
0710 * Check whether a method in the native history object is supported.
0711 *
0712 * @param {string} fn Method name.
0713 * @returns {bool} Returns true if the method is supported.
0714 */
0715 phpbb.history.isSupported = function(fn) {
0716 return !(typeof history === 'undefined' || typeof history[fn] === 'undefined');
0717 };
0718
0719 /**
0720 * Wrapper for the pushState and replaceState methods of the
0721 * native history object.
0722 *
0723 * @param {string} mode Mode. Either push or replace.
0724 * @param {string} url New URL.
0725 * @param {string} [title] Optional page title.
0726 * @param {object} [obj] Optional state object.
0727 */
0728 phpbb.history.alterUrl = function(mode, url, title, obj) {
0729 var fn = mode + 'State';
0730
0731 if (!url || !phpbb.history.isSupported(fn)) {
0732 return;
0733 }
0734 if (!title) {
0735 title = document.title;
0736 }
0737 if (!obj) {
0738 obj = null;
0739 }
0740
0741 history[fn](obj, title, url);
0742 };
0743
0744 /**
0745 * Wrapper for the native history.replaceState method.
0746 *
0747 * @param {string} url New URL.
0748 * @param {string} [title] Optional page title.
0749 * @param {object} [obj] Optional state object.
0750 */
0751 phpbb.history.replaceUrl = function(url, title, obj) {
0752 phpbb.history.alterUrl('replace', url, title, obj);
0753 };
0754
0755 /**
0756 * Wrapper for the native history.pushState method.
0757 *
0758 * @param {string} url New URL.
0759 * @param {string} [title] Optional page title.
0760 * @param {object} [obj] Optional state object.
0761 */
0762 phpbb.history.pushUrl = function(url, title, obj) {
0763 phpbb.history.alterUrl('push', url, title, obj);
0764 };
0765
0766 /**
0767 * Hide the optgroups that are not the selected timezone
0768 *
0769 * @param {bool} keepSelection Shall we keep the value selected, or shall the
0770 * user be forced to repick one.
0771 */
0772 phpbb.timezoneSwitchDate = function(keepSelection) {
0773 var $timezoneCopy = $('#timezone_copy');
0774 var $timezone = $('#timezone');
0775 var $tzDate = $('#tz_date');
0776 var $tzSelectDateSuggest = $('#tz_select_date_suggest');
0777
0778 if ($timezoneCopy.length === 0) {
0779 // We make a backup of the original dropdown, so we can remove optgroups
0780 // instead of setting display to none, because IE and chrome will not
0781 // hide options inside of optgroups and selects via css
0782 $timezone.clone()
0783 .attr('id', 'timezone_copy')
0784 .css('display', 'none')
0785 .attr('name', 'tz_copy')
0786 .insertAfter('#timezone');
0787 } else {
0788 // Copy the content of our backup, so we can remove all unneeded options
0789 $timezone.html($timezoneCopy.html());
0790 }
0791
0792 if ($tzDate.val() !== '') {
0793 $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])');
0794 }
0795
0796 if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) {
0797 $tzSelectDateSuggest.css('display', 'none');
0798 } else {
0799 $tzSelectDateSuggest.css('display', 'inline');
0800 }
0801
0802 var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option');
0803
0804 if ($tzOptions.length === 1) {
0805 // If there is only one timezone for the selected date, we just select that automatically.
0806 $tzOptions.prop('selected', true);
0807 keepSelection = true;
0808 }
0809
0810 if (typeof keepSelection !== 'undefined' && !keepSelection) {
0811 var $timezoneOptions = $timezone.find('optgroup option');
0812 if ($timezoneOptions.filter(':selected').length <= 0) {
0813 $timezoneOptions.filter(':first').prop('selected', true);
0814 }
0815 }
0816 };
0817
0818 /**
0819 * Display the date/time select
0820 */
0821 phpbb.timezoneEnableDateSelection = function() {
0822 $('#tz_select_date').css('display', 'block');
0823 };
0824
0825 /**
0826 * Preselect a date/time or suggest one, if it is not picked.
0827 *
0828 * @param {bool} forceSelector Shall we select the suggestion?
0829 */
0830 phpbb.timezonePreselectSelect = function(forceSelector) {
0831
0832 // The offset returned here is in minutes and negated.
0833 var offset = (new Date()).getTimezoneOffset();
0834 var sign = '-';
0835
0836 if (offset < 0) {
0837 sign = '+';
0838 offset = -offset;
0839 }
0840
0841 var minutes = offset % 60;
0842 var hours = (offset - minutes) / 60;
0843
0844 if (hours < 10) {
0845 hours = '0' + hours.toString();
0846 } else {
0847 hours = hours.toString();
0848 }
0849
0850 if (minutes < 10) {
0851 minutes = '0' + minutes.toString();
0852 } else {
0853 minutes = minutes.toString();
0854 }
0855
0856 var prefix = 'UTC' + sign + hours + ':' + minutes;
0857 var prefixLength = prefix.length;
0858 var selectorOptions = $('option', '#tz_date');
0859 var i;
0860
0861 var $tzSelectDateSuggest = $('#tz_select_date_suggest');
0862
0863 for (i = 0; i < selectorOptions.length; ++i) {
0864 var option = selectorOptions[i];
0865
0866 if (option.value.substring(0, prefixLength) === prefix) {
0867 if ($('#tz_date').val() !== option.value && !forceSelector) {
0868 // We do not select the option for the user, but notify him,
0869 // that we would suggest a different setting.
0870 phpbb.timezoneSwitchDate(true);
0871 $tzSelectDateSuggest.css('display', 'inline');
0872 } else {
0873 option.selected = true;
0874 phpbb.timezoneSwitchDate(!forceSelector);
0875 $tzSelectDateSuggest.css('display', 'none');
0876 }
0877
0878 var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion');
0879
0880 $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML));
0881 $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9)));
0882 $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML);
0883
0884 // Found the suggestion, there cannot be more, so return from here.
0885 return;
0886 }
0887 }
0888 };
0889
0890 phpbb.ajaxCallbacks = {};
0891
0892 /**
0893 * Adds an AJAX callback to be used by phpbb.ajaxify.
0894 *
0895 * See the phpbb.ajaxify comments for information on stuff like parameters.
0896 *
0897 * @param {string} id The name of the callback.
0898 * @param {function} callback The callback to be called.
0899 */
0900 phpbb.addAjaxCallback = function(id, callback) {
0901 if (typeof callback === 'function') {
0902 phpbb.ajaxCallbacks[id] = callback;
0903 }
0904 return this;
0905 };
0906
0907 /**
0908 * This callback handles live member searches.
0909 */
0910 phpbb.addAjaxCallback('member_search', function(res) {
0911 phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick'));
0912 });
0913
0914 /**
0915 * This callback alternates text - it replaces the current text with the text in
0916 * the alt-text data attribute, and replaces the text in the attribute with the
0917 * current text so that the process can be repeated.
0918 */
0919 phpbb.addAjaxCallback('alt_text', function() {
0920 var $anchor,
0921 updateAll = $(this).data('update-all'),
0922 altText;
0923
0924 if (updateAll !== undefined && updateAll.length) {
0925 $anchor = $(updateAll);
0926 } else {
0927 $anchor = $(this);
0928 }
0929
0930 $anchor.each(function() {
0931 var $this = $(this);
0932 altText = $this.attr('data-alt-text');
0933 $this.attr('data-alt-text', $this.text());
0934 $this.attr('title', $.trim(altText));
0935 $this.text(altText);
0936 });
0937 });
0938
0939 /**
0940 * This callback is based on the alt_text callback.
0941 *
0942 * It replaces the current text with the text in the alt-text data attribute,
0943 * and replaces the text in the attribute with the current text so that the
0944 * process can be repeated.
0945 * Additionally it replaces the class of the link's parent
0946 * and changes the link itself.
0947 */
0948 phpbb.addAjaxCallback('toggle_link', function() {
0949 var $anchor,
0950 updateAll = $(this).data('update-all') ,
0951 toggleText,
0952 toggleUrl,
0953 toggleClass;
0954
0955 if (updateAll !== undefined && updateAll.length) {
0956 $anchor = $(updateAll);
0957 } else {
0958 $anchor = $(this);
0959 }
0960
0961 $anchor.each(function() {
0962 var $this = $(this);
0963
0964 // Toggle link url
0965 toggleUrl = $this.attr('data-toggle-url');
0966 $this.attr('data-toggle-url', $this.attr('href'));
0967 $this.attr('href', toggleUrl);
0968
0969 // Toggle class of link parent
0970 toggleClass = $this.attr('data-toggle-class');
0971 $this.attr('data-toggle-class', $this.children().attr('class'));
0972 $this.children('.icon').attr('class', toggleClass);
0973
0974 // Toggle link text
0975 toggleText = $this.attr('data-toggle-text');
0976 $this.attr('data-toggle-text', $this.children('span').text());
0977 $this.attr('title', $.trim(toggleText));
0978 $this.children('span').text(toggleText);
0979 });
0980 });
0981
0982 /**
0983 * Automatically resize textarea
0984 *
0985 * This function automatically resizes textarea elements when user
0986 * types text.
0987 *
0988 * @param {jQuery} $items jQuery object(s) to resize
0989 * @param {object} [options] Optional parameter that adjusts default
0990 * configuration. See configuration variable
0991 *
0992 * Optional parameters:
0993 * minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500
0994 * minHeight {number} Minimum height of textarea. Default = 200
0995 * maxHeight {number} Maximum height of textarea. Default = 500
0996 * heightDiff {number} Minimum difference between window and textarea height. Default = 200
0997 * resizeCallback {function} Function to call after resizing textarea
0998 * resetCallback {function} Function to call when resize has been canceled
0999
1000 * Callback function format: function(item) {}
1001 * this points to DOM object
1002 * item is a jQuery object, same as this
1003 */
1004 phpbb.resizeTextArea = function($items, options) {
1005 // Configuration
1006 var configuration = {
1007 minWindowHeight: 500,
1008 minHeight: 200,
1009 maxHeight: 500,
1010 heightDiff: 200,
1011 resizeCallback: function() {},
1012 resetCallback: function() {}
1013 };
1014
1015 if (phpbb.isTouch) {
1016 return;
1017 }
1018
1019 if (arguments.length > 1) {
1020 configuration = $.extend(configuration, options);
1021 }
1022
1023 function resetAutoResize(item) {
1024 var $item = $(item);
1025 if ($item.hasClass('auto-resized')) {
1026 $(item)
1027 .css({ height: '', resize: '' })
1028 .removeClass('auto-resized');
1029 configuration.resetCallback.call(item, $item);
1030 }
1031 }
1032
1033 function autoResize(item) {
1034 function setHeight(height) {
1035 height += parseInt($item.css('height'), 10) - $item.innerHeight();
1036 $item
1037 .css({ height: height + 'px', resize: 'none' })
1038 .addClass('auto-resized');
1039 configuration.resizeCallback.call(item, $item);
1040 }
1041
1042 var windowHeight = $(window).height();
1043
1044 if (windowHeight < configuration.minWindowHeight) {
1045 resetAutoResize(item);
1046 return;
1047 }
1048
1049 var maxHeight = Math.min(
1050 Math.max(windowHeight - configuration.heightDiff, configuration.minHeight),
1051 configuration.maxHeight
1052 ),
1053 $item = $(item),
1054 height = parseInt($item.innerHeight(), 10),
1055 scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0;
1056
1057 if (height < 0) {
1058 return;
1059 }
1060
1061 if (height > maxHeight) {
1062 setHeight(maxHeight);
1063 } else if (scrollHeight > (height + 5)) {
1064 setHeight(Math.min(maxHeight, scrollHeight));
1065 }
1066 }
1067
1068 $items.on('focus change keyup', function() {
1069 $(this).each(function() {
1070 autoResize(this);
1071 });
1072 }).change();
1073
1074 $(window).resize(function() {
1075 $items.each(function() {
1076 if ($(this).hasClass('auto-resized')) {
1077 autoResize(this);
1078 }
1079 });
1080 });
1081 };
1082
1083 /**
1084 * Check if cursor in textarea is currently inside a bbcode tag
1085 *
1086 * @param {object} textarea Textarea DOM object
1087 * @param {Array} startTags List of start tags to look for
1088 * For example, Array('[code]', '[code=')
1089 * @param {Array} endTags List of end tags to look for
1090 * For example, Array('[/code]')
1091 *
1092 * @returns {boolean} True if cursor is in bbcode tag
1093 */
1094 phpbb.inBBCodeTag = function(textarea, startTags, endTags) {
1095 var start = textarea.selectionStart,
1096 lastEnd = -1,
1097 lastStart = -1,
1098 i, index, value;
1099
1100 if (typeof start !== 'number') {
1101 return false;
1102 }
1103
1104 value = textarea.value.toLowerCase();
1105
1106 for (i = 0; i < startTags.length; i++) {
1107 var tagLength = startTags[i].length;
1108 if (start >= tagLength) {
1109 index = value.lastIndexOf(startTags[i], start - tagLength);
1110 lastStart = Math.max(lastStart, index);
1111 }
1112 }
1113 if (lastStart === -1) {
1114 return false;
1115 }
1116
1117 if (start > 0) {
1118 for (i = 0; i < endTags.length; i++) {
1119 index = value.lastIndexOf(endTags[i], start - 1);
1120 lastEnd = Math.max(lastEnd, index);
1121 }
1122 }
1123
1124 return (lastEnd < lastStart);
1125 };
1126
1127
1128 /**
1129 * Adjust textarea to manage code bbcode
1130 *
1131 * This function allows to use tab characters when typing code
1132 * and keeps indentation of previous line of code when adding new
1133 * line while typing code.
1134 *
1135 * Editor's functionality is changed only when cursor is between
1136 * [code] and [/code] bbcode tags.
1137 *
1138 * @param {object} textarea Textarea DOM object to apply editor to
1139 */
1140 phpbb.applyCodeEditor = function(textarea) {
1141 // list of allowed start and end bbcode code tags, in lower case
1142 var startTags = ['[code]', '[code='],
1143 startTagsEnd = ']',
1144 endTags = ['[/code]'];
1145
1146 if (!textarea || typeof textarea.selectionStart !== 'number') {
1147 return;
1148 }
1149
1150 if ($(textarea).data('code-editor') === true) {
1151 return;
1152 }
1153
1154 function inTag() {
1155 return phpbb.inBBCodeTag(textarea, startTags, endTags);
1156 }
1157
1158 /**
1159 * Get line of text before cursor
1160 *
1161 * @param {boolean} stripCodeStart If true, only part of line
1162 * after [code] tag will be returned.
1163 *
1164 * @returns {string} Line of text
1165 */
1166 function getLastLine(stripCodeStart) {
1167 var start = textarea.selectionStart,
1168 value = textarea.value,
1169 index = value.lastIndexOf('\n', start - 1);
1170
1171 value = value.substring(index + 1, start);
1172
1173 if (stripCodeStart) {
1174 for (var i = 0; i < startTags.length; i++) {
1175 index = value.lastIndexOf(startTags[i]);
1176 if (index >= 0) {
1177 var tagLength = startTags[i].length;
1178
1179 value = value.substring(index + tagLength);
1180 if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) {
1181 index = value.indexOf(startTagsEnd);
1182
1183 if (index >= 0) {
1184 value = value.substr(index + 1);
1185 }
1186 }
1187 }
1188 }
1189 }
1190
1191 return value;
1192 }
1193
1194 /**
1195 * Append text at cursor position
1196 *
1197 * @param {string} text Text to append
1198 */
1199 function appendText(text) {
1200 var start = textarea.selectionStart,
1201 end = textarea.selectionEnd,
1202 value = textarea.value;
1203
1204 textarea.value = value.substr(0, start) + text + value.substr(end);
1205 textarea.selectionStart = textarea.selectionEnd = start + text.length;
1206 }
1207
1208 $(textarea).data('code-editor', true).on('keydown', function(event) {
1209 var key = event.keyCode || event.which;
1210
1211 // intercept tabs
1212 if (key === keymap.TAB &&
1213 !event.ctrlKey &&
1214 !event.shiftKey &&
1215 !event.altKey &&
1216 !event.metaKey) {
1217 if (inTag()) {
1218 appendText('\t');
1219 event.preventDefault();
1220 return;
1221 }
1222 }
1223
1224 // intercept new line characters
1225 if (key === keymap.ENTER) {
1226 if (inTag()) {
1227 var lastLine = getLastLine(true),
1228 code = '' + /^\s*/g.exec(lastLine);
1229
1230 if (code.length > 0) {
1231 appendText('\n' + code);
1232 event.preventDefault();
1233 }
1234 }
1235 }
1236 });
1237 };
1238
1239 /**
1240 * Show drag and drop animation when textarea is present
1241 *
1242 * This function will enable the drag and drop animation for a specified
1243 * textarea.
1244 *
1245 * @param {HTMLElement} textarea Textarea DOM object to apply editor to
1246 */
1247 phpbb.showDragNDrop = function(textarea) {
1248 if (!textarea) {
1249 return;
1250 }
1251
1252 $('body').on('dragenter dragover', function () {
1253 $(textarea).addClass('drag-n-drop');
1254 }).on('dragleave dragout dragend drop', function() {
1255 $(textarea).removeClass('drag-n-drop');
1256 });
1257 $(textarea).on('dragenter dragover', function () {
1258 $(textarea).addClass('drag-n-drop-highlight');
1259 }).on('dragleave dragout dragend drop', function() {
1260 $(textarea).removeClass('drag-n-drop-highlight');
1261 });
1262 };
1263
1264 /**
1265 * List of classes that toggle dropdown menu,
1266 * list of classes that contain visible dropdown menu
1267 *
1268 * Add your own classes to strings with comma (probably you
1269 * will never need to do that)
1270 */
1271 phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle';
1272 phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible';
1273
1274 /**
1275 * Dropdown toggle event handler
1276 * This handler is used by phpBB.registerDropdown() and other functions
1277 */
1278 phpbb.toggleDropdown = function() {
1279 var $this = $(this),
1280 options = $this.data('dropdown-options'),
1281 parent = options.parent,
1282 visible = parent.hasClass('dropdown-visible'),
1283 direction;
1284
1285 if (!visible) {
1286 // Hide other dropdown menus
1287 $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1288
1289 // Figure out direction of dropdown
1290 direction = options.direction;
1291 var verticalDirection = options.verticalDirection,
1292 offset = $this.offset();
1293
1294 if (direction === 'auto') {
1295 if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) {
1296 direction = 'right';
1297 } else {
1298 direction = 'left';
1299 }
1300 }
1301 parent.toggleClass(options.leftClass, direction === 'left')
1302 .toggleClass(options.rightClass, direction === 'right');
1303
1304 if (verticalDirection === 'auto') {
1305 var height = $(window).height(),
1306 top = offset.top - $(window).scrollTop();
1307
1308 verticalDirection = (top < height * 0.7) ? 'down' : 'up';
1309 }
1310 parent.toggleClass(options.upClass, verticalDirection === 'up')
1311 .toggleClass(options.downClass, verticalDirection === 'down');
1312 }
1313
1314 options.dropdown.toggle();
1315 parent.toggleClass(options.visibleClass, !visible)
1316 .toggleClass('dropdown-visible', !visible);
1317
1318 // Check dimensions when showing dropdown
1319 // !visible because variable shows state of dropdown before it was toggled
1320 if (!visible) {
1321 var windowWidth = $(window).width();
1322
1323 options.dropdown.find('.dropdown-contents').each(function() {
1324 var $this = $(this);
1325
1326 $this.css({
1327 marginLeft: 0,
1328 left: 0,
1329 maxWidth: (windowWidth - 4) + 'px'
1330 });
1331
1332 var offset = $this.offset().left,
1333 width = $this.outerWidth(true);
1334
1335 if (offset < 2) {
1336 $this.css('left', (2 - offset) + 'px');
1337 } else if ((offset + width + 2) > windowWidth) {
1338 $this.css('margin-left', (windowWidth - offset - width - 2) + 'px');
1339 }
1340
1341 // Check whether the vertical scrollbar is present.
1342 $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight());
1343
1344 });
1345 var freeSpace = parent.offset().left - 4;
1346
1347 if (direction === 'left') {
1348 options.dropdown.css('margin-left', '-' + freeSpace + 'px');
1349
1350 // Try to position the notification dropdown correctly in RTL-responsive mode
1351 if (options.dropdown.hasClass('dropdown-extended')) {
1352 var contentWidth,
1353 fullFreeSpace = freeSpace + parent.outerWidth();
1354
1355 options.dropdown.find('.dropdown-contents').each(function() {
1356 contentWidth = parseInt($(this).outerWidth(), 10);
1357 $(this).css({ marginLeft: 0, left: 0 });
1358 });
1359
1360 var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px';
1361 options.dropdown.css({
1362 width: maxOffset,
1363 marginLeft: -maxOffset
1364 });
1365 }
1366 } else {
1367 options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px');
1368 }
1369 }
1370
1371 // Prevent event propagation
1372 if (arguments.length > 0) {
1373 try {
1374 var e = arguments[0];
1375 e.preventDefault();
1376 e.stopPropagation();
1377 } catch (error) { }
1378 }
1379 return false;
1380 };
1381
1382 /**
1383 * Toggle dropdown submenu
1384 */
1385 phpbb.toggleSubmenu = function(e) {
1386 $(this).siblings('.dropdown-submenu').toggle();
1387 e.preventDefault();
1388 };
1389
1390 /**
1391 * Register dropdown menu
1392 * Shows/hides dropdown, decides which side to open to
1393 *
1394 * @param {jQuery} toggle Link that toggles dropdown.
1395 * @param {jQuery} dropdown Dropdown menu.
1396 * @param {Object} options List of options. Optional.
1397 */
1398 phpbb.registerDropdown = function(toggle, dropdown, options) {
1399 var ops = {
1400 parent: toggle.parent(), // Parent item to add classes to
1401 direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right
1402 verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down
1403 visibleClass: 'visible', // Class to add to parent item when dropdown is visible
1404 leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side
1405 rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side
1406 upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item
1407 downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item
1408 };
1409 if (options) {
1410 ops = $.extend(ops, options);
1411 }
1412 ops.dropdown = dropdown;
1413
1414 ops.parent.addClass('dropdown-container');
1415 toggle.addClass('dropdown-toggle');
1416
1417 toggle.data('dropdown-options', ops);
1418
1419 toggle.click(phpbb.toggleDropdown);
1420 $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu);
1421 };
1422
1423 /**
1424 * Get the HTML for a color palette table.
1425 *
1426 * @param {string} dir Palette direction - either v or h
1427 * @param {int} width Palette cell width.
1428 * @param {int} height Palette cell height.
1429 */
1430 phpbb.colorPalette = function(dir, width, height) {
1431 var r, g, b,
1432 numberList = new Array(6),
1433 color = '',
1434 html = '';
1435
1436 numberList[0] = '00';
1437 numberList[1] = '40';
1438 numberList[2] = '80';
1439 numberList[3] = 'BF';
1440 numberList[4] = 'FF';
1441
1442 var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette';
1443 html += '<table class="not-responsive colour-palette ' + tableClass + '" style="width: auto;">';
1444
1445 for (r = 0; r < 5; r++) {
1446 if (dir === 'h') {
1447 html += '<tr>';
1448 }
1449
1450 for (g = 0; g < 5; g++) {
1451 if (dir === 'v') {
1452 html += '<tr>';
1453 }
1454
1455 for (b = 0; b < 5; b++) {
1456 color = '' + numberList[r] + numberList[g] + numberList[b];
1457 html += '<td style="background-color: #' + color + '; width: ' + width + 'px; height: ' +
1458 height + 'px;"><a href="#" data-color="' + color + '" style="display: block; width: ' +
1459 width + 'px; height: ' + height + 'px; " alt="#' + color + '" title="#' + color + '"></a>';
1460 html += '</td>';
1461 }
1462
1463 if (dir === 'v') {
1464 html += '</tr>';
1465 }
1466 }
1467
1468 if (dir === 'h') {
1469 html += '</tr>';
1470 }
1471 }
1472 html += '</table>';
1473 return html;
1474 };
1475
1476 /**
1477 * Register a color palette.
1478 *
1479 * @param {jQuery} el jQuery object for the palette container.
1480 */
1481 phpbb.registerPalette = function(el) {
1482 var orientation = el.attr('data-orientation'),
1483 height = el.attr('data-height'),
1484 width = el.attr('data-width'),
1485 target = el.attr('data-target'),
1486 bbcode = el.attr('data-bbcode');
1487
1488 // Insert the palette HTML into the container.
1489 el.html(phpbb.colorPalette(orientation, width, height));
1490
1491 // Add toggle control.
1492 $('#color_palette_toggle').click(function(e) {
1493 el.toggle();
1494 e.preventDefault();
1495 });
1496
1497 // Attach event handler when a palette cell is clicked.
1498 $(el).on('click', 'a', function(e) {
1499 var color = $(this).attr('data-color');
1500
1501 if (bbcode) {
1502 bbfontstyle('[color=#' + color + ']', '[/color]');
1503 } else {
1504 $(target).val(color);
1505 }
1506 e.preventDefault();
1507 });
1508 };
1509
1510 /**
1511 * Set display of page element
1512 *
1513 * @param {string} id The ID of the element to change
1514 * @param {int} action Set to 0 if element display should be toggled, -1 for
1515 * hiding the element, and 1 for showing it.
1516 * @param {string} type Display type that should be used, e.g. inline, block or
1517 * other CSS "display" types
1518 */
1519 phpbb.toggleDisplay = function(id, action, type) {
1520 if (!type) {
1521 type = 'block';
1522 }
1523
1524 var $element = $('#' + id);
1525
1526 var display = $element.css('display');
1527 if (!action) {
1528 action = (display === '' || display === type) ? -1 : 1;
1529 }
1530 $element.css('display', ((action === 1) ? type : 'none'));
1531 };
1532
1533 /**
1534 * Toggle additional settings based on the selected
1535 * option of select element.
1536 *
1537 * @param {jQuery} el jQuery select element object.
1538 */
1539 phpbb.toggleSelectSettings = function(el) {
1540 el.children().each(function() {
1541 var $this = $(this),
1542 $setting = $($this.data('toggle-setting'));
1543 $setting.toggle($this.is(':selected'));
1544
1545 // Disable any input elements that are not visible right now
1546 if ($this.is(':selected')) {
1547 $($this.data('toggle-setting') + ' input').prop('disabled', false);
1548 } else {
1549 $($this.data('toggle-setting') + ' input').prop('disabled', true);
1550 }
1551 });
1552 };
1553
1554 /**
1555 * Get function from name.
1556 * Based on http://stackoverflow.com/a/359910
1557 *
1558 * @param {string} functionName Function to get.
1559 * @returns function
1560 */
1561 phpbb.getFunctionByName = function (functionName) {
1562 var namespaces = functionName.split('.'),
1563 func = namespaces.pop(),
1564 context = window;
1565
1566 for (var i = 0; i < namespaces.length; i++) {
1567 context = context[namespaces[i]];
1568 }
1569 return context[func];
1570 };
1571
1572 /**
1573 * Register page dropdowns.
1574 */
1575 phpbb.registerPageDropdowns = function() {
1576 var $body = $('body');
1577
1578 $body.find('.dropdown-container').each(function() {
1579 var $this = $(this),
1580 $trigger = $this.find('.dropdown-trigger:first'),
1581 $contents = $this.find('.dropdown'),
1582 options = {
1583 direction: 'auto',
1584 verticalDirection: 'auto'
1585 },
1586 data;
1587
1588 if (!$trigger.length) {
1589 data = $this.attr('data-dropdown-trigger');
1590 $trigger = data ? $this.children(data) : $this.children('a:first');
1591 }
1592
1593 if (!$contents.length) {
1594 data = $this.attr('data-dropdown-contents');
1595 $contents = data ? $this.children(data) : $this.children('div:first');
1596 }
1597
1598 if (!$trigger.length || !$contents.length) {
1599 return;
1600 }
1601
1602 if ($this.hasClass('dropdown-up')) {
1603 options.verticalDirection = 'up';
1604 }
1605 if ($this.hasClass('dropdown-down')) {
1606 options.verticalDirection = 'down';
1607 }
1608 if ($this.hasClass('dropdown-left')) {
1609 options.direction = 'left';
1610 }
1611 if ($this.hasClass('dropdown-right')) {
1612 options.direction = 'right';
1613 }
1614
1615 phpbb.registerDropdown($trigger, $contents, options);
1616 });
1617
1618 // Hide active dropdowns when click event happens outside
1619 $body.click(function(e) {
1620 var $parents = $(e.target).parents();
1621 if (!$parents.is(phpbb.dropdownVisibleContainers)) {
1622 $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1623 }
1624 });
1625 };
1626
1627 /**
1628 * Handle avatars to be lazy loaded.
1629 */
1630 phpbb.lazyLoadAvatars = function loadAvatars() {
1631 $('.avatar[data-src]').each(function () {
1632 var $avatar = $(this);
1633
1634 $avatar
1635 .attr('src', $avatar.data('src'))
1636 .removeAttr('data-src');
1637 });
1638 };
1639
1640 $(window).load(phpbb.lazyLoadAvatars);
1641
1642 /**
1643 * Apply code editor to all textarea elements with data-bbcode attribute
1644 */
1645 $(function() {
1646 $('textarea[data-bbcode]').each(function() {
1647 phpbb.applyCodeEditor(this);
1648 });
1649
1650 phpbb.registerPageDropdowns();
1651
1652 $('#color_palette_placeholder').each(function() {
1653 phpbb.registerPalette($(this));
1654 });
1655
1656 // Update browser history URL to point to specific post in viewtopic.php
1657 // when using view=unread#unread link.
1658 phpbb.history.replaceUrl($('#unread[data-url]').data('url'));
1659
1660 // Hide settings that are not selected via select element.
1661 $('select[data-togglable-settings]').each(function() {
1662 var $this = $(this);
1663
1664 $this.change(function() {
1665 phpbb.toggleSelectSettings($this);
1666 });
1667 phpbb.toggleSelectSettings($this);
1668 });
1669 });
1670
1671 })(jQuery); // Avoid conflicts with other libraries
1672