Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

core.js

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


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