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