Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

core.js

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 42.43 KiB


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