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.
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: 43.92 KiB


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('&amp;', '&');
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('&amp;', '&');
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