[ Index ]

PHP Cross Reference of phpBB-3.1.12-deutsch

title

Body

[close]

/assets/javascript/ -> core.js (source)

   1  /* global bbfontstyle */
   2  
   3  var phpbb = {};
   4  phpbb.alertTime = 100;
   5  
   6  (function($) {  // Avoid conflicts with other libraries
   7  
   8  'use strict';
   9  
  10  // define a couple constants for keydown functions.
  11  var keymap = {
  12      TAB: 9,
  13      ENTER: 13,
  14      ESC: 27
  15  };
  16  
  17  var $dark = $('#darkenwrapper');
  18  var $loadingIndicator;
  19  var phpbbAlertTimer = null;
  20  
  21  phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined');
  22  
  23  /**
  24   * Display a loading screen
  25   *
  26   * @returns {object} Returns loadingIndicator.
  27   */
  28  phpbb.loadingIndicator = function() {
  29      if (!$loadingIndicator) {
  30          $loadingIndicator = $('<div />', { id: 'loading_indicator' });
  31          $loadingIndicator.appendTo('#page-footer');
  32      }
  33  
  34      if (!$loadingIndicator.is(':visible')) {
  35          $loadingIndicator.fadeIn(phpbb.alertTime);
  36          // Wait 60 seconds and display an error if nothing has been returned by then.
  37          phpbb.clearLoadingTimeout();
  38          phpbbAlertTimer = setTimeout(function() {
  39              phpbb.showTimeoutMessage();
  40          }, 60000);
  41      }
  42  
  43      return $loadingIndicator;
  44  };
  45  
  46  /**
  47   * Show timeout message
  48   */
  49  phpbb.showTimeoutMessage = function () {
  50      var $alert = $('#phpbb_alert');
  51  
  52      if ($loadingIndicator.is(':visible')) {
  53          phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req'));
  54      }
  55  };
  56  
  57  /**
  58   * Clear loading alert timeout
  59  */
  60  phpbb.clearLoadingTimeout = function() {
  61      if (phpbbAlertTimer !== null) {
  62          clearTimeout(phpbbAlertTimer);
  63          phpbbAlertTimer = null;
  64      }
  65  };
  66  
  67  
  68  /**
  69  * Close popup alert after a specified delay
  70  *
  71  * @param {int} delay Delay in ms until darkenwrapper's click event is triggered
  72  */
  73  phpbb.closeDarkenWrapper = function(delay) {
  74      phpbbAlertTimer = setTimeout(function() {
  75          $('#darkenwrapper').trigger('click');
  76      }, delay);
  77  };
  78  
  79  /**
  80   * Display a simple alert similar to JSs native alert().
  81   *
  82   * You can only call one alert or confirm box at any one time.
  83   *
  84   * @param {string} title Title of the message, eg "Information" (HTML).
  85   * @param {string} msg Message to display (HTML).
  86   *
  87   * @returns {object} Returns the div created.
  88   */
  89  phpbb.alert = function(title, msg) {
  90      var $alert = $('#phpbb_alert');
  91      $alert.find('.alert_title').html(title);
  92      $alert.find('.alert_text').html(msg);
  93  
  94      $(document).on('keydown.phpbb.alert', function(e) {
  95          if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
  96              phpbb.alert.close($alert, true);
  97              e.preventDefault();
  98              e.stopPropagation();
  99          }
 100      });
 101      phpbb.alert.open($alert);
 102  
 103      return $alert;
 104  };
 105  
 106  /**
 107  * Handler for opening an alert box.
 108  *
 109  * @param {jQuery} $alert jQuery object.
 110  */
 111  phpbb.alert.open = function($alert) {
 112      if (!$dark.is(':visible')) {
 113          $dark.fadeIn(phpbb.alertTime);
 114      }
 115  
 116      if ($loadingIndicator && $loadingIndicator.is(':visible')) {
 117          $loadingIndicator.fadeOut(phpbb.alertTime, function() {
 118              $dark.append($alert);
 119              $alert.fadeIn(phpbb.alertTime);
 120          });
 121      } else if ($dark.is(':visible')) {
 122          $dark.append($alert);
 123          $alert.fadeIn(phpbb.alertTime);
 124      } else {
 125          $dark.append($alert);
 126          $alert.show();
 127          $dark.fadeIn(phpbb.alertTime);
 128      }
 129  
 130      $alert.on('click', function(e) {
 131          e.stopPropagation();
 132      });
 133  
 134      $dark.one('click', function(e) {
 135          phpbb.alert.close($alert, true);
 136          e.preventDefault();
 137          e.stopPropagation();
 138      });
 139  
 140      $alert.find('.alert_close').one('click', function(e) {
 141          phpbb.alert.close($alert, true);
 142          e.preventDefault();
 143      });
 144  };
 145  
 146  /**
 147  * Handler for closing an alert box.
 148  *
 149  * @param {jQuery} $alert jQuery object.
 150  * @param {bool} fadedark Whether to remove dark background.
 151  */
 152  phpbb.alert.close = function($alert, fadedark) {
 153      var $fade = (fadedark) ? $dark : $alert;
 154  
 155      $fade.fadeOut(phpbb.alertTime, function() {
 156          $alert.hide();
 157      });
 158  
 159      $alert.find('.alert_close').off('click');
 160      $(document).off('keydown.phpbb.alert');
 161  };
 162  
 163  /**
 164   * Display a simple yes / no box to the user.
 165   *
 166   * You can only call one alert or confirm box at any one time.
 167   *
 168   * @param {string} msg Message to display (HTML).
 169   * @param {function} callback Callback. Bool param, whether the user pressed
 170   *     yes or no (or whatever their language is).
 171   * @param {bool} fadedark Remove the dark background when done? Defaults
 172   *     to yes.
 173   *
 174   * @returns {object} Returns the div created.
 175   */
 176  phpbb.confirm = function(msg, callback, fadedark) {
 177      var $confirmDiv = $('#phpbb_confirm');
 178      $confirmDiv.find('.alert_text').html(msg);
 179      fadedark = fadedark || true;
 180  
 181      $(document).on('keydown.phpbb.alert', function(e) {
 182          if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) {
 183              var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel';
 184  
 185              $('input[name="' + name + '"]').trigger('click');
 186              e.preventDefault();
 187              e.stopPropagation();
 188          }
 189      });
 190  
 191      $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) {
 192          var confirmed = this.name === 'confirm';
 193  
 194          if (confirmed) {
 195              callback(true);
 196          }
 197          $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox');
 198          phpbb.alert.close($confirmDiv, fadedark || !confirmed);
 199  
 200          e.preventDefault();
 201          e.stopPropagation();
 202      });
 203  
 204      phpbb.alert.open($confirmDiv);
 205  
 206      return $confirmDiv;
 207  };
 208  
 209  /**
 210   * Turn a querystring into an array.
 211   *
 212   * @argument {string} string The querystring to parse.
 213   * @returns {object} The object created.
 214   */
 215  phpbb.parseQuerystring = function(string) {
 216      var params = {}, i, split;
 217  
 218      string = string.split('&');
 219      for (i = 0; i < string.length; i++) {
 220          split = string[i].split('=');
 221          params[split[0]] = decodeURIComponent(split[1]);
 222      }
 223      return params;
 224  };
 225  
 226  
 227  /**
 228   * Makes a link use AJAX instead of loading an entire page.
 229   *
 230   * This function will work for links (both standard links and links which
 231   * invoke confirm_box) and forms. It will be called automatically for links
 232   * and forms with the data-ajax attribute set, and will call the necessary
 233   * callback.
 234   *
 235   * For more info, view the following page on the phpBB wiki:
 236   * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify
 237   *
 238   * @param {object} options Options.
 239   */
 240  phpbb.ajaxify = function(options) {
 241      var $elements = $(options.selector),
 242          refresh = options.refresh,
 243          callback = options.callback,
 244          overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true,
 245          isForm = $elements.is('form'),
 246          isText = $elements.is('input[type="text"], textarea'),
 247          eventName;
 248  
 249      if (isForm) {
 250          eventName = 'submit';
 251      } else if (isText) {
 252          eventName = 'keyup';
 253      } else {
 254          eventName = 'click';
 255      }
 256  
 257      $elements.on(eventName, function(event) {
 258          var action, method, data, submit, that = this, $this = $(this);
 259  
 260          if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') {
 261              return;
 262          }
 263  
 264          /**
 265           * Handler for AJAX errors
 266           */
 267  		function errorHandler(jqXHR, textStatus, errorThrown) {
 268              if (typeof console !== 'undefined' && console.log) {
 269                  console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown);
 270              }
 271              phpbb.clearLoadingTimeout();
 272              var responseText, errorText = false;
 273              try {
 274                  responseText = JSON.parse(jqXHR.responseText);
 275                  responseText = responseText.message;
 276              } catch (e) {}
 277              if (typeof responseText === 'string' && responseText.length > 0) {
 278                  errorText = responseText;
 279              } else if (typeof errorThrown === 'string' && errorThrown.length > 0) {
 280                  errorText = errorThrown;
 281              } else {
 282                  errorText = $dark.attr('data-ajax-error-text-' + textStatus);
 283                  if (typeof errorText !== 'string' || !errorText.length) {
 284                      errorText = $dark.attr('data-ajax-error-text');
 285                  }
 286              }
 287              phpbb.alert($dark.attr('data-ajax-error-title'), errorText);
 288          }
 289  
 290          /**
 291           * This is a private function used to handle the callbacks, refreshes
 292           * and alert. It calls the callback, refreshes the page if necessary, and
 293           * displays an alert to the user and removes it after an amount of time.
 294           *
 295           * It cannot be called from outside this function, and is purely here to
 296           * avoid repetition of code.
 297           *
 298           * @param {object} res The object sent back by the server.
 299           */
 300  		function returnHandler(res) {
 301              var alert;
 302  
 303              phpbb.clearLoadingTimeout();
 304  
 305              // Is a confirmation required?
 306              if (typeof res.S_CONFIRM_ACTION === 'undefined') {
 307                  // If a confirmation is not required, display an alert and call the
 308                  // callbacks.
 309                  if (typeof res.MESSAGE_TITLE !== 'undefined') {
 310                      alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT);
 311                  } else {
 312                      $dark.fadeOut(phpbb.alertTime);
 313  
 314                      if ($loadingIndicator) {
 315                          $loadingIndicator.fadeOut(phpbb.alertTime);
 316                      }
 317                  }
 318  
 319                  if (typeof phpbb.ajaxCallbacks[callback] === 'function') {
 320                      phpbb.ajaxCallbacks[callback].call(that, res);
 321                  }
 322  
 323                  // If the server says to refresh the page, check whether the page should
 324                  // be refreshed and refresh page after specified time if required.
 325                  if (res.REFRESH_DATA) {
 326                      if (typeof refresh === 'function') {
 327                          refresh = refresh(res.REFRESH_DATA.url);
 328                      } else if (typeof refresh !== 'boolean') {
 329                          refresh = false;
 330                      }
 331  
 332                      phpbbAlertTimer = setTimeout(function() {
 333                          if (refresh) {
 334                              window.location = res.REFRESH_DATA.url;
 335                          }
 336  
 337                          // Hide the alert even if we refresh the page, in case the user
 338                          // presses the back button.
 339                          $dark.fadeOut(phpbb.alertTime, function() {
 340                              if (typeof alert !== 'undefined') {
 341                                  alert.hide();
 342                              }
 343                          });
 344                      }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds
 345                  }
 346              } else {
 347                  // If confirmation is required, display a dialog to the user.
 348                  phpbb.confirm(res.MESSAGE_BODY, function(del) {
 349                      if (!del) {
 350                          return;
 351                      }
 352  
 353                      phpbb.loadingIndicator();
 354                      data =  $('<form>' + res.S_HIDDEN_FIELDS + '</form>').serialize();
 355                      $.ajax({
 356                          url: res.S_CONFIRM_ACTION,
 357                          type: 'POST',
 358                          data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(),
 359                          success: returnHandler,
 360                          error: errorHandler
 361                      });
 362                  }, false);
 363              }
 364          }
 365  
 366          // If the element is a form, POST must be used and some extra data must
 367          // be taken from the form.
 368          var runFilter = (typeof options.filter === 'function');
 369          data = {};
 370  
 371          if (isForm) {
 372              action = $this.attr('action').replace('&amp;', '&');
 373              data = $this.serializeArray();
 374              method = $this.attr('method') || 'GET';
 375  
 376              if ($this.find('input[type="submit"][data-clicked]')) {
 377                  submit = $this.find('input[type="submit"][data-clicked]');
 378                  data.push({
 379                      name: submit.attr('name'),
 380                      value: submit.val()
 381                  });
 382              }
 383          } else if (isText) {
 384              var name = $this.attr('data-name') || this.name;
 385              action = $this.attr('data-url').replace('&amp;', '&');
 386              data[name] = this.value;
 387              method = 'POST';
 388          } else {
 389              action = this.href;
 390              data = null;
 391              method = 'GET';
 392          }
 393  
 394          var sendRequest = function() {
 395              var dataOverlay = $this.attr('data-overlay');
 396              if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) {
 397                  phpbb.loadingIndicator();
 398              }
 399  
 400              var request = $.ajax({
 401                  url: action,
 402                  type: method,
 403                  data: data,
 404                  success: returnHandler,
 405                  error: errorHandler,
 406                  cache: false
 407              });
 408  
 409              request.always(function() {
 410                  if ($loadingIndicator && $loadingIndicator.is(':visible')) {
 411                      $loadingIndicator.fadeOut(phpbb.alertTime);
 412                  }
 413              });
 414          };
 415  
 416          // If filter function returns false, cancel the AJAX functionality,
 417          // and return true (meaning that the HTTP request will be sent normally).
 418          if (runFilter && !options.filter.call(this, data, event, sendRequest)) {
 419              return;
 420          }
 421  
 422          sendRequest();
 423          event.preventDefault();
 424      });
 425  
 426      if (isForm) {
 427          $elements.find('input:submit').click(function () {
 428              var $this = $(this);
 429  
 430              // Remove data-clicked attribute from any submit button of form
 431              $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked');
 432  
 433              $this.attr('data-clicked', 'true');
 434          });
 435      }
 436  
 437      return this;
 438  };
 439  
 440  phpbb.search = {
 441      cache: {
 442          data: []
 443      },
 444      tpl: [],
 445      container: []
 446  };
 447  
 448  /**
 449   * Get cached search data.
 450   *
 451   * @param {string} id Search ID.
 452   * @returns {bool|object} Cached data object. Returns false if no data exists.
 453   */
 454  phpbb.search.cache.get = function(id) {
 455      if (this.data[id]) {
 456          return this.data[id];
 457      }
 458      return false;
 459  };
 460  
 461  /**
 462   * Set search cache data value.
 463   *
 464   * @param {string} id        Search ID.
 465   * @param {string} key        Data key.
 466   * @param {string} value    Data value.
 467   */
 468  phpbb.search.cache.set = function(id, key, value) {
 469      if (!this.data[id]) {
 470          this.data[id] = { results: [] };
 471      }
 472      this.data[id][key] = value;
 473  };
 474  
 475  /**
 476   * Cache search result.
 477   *
 478   * @param {string} id        Search ID.
 479   * @param {string} keyword    Keyword.
 480   * @param {Array} results    Search results.
 481   */
 482  phpbb.search.cache.setResults = function(id, keyword, results) {
 483      this.data[id].results[keyword] = results;
 484  };
 485  
 486  /**
 487   * Trim spaces from keyword and lower its case.
 488   *
 489   * @param {string} keyword Search keyword to clean.
 490   * @returns {string} Cleaned string.
 491   */
 492  phpbb.search.cleanKeyword = function(keyword) {
 493      return $.trim(keyword).toLowerCase();
 494  };
 495  
 496  /**
 497   * Get clean version of search keyword. If textarea supports several keywords
 498   * (one per line), it fetches the current keyword based on the caret position.
 499   *
 500   * @param {jQuery} $input    Search input|textarea.
 501   * @param {string} keyword    Input|textarea value.
 502   * @param {bool} multiline    Whether textarea supports multiple search keywords.
 503   *
 504   * @returns string Clean string.
 505   */
 506  phpbb.search.getKeyword = function($input, keyword, multiline) {
 507      if (multiline) {
 508          var line = phpbb.search.getKeywordLine($input);
 509          keyword = keyword.split('\n').splice(line, 1);
 510      }
 511      return phpbb.search.cleanKeyword(keyword);
 512  };
 513  
 514  /**
 515   * Get the textarea line number on which the keyword resides - for textareas
 516   * that support multiple keywords (one per line).
 517   *
 518   * @param {jQuery} $textarea Search textarea.
 519   * @returns {int} The line number.
 520   */
 521  phpbb.search.getKeywordLine = function ($textarea) {
 522      var selectionStart = $textarea.get(0).selectionStart;
 523      return $textarea.val().substr(0, selectionStart).split('\n').length - 1;
 524  };
 525  
 526  /**
 527   * Set the value on the input|textarea. If textarea supports multiple
 528   * keywords, only the active keyword is replaced.
 529   *
 530   * @param {jQuery} $input    Search input|textarea.
 531   * @param {string} value    Value to set.
 532   * @param {bool} multiline    Whether textarea supports multiple search keywords.
 533   */
 534  phpbb.search.setValue = function($input, value, multiline) {
 535      if (multiline) {
 536          var line = phpbb.search.getKeywordLine($input),
 537              lines = $input.val().split('\n');
 538          lines[line] = value;
 539          value = lines.join('\n');
 540      }
 541      $input.val(value);
 542  };
 543  
 544  /**
 545   * Sets the onclick event to set the value on the input|textarea to the
 546   * selected search result.
 547   *
 548   * @param {jQuery} $input        Search input|textarea.
 549   * @param {object} value        Result object.
 550   * @param {jQuery} $row            Result element.
 551   * @param {jQuery} $container    jQuery object for the search container.
 552   */
 553  phpbb.search.setValueOnClick = function($input, value, $row, $container) {
 554      $row.click(function() {
 555          phpbb.search.setValue($input, value.result, $input.attr('data-multiline'));
 556          $container.hide();
 557      });
 558  };
 559  
 560  /**
 561   * Runs before the AJAX search request is sent and determines whether
 562   * there is a need to contact the server. If there are cached results
 563   * already, those are displayed instead. Executes the AJAX request function
 564   * itself due to the need to use a timeout to limit the number of requests.
 565   *
 566   * @param {Array} data                Data to be sent to the server.
 567   * @param {object} event            Onkeyup event object.
 568   * @param {function} sendRequest    Function to execute AJAX request.
 569   *
 570   * @returns {bool} Returns false.
 571   */
 572  phpbb.search.filter = function(data, event, sendRequest) {
 573      var $this = $(this),
 574          dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'),
 575          minLength = parseInt($this.attr('data-min-length'), 10),
 576          searchID = $this.attr('data-results'),
 577          keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')),
 578          cache = phpbb.search.cache.get(searchID),
 579          proceed = true;
 580      data[dataName] = keyword;
 581  
 582      if (cache.timeout) {
 583          clearTimeout(cache.timeout);
 584      }
 585  
 586      var timeout = setTimeout(function() {
 587          // Check min length and existence of cache.
 588          if (minLength > keyword.length) {
 589              proceed = false;
 590          } else if (cache.lastSearch) {
 591              // Has the keyword actually changed?
 592              if (cache.lastSearch === keyword) {
 593                  proceed = false;
 594              } else {
 595                  // Do we already have results for this?
 596                  if (cache.results[keyword]) {
 597                      var response = {
 598                          keyword: keyword,
 599                          results: cache.results[keyword]
 600                      };
 601                      phpbb.search.handleResponse(response, $this, true);
 602                      proceed = false;
 603                  }
 604  
 605                  // If the previous search didn't yield results and the string only had characters added to it,
 606                  // then we won't bother sending a request.
 607                  if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) {
 608                      phpbb.search.cache.set(searchID, 'lastSearch', keyword);
 609                      phpbb.search.cache.setResults(searchID, keyword, []);
 610                      proceed = false;
 611                  }
 612              }
 613          }
 614  
 615          if (proceed) {
 616              sendRequest.call(this);
 617          }
 618      }, 350);
 619      phpbb.search.cache.set(searchID, 'timeout', timeout);
 620  
 621      return false;
 622  };
 623  
 624  /**
 625   * Handle search result response.
 626   *
 627   * @param {object} res            Data received from server.
 628   * @param {jQuery} $input        Search input|textarea.
 629   * @param {bool} fromCache        Whether the results are from the cache.
 630   * @param {function} callback    Optional callback to run when assigning each search result.
 631   */
 632  phpbb.search.handleResponse = function(res, $input, fromCache, callback) {
 633      if (typeof res !== 'object') {
 634          return;
 635      }
 636  
 637      var searchID = $input.attr('data-results'),
 638          $container = $(searchID);
 639  
 640      if (this.cache.get(searchID).callback) {
 641          callback = this.cache.get(searchID).callback;
 642      } else if (typeof callback === 'function') {
 643          this.cache.set(searchID, 'callback', callback);
 644      }
 645  
 646      if (!fromCache) {
 647          this.cache.setResults(searchID, res.keyword, res.results);
 648      }
 649  
 650      this.cache.set(searchID, 'lastSearch', res.keyword);
 651      this.showResults(res.results, $input, $container, callback);
 652  };
 653  
 654  /**
 655   * Show search results.
 656   *
 657   * @param {Array} results        Search results.
 658   * @param {jQuery} $input        Search input|textarea.
 659   * @param {jQuery} $container    Search results container element.
 660   * @param {function} callback    Optional callback to run when assigning each search result.
 661   */
 662  phpbb.search.showResults = function(results, $input, $container, callback) {
 663      var $resultContainer = $('.search-results', $container);
 664      this.clearResults($resultContainer);
 665  
 666      if (!results.length) {
 667          $container.hide();
 668          return;
 669      }
 670  
 671      var searchID = $container.attr('id'),
 672          tpl,
 673          row;
 674  
 675      if (!this.tpl[searchID]) {
 676          tpl = $('.search-result-tpl', $container);
 677          this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl');
 678          tpl.remove();
 679      }
 680      tpl = this.tpl[searchID];
 681  
 682      $.each(results, function(i, item) {
 683          row = tpl.clone();
 684          row.find('.search-result').html(item.display);
 685  
 686          if (typeof callback === 'function') {
 687              callback.call(this, $input, item, row, $container);
 688          }
 689          row.appendTo($resultContainer).show();
 690      });
 691      $container.show();
 692  };
 693  
 694  /**
 695   * Clear search results.
 696   *
 697   * @param {jQuery} $container Search results container.
 698   */
 699  phpbb.search.clearResults = function($container) {
 700      $container.children(':not(.search-result-tpl)').remove();
 701  };
 702  
 703  $('#phpbb').click(function() {
 704      var $this = $(this);
 705  
 706      if (!$this.is('.live-search') && !$this.parents().is('.live-search')) {
 707          $('.live-search').hide();
 708      }
 709  });
 710  
 711  phpbb.history = {};
 712  
 713  /**
 714  * Check whether a method in the native history object is supported.
 715  *
 716  * @param {string} fn Method name.
 717  * @returns {bool} Returns true if the method is supported.
 718  */
 719  phpbb.history.isSupported = function(fn) {
 720      return !(typeof history === 'undefined' || typeof history[fn] === 'undefined');
 721  };
 722  
 723  /**
 724  * Wrapper for the pushState and replaceState methods of the
 725  * native history object.
 726  *
 727  * @param {string} mode        Mode. Either push or replace.
 728  * @param {string} url        New URL.
 729  * @param {string} [title]    Optional page title.
 730  * @param {object} [obj]        Optional state object.
 731  */
 732  phpbb.history.alterUrl = function(mode, url, title, obj) {
 733      var fn = mode + 'State';
 734  
 735      if (!url || !phpbb.history.isSupported(fn)) {
 736          return;
 737      }
 738      if (!title) {
 739          title = document.title;
 740      }
 741      if (!obj) {
 742          obj = null;
 743      }
 744  
 745      history[fn](obj, title, url);
 746  };
 747  
 748  /**
 749  * Wrapper for the native history.replaceState method.
 750  *
 751  * @param {string} url        New URL.
 752  * @param {string} [title]    Optional page title.
 753  * @param {object} [obj]        Optional state object.
 754  */
 755  phpbb.history.replaceUrl = function(url, title, obj) {
 756      phpbb.history.alterUrl('replace', url, title, obj);
 757  };
 758  
 759  /**
 760  * Wrapper for the native history.pushState method.
 761  *
 762  * @param {string} url        New URL.
 763  * @param {string} [title]    Optional page title.
 764  * @param {object} [obj]        Optional state object.
 765  */
 766  phpbb.history.pushUrl = function(url, title, obj) {
 767      phpbb.history.alterUrl('push', url, title, obj);
 768  };
 769  
 770  /**
 771  * Hide the optgroups that are not the selected timezone
 772  *
 773  * @param {bool} keepSelection Shall we keep the value selected, or shall the
 774  *     user be forced to repick one.
 775  */
 776  phpbb.timezoneSwitchDate = function(keepSelection) {
 777      var $timezoneCopy = $('#timezone_copy');
 778      var $timezone = $('#timezone');
 779      var $tzDate = $('#tz_date');
 780      var $tzSelectDateSuggest = $('#tz_select_date_suggest');
 781  
 782      if ($timezoneCopy.length === 0) {
 783          // We make a backup of the original dropdown, so we can remove optgroups
 784          // instead of setting display to none, because IE and chrome will not
 785          // hide options inside of optgroups and selects via css
 786          $timezone.clone()
 787              .attr('id', 'timezone_copy')
 788              .css('display', 'none')
 789              .attr('name', 'tz_copy')
 790              .insertAfter('#timezone');
 791      } else {
 792          // Copy the content of our backup, so we can remove all unneeded options
 793          $timezone.html($timezoneCopy.html());
 794      }
 795  
 796      if ($tzDate.val() !== '') {
 797          $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])');
 798      }
 799  
 800      if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) {
 801          $tzSelectDateSuggest.css('display', 'none');
 802      } else {
 803          $tzSelectDateSuggest.css('display', 'inline');
 804      }
 805  
 806      var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option');
 807  
 808      if ($tzOptions.length === 1) {
 809          // If there is only one timezone for the selected date, we just select that automatically.
 810          $tzOptions.prop('selected', true);
 811          keepSelection = true;
 812      }
 813  
 814      if (typeof keepSelection !== 'undefined' && !keepSelection) {
 815          var $timezoneOptions = $timezone.find('optgroup option');
 816          if ($timezoneOptions.filter(':selected').length <= 0) {
 817              $timezoneOptions.filter(':first').prop('selected', true);
 818          }
 819      }
 820  };
 821  
 822  /**
 823  * Display the date/time select
 824  */
 825  phpbb.timezoneEnableDateSelection = function() {
 826      $('#tz_select_date').css('display', 'block');
 827  };
 828  
 829  /**
 830  * Preselect a date/time or suggest one, if it is not picked.
 831  *
 832  * @param {bool} forceSelector Shall we select the suggestion?
 833  */
 834  phpbb.timezonePreselectSelect = function(forceSelector) {
 835  
 836      // The offset returned here is in minutes and negated.
 837      var offset = (new Date()).getTimezoneOffset();
 838      var sign = '-';
 839  
 840      if (offset < 0) {
 841          sign = '+';
 842          offset = -offset;
 843      }
 844  
 845      var minutes = offset % 60;
 846      var hours = (offset - minutes) / 60;
 847  
 848      if (hours < 10) {
 849          hours = '0' + hours.toString();
 850      } else {
 851          hours = hours.toString();
 852      }
 853  
 854      if (minutes < 10) {
 855          minutes = '0' + minutes.toString();
 856      } else {
 857          minutes = minutes.toString();
 858      }
 859  
 860      var prefix = 'UTC' + sign + hours + ':' + minutes;
 861      var prefixLength = prefix.length;
 862      var selectorOptions = $('option', '#tz_date');
 863      var i;
 864  
 865      var $tzSelectDateSuggest = $('#tz_select_date_suggest');
 866  
 867      for (i = 0; i < selectorOptions.length; ++i) {
 868          var option = selectorOptions[i];
 869  
 870          if (option.value.substring(0, prefixLength) === prefix) {
 871              if ($('#tz_date').val() !== option.value && !forceSelector) {
 872                  // We do not select the option for the user, but notify him,
 873                  // that we would suggest a different setting.
 874                  phpbb.timezoneSwitchDate(true);
 875                  $tzSelectDateSuggest.css('display', 'inline');
 876              } else {
 877                  option.selected = true;
 878                  phpbb.timezoneSwitchDate(!forceSelector);
 879                  $tzSelectDateSuggest.css('display', 'none');
 880              }
 881  
 882              var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion');
 883  
 884              $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML));
 885              $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9)));
 886              $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML);
 887  
 888              // Found the suggestion, there cannot be more, so return from here.
 889              return;
 890          }
 891      }
 892  };
 893  
 894  phpbb.ajaxCallbacks = {};
 895  
 896  /**
 897   * Adds an AJAX callback to be used by phpbb.ajaxify.
 898   *
 899   * See the phpbb.ajaxify comments for information on stuff like parameters.
 900   *
 901   * @param {string} id The name of the callback.
 902   * @param {function} callback The callback to be called.
 903   */
 904  phpbb.addAjaxCallback = function(id, callback) {
 905      if (typeof callback === 'function') {
 906          phpbb.ajaxCallbacks[id] = callback;
 907      }
 908      return this;
 909  };
 910  
 911  /**
 912   * This callback handles live member searches.
 913   */
 914  phpbb.addAjaxCallback('member_search', function(res) {
 915      phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick'));
 916  });
 917  
 918  /**
 919   * This callback alternates text - it replaces the current text with the text in
 920   * the alt-text data attribute, and replaces the text in the attribute with the
 921   * current text so that the process can be repeated.
 922   */
 923  phpbb.addAjaxCallback('alt_text', function() {
 924      var $anchor,
 925          updateAll = $(this).data('update-all'),
 926          altText;
 927  
 928      if (updateAll !== undefined && updateAll.length) {
 929          $anchor = $(updateAll);
 930      } else {
 931          $anchor = $(this);
 932      }
 933  
 934      $anchor.each(function() {
 935          var $this = $(this);
 936          altText = $this.attr('data-alt-text');
 937          $this.attr('data-alt-text', $this.text());
 938          $this.attr('title', $.trim(altText));
 939          $this.text(altText);
 940      });
 941  });
 942  
 943  /**
 944   * This callback is based on the alt_text callback.
 945   *
 946   * It replaces the current text with the text in the alt-text data attribute,
 947   * and replaces the text in the attribute with the current text so that the
 948   * process can be repeated.
 949   * Additionally it replaces the class of the link's parent
 950   * and changes the link itself.
 951   */
 952  phpbb.addAjaxCallback('toggle_link', function() {
 953      var $anchor,
 954          updateAll = $(this).data('update-all') ,
 955          toggleText,
 956          toggleUrl,
 957          toggleClass;
 958  
 959      if (updateAll !== undefined && updateAll.length) {
 960          $anchor = $(updateAll);
 961      } else {
 962          $anchor = $(this);
 963      }
 964  
 965      $anchor.each(function() {
 966          var $this = $(this);
 967  
 968          // Toggle link text
 969          toggleText = $this.attr('data-toggle-text');
 970          $this.attr('data-toggle-text', $this.text());
 971          $this.attr('title', $.trim(toggleText));
 972          $this.text(toggleText);
 973  
 974          // Toggle link url
 975          toggleUrl = $this.attr('data-toggle-url');
 976          $this.attr('data-toggle-url', $this.attr('href'));
 977          $this.attr('href', toggleUrl);
 978  
 979          // Toggle class of link parent
 980          toggleClass = $this.attr('data-toggle-class');
 981          $this.attr('data-toggle-class', $this.parent().attr('class'));
 982          $this.parent().attr('class', toggleClass);
 983      });
 984  });
 985  
 986  /**
 987  * Automatically resize textarea
 988  *
 989  * This function automatically resizes textarea elements when user
 990  * types text.
 991  *
 992  * @param {jQuery} $items jQuery object(s) to resize
 993  * @param {object} [options] Optional parameter that adjusts default
 994  *     configuration. See configuration variable
 995  *
 996  * Optional parameters:
 997  *    minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500
 998  *    minHeight {number} Minimum height of textarea. Default = 200
 999  *    maxHeight {number} Maximum height of textarea. Default = 500
1000  *    heightDiff {number} Minimum difference between window and textarea height. Default = 200
1001  *    resizeCallback {function} Function to call after resizing textarea
1002  *    resetCallback {function} Function to call when resize has been canceled
1003  
1004  *        Callback function format: function(item) {}
1005  *            this points to DOM object
1006  *            item is a jQuery object, same as this
1007  */
1008  phpbb.resizeTextArea = function($items, options) {
1009      // Configuration
1010      var configuration = {
1011          minWindowHeight: 500,
1012          minHeight: 200,
1013          maxHeight: 500,
1014          heightDiff: 200,
1015          resizeCallback: function() {},
1016          resetCallback: function() {}
1017      };
1018  
1019      if (phpbb.isTouch) {
1020          return;
1021      }
1022  
1023      if (arguments.length > 1) {
1024          configuration = $.extend(configuration, options);
1025      }
1026  
1027  	function resetAutoResize(item) {
1028          var $item = $(item);
1029          if ($item.hasClass('auto-resized')) {
1030              $(item)
1031                  .css({ height: '', resize: '' })
1032                  .removeClass('auto-resized');
1033              configuration.resetCallback.call(item, $item);
1034          }
1035      }
1036  
1037  	function autoResize(item) {
1038  		function setHeight(height) {
1039              height += parseInt($item.css('height'), 10) - $item.innerHeight();
1040              $item
1041                  .css({ height: height + 'px', resize: 'none' })
1042                  .addClass('auto-resized');
1043              configuration.resizeCallback.call(item, $item);
1044          }
1045  
1046          var windowHeight = $(window).height();
1047  
1048          if (windowHeight < configuration.minWindowHeight) {
1049              resetAutoResize(item);
1050              return;
1051          }
1052  
1053          var maxHeight = Math.min(
1054                  Math.max(windowHeight - configuration.heightDiff, configuration.minHeight),
1055                  configuration.maxHeight
1056              ),
1057              $item = $(item),
1058              height = parseInt($item.innerHeight(), 10),
1059              scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0;
1060  
1061          if (height < 0) {
1062              return;
1063          }
1064  
1065          if (height > maxHeight) {
1066              setHeight(maxHeight);
1067          } else if (scrollHeight > (height + 5)) {
1068              setHeight(Math.min(maxHeight, scrollHeight));
1069          }
1070      }
1071  
1072      $items.on('focus change keyup', function() {
1073          $(this).each(function() {
1074              autoResize(this);
1075          });
1076      }).change();
1077  
1078      $(window).resize(function() {
1079          $items.each(function() {
1080              if ($(this).hasClass('auto-resized')) {
1081                  autoResize(this);
1082              }
1083          });
1084      });
1085  };
1086  
1087  /**
1088  * Check if cursor in textarea is currently inside a bbcode tag
1089  *
1090  * @param {object} textarea Textarea DOM object
1091  * @param {Array} startTags List of start tags to look for
1092  *        For example, Array('[code]', '[code=')
1093  * @param {Array} endTags List of end tags to look for
1094  *        For example, Array('[/code]')
1095  *
1096  * @returns {boolean} True if cursor is in bbcode tag
1097  */
1098  phpbb.inBBCodeTag = function(textarea, startTags, endTags) {
1099      var start = textarea.selectionStart,
1100          lastEnd = -1,
1101          lastStart = -1,
1102          i, index, value;
1103  
1104      if (typeof start !== 'number') {
1105          return false;
1106      }
1107  
1108      value = textarea.value.toLowerCase();
1109  
1110      for (i = 0; i < startTags.length; i++) {
1111          var tagLength = startTags[i].length;
1112          if (start >= tagLength) {
1113              index = value.lastIndexOf(startTags[i], start - tagLength);
1114              lastStart = Math.max(lastStart, index);
1115          }
1116      }
1117      if (lastStart === -1) {
1118          return false;
1119      }
1120  
1121      if (start > 0) {
1122          for (i = 0; i < endTags.length; i++) {
1123              index = value.lastIndexOf(endTags[i], start - 1);
1124              lastEnd = Math.max(lastEnd, index);
1125          }
1126      }
1127  
1128      return (lastEnd < lastStart);
1129  };
1130  
1131  
1132  /**
1133  * Adjust textarea to manage code bbcode
1134  *
1135  * This function allows to use tab characters when typing code
1136  * and keeps indentation of previous line of code when adding new
1137  * line while typing code.
1138  *
1139  * Editor's functionality is changed only when cursor is between
1140  * [code] and [/code] bbcode tags.
1141  *
1142  * @param {object} textarea Textarea DOM object to apply editor to
1143  */
1144  phpbb.applyCodeEditor = function(textarea) {
1145      // list of allowed start and end bbcode code tags, in lower case
1146      var startTags = ['[code]', '[code='],
1147          startTagsEnd = ']',
1148          endTags = ['[/code]'];
1149  
1150      if (!textarea || typeof textarea.selectionStart !== 'number') {
1151          return;
1152      }
1153  
1154      if ($(textarea).data('code-editor') === true) {
1155          return;
1156      }
1157  
1158  	function inTag() {
1159          return phpbb.inBBCodeTag(textarea, startTags, endTags);
1160      }
1161  
1162      /**
1163      * Get line of text before cursor
1164      *
1165      * @param {boolean} stripCodeStart If true, only part of line
1166      *        after [code] tag will be returned.
1167      *
1168      * @returns {string} Line of text
1169      */
1170  	function getLastLine(stripCodeStart) {
1171          var start = textarea.selectionStart,
1172              value = textarea.value,
1173              index = value.lastIndexOf('\n', start - 1);
1174  
1175          value = value.substring(index + 1, start);
1176  
1177          if (stripCodeStart) {
1178              for (var i = 0; i < startTags.length; i++) {
1179                  index = value.lastIndexOf(startTags[i]);
1180                  if (index >= 0) {
1181                      var tagLength = startTags[i].length;
1182  
1183                      value = value.substring(index + tagLength);
1184                      if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) {
1185                          index = value.indexOf(startTagsEnd);
1186  
1187                          if (index >= 0) {
1188                              value = value.substr(index + 1);
1189                          }
1190                      }
1191                  }
1192              }
1193          }
1194  
1195          return value;
1196      }
1197  
1198      /**
1199      * Append text at cursor position
1200      *
1201      * @param {string} text Text to append
1202      */
1203  	function appendText(text) {
1204          var start = textarea.selectionStart,
1205              end = textarea.selectionEnd,
1206              value = textarea.value;
1207  
1208          textarea.value = value.substr(0, start) + text + value.substr(end);
1209          textarea.selectionStart = textarea.selectionEnd = start + text.length;
1210      }
1211  
1212      $(textarea).data('code-editor', true).on('keydown', function(event) {
1213          var key = event.keyCode || event.which;
1214  
1215          // intercept tabs
1216          if (key === keymap.TAB    &&
1217              !event.ctrlKey        &&
1218              !event.shiftKey        &&
1219              !event.altKey        &&
1220              !event.metaKey) {
1221              if (inTag()) {
1222                  appendText('\t');
1223                  event.preventDefault();
1224                  return;
1225              }
1226          }
1227  
1228          // intercept new line characters
1229          if (key === keymap.ENTER) {
1230              if (inTag()) {
1231                  var lastLine = getLastLine(true),
1232                      code = '' + /^\s*/g.exec(lastLine);
1233  
1234                  if (code.length > 0) {
1235                      appendText('\n' + code);
1236                      event.preventDefault();
1237                  }
1238              }
1239          }
1240      });
1241  };
1242  
1243  /**
1244   * Show drag and drop animation when textarea is present
1245   *
1246   * This function will enable the drag and drop animation for a specified
1247   * textarea.
1248   *
1249   * @param {HTMLElement} textarea Textarea DOM object to apply editor to
1250   */
1251  phpbb.showDragNDrop = function(textarea) {
1252      if (!textarea) {
1253          return;
1254      }
1255  
1256      $('body').on('dragenter dragover', function () {
1257          $(textarea).addClass('drag-n-drop');
1258      }).on('dragleave dragout dragend drop', function() {
1259          $(textarea).removeClass('drag-n-drop');
1260      });
1261      $(textarea).on('dragenter dragover', function () {
1262          $(textarea).addClass('drag-n-drop-highlight');
1263      }).on('dragleave dragout dragend drop', function() {
1264          $(textarea).removeClass('drag-n-drop-highlight');
1265      });
1266  };
1267  
1268  /**
1269  * List of classes that toggle dropdown menu,
1270  * list of classes that contain visible dropdown menu
1271  *
1272  * Add your own classes to strings with comma (probably you
1273  * will never need to do that)
1274  */
1275  phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle';
1276  phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible';
1277  
1278  /**
1279  * Dropdown toggle event handler
1280  * This handler is used by phpBB.registerDropdown() and other functions
1281  */
1282  phpbb.toggleDropdown = function() {
1283      var $this = $(this),
1284          options = $this.data('dropdown-options'),
1285          parent = options.parent,
1286          visible = parent.hasClass('dropdown-visible'),
1287          direction;
1288  
1289      if (!visible) {
1290          // Hide other dropdown menus
1291          $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1292  
1293          // Figure out direction of dropdown
1294          direction = options.direction;
1295          var verticalDirection = options.verticalDirection,
1296              offset = $this.offset();
1297  
1298          if (direction === 'auto') {
1299              if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) {
1300                  direction = 'right';
1301              } else {
1302                  direction = 'left';
1303              }
1304          }
1305          parent.toggleClass(options.leftClass, direction === 'left')
1306              .toggleClass(options.rightClass, direction === 'right');
1307  
1308          if (verticalDirection === 'auto') {
1309              var height = $(window).height(),
1310                  top = offset.top - $(window).scrollTop();
1311  
1312              verticalDirection = (top < height * 0.7) ? 'down' : 'up';
1313          }
1314          parent.toggleClass(options.upClass, verticalDirection === 'up')
1315              .toggleClass(options.downClass, verticalDirection === 'down');
1316      }
1317  
1318      options.dropdown.toggle();
1319      parent.toggleClass(options.visibleClass, !visible)
1320          .toggleClass('dropdown-visible', !visible);
1321  
1322      // Check dimensions when showing dropdown
1323      // !visible because variable shows state of dropdown before it was toggled
1324      if (!visible) {
1325          var windowWidth = $(window).width();
1326  
1327          options.dropdown.find('.dropdown-contents').each(function() {
1328              var $this = $(this);
1329  
1330              $this.css({
1331                  marginLeft: 0,
1332                  left: 0,
1333                  maxWidth: (windowWidth - 4) + 'px'
1334              });
1335  
1336              var offset = $this.offset().left,
1337                  width = $this.outerWidth(true);
1338  
1339              if (offset < 2) {
1340                  $this.css('left', (2 - offset) + 'px');
1341              } else if ((offset + width + 2) > windowWidth) {
1342                  $this.css('margin-left', (windowWidth - offset - width - 2) + 'px');
1343              }
1344  
1345              // Check whether the vertical scrollbar is present.
1346              $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight());
1347  
1348          });
1349          var freeSpace = parent.offset().left - 4;
1350  
1351          if (direction === 'left') {
1352              options.dropdown.css('margin-left', '-' + freeSpace + 'px');
1353  
1354              // Try to position the notification dropdown correctly in RTL-responsive mode
1355              if (options.dropdown.hasClass('dropdown-extended')) {
1356                  var contentWidth,
1357                      fullFreeSpace = freeSpace + parent.outerWidth();
1358  
1359                  options.dropdown.find('.dropdown-contents').each(function() {
1360                      contentWidth = parseInt($(this).outerWidth(), 10);
1361                      $(this).css({ marginLeft: 0, left: 0 });
1362                  });
1363  
1364                  var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px';
1365                  options.dropdown.css({
1366                      width: maxOffset,
1367                      marginLeft: -maxOffset
1368                  });
1369              }
1370          } else {
1371              options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px');
1372          }
1373      }
1374  
1375      // Prevent event propagation
1376      if (arguments.length > 0) {
1377          try {
1378              var e = arguments[0];
1379              e.preventDefault();
1380              e.stopPropagation();
1381          } catch (error) { }
1382      }
1383      return false;
1384  };
1385  
1386  /**
1387  * Toggle dropdown submenu
1388  */
1389  phpbb.toggleSubmenu = function(e) {
1390      $(this).siblings('.dropdown-submenu').toggle();
1391      e.preventDefault();
1392  };
1393  
1394  /**
1395  * Register dropdown menu
1396  * Shows/hides dropdown, decides which side to open to
1397  *
1398  * @param {jQuery} toggle Link that toggles dropdown.
1399  * @param {jQuery} dropdown Dropdown menu.
1400  * @param {Object} options List of options. Optional.
1401  */
1402  phpbb.registerDropdown = function(toggle, dropdown, options) {
1403      var ops = {
1404              parent: toggle.parent(), // Parent item to add classes to
1405              direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right
1406              verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down
1407              visibleClass: 'visible', // Class to add to parent item when dropdown is visible
1408              leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side
1409              rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side
1410              upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item
1411              downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item
1412          };
1413      if (options) {
1414          ops = $.extend(ops, options);
1415      }
1416      ops.dropdown = dropdown;
1417  
1418      ops.parent.addClass('dropdown-container');
1419      toggle.addClass('dropdown-toggle');
1420  
1421      toggle.data('dropdown-options', ops);
1422  
1423      toggle.click(phpbb.toggleDropdown);
1424      $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu);
1425  };
1426  
1427  /**
1428  * Get the HTML for a color palette table.
1429  *
1430  * @param {string} dir Palette direction - either v or h
1431  * @param {int} width Palette cell width.
1432  * @param {int} height Palette cell height.
1433  */
1434  phpbb.colorPalette = function(dir, width, height) {
1435      var r, g, b,
1436          numberList = new Array(6),
1437          color = '',
1438          html = '';
1439  
1440      numberList[0] = '00';
1441      numberList[1] = '40';
1442      numberList[2] = '80';
1443      numberList[3] = 'BF';
1444      numberList[4] = 'FF';
1445  
1446      var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette';
1447      html += '<table class="not-responsive colour-palette ' + tableClass + '" style="width: auto;">';
1448  
1449      for (r = 0; r < 5; r++) {
1450          if (dir === 'h') {
1451              html += '<tr>';
1452          }
1453  
1454          for (g = 0; g < 5; g++) {
1455              if (dir === 'v') {
1456                  html += '<tr>';
1457              }
1458  
1459              for (b = 0; b < 5; b++) {
1460                  color = '' + numberList[r] + numberList[g] + numberList[b];
1461                  html += '<td style="background-color: #' + color + '; width: ' + width + 'px; height: ' +
1462                      height + 'px;"><a href="#" data-color="' + color + '" style="display: block; width: ' +
1463                      width + 'px; height: ' + height + 'px; " alt="#' + color + '" title="#' + color + '"></a>';
1464                  html += '</td>';
1465              }
1466  
1467              if (dir === 'v') {
1468                  html += '</tr>';
1469              }
1470          }
1471  
1472          if (dir === 'h') {
1473              html += '</tr>';
1474          }
1475      }
1476      html += '</table>';
1477      return html;
1478  };
1479  
1480  /**
1481  * Register a color palette.
1482  *
1483  * @param {jQuery} el jQuery object for the palette container.
1484  */
1485  phpbb.registerPalette = function(el) {
1486      var    orientation    = el.attr('data-orientation'),
1487          height        = el.attr('data-height'),
1488          width        = el.attr('data-width'),
1489          target        = el.attr('data-target'),
1490          bbcode        = el.attr('data-bbcode');
1491  
1492      // Insert the palette HTML into the container.
1493      el.html(phpbb.colorPalette(orientation, width, height));
1494  
1495      // Add toggle control.
1496      $('#color_palette_toggle').click(function(e) {
1497          el.toggle();
1498          e.preventDefault();
1499      });
1500  
1501      // Attach event handler when a palette cell is clicked.
1502      $(el).on('click', 'a', function(e) {
1503          var color = $(this).attr('data-color');
1504  
1505          if (bbcode) {
1506              bbfontstyle('[color=#' + color + ']', '[/color]');
1507          } else {
1508              $(target).val(color);
1509          }
1510          e.preventDefault();
1511      });
1512  };
1513  
1514  /**
1515  * Set display of page element
1516  *
1517  * @param {string} id The ID of the element to change
1518  * @param {int} action Set to 0 if element display should be toggled, -1 for
1519  *            hiding the element, and 1 for showing it.
1520  * @param {string} type Display type that should be used, e.g. inline, block or
1521  *            other CSS "display" types
1522  */
1523  phpbb.toggleDisplay = function(id, action, type) {
1524      if (!type) {
1525          type = 'block';
1526      }
1527  
1528      var $element = $('#' + id);
1529  
1530      var display = $element.css('display');
1531      if (!action) {
1532          action = (display === '' || display === type) ? -1 : 1;
1533      }
1534      $element.css('display', ((action === 1) ? type : 'none'));
1535  };
1536  
1537  /**
1538  * Toggle additional settings based on the selected
1539  * option of select element.
1540  *
1541  * @param {jQuery} el jQuery select element object.
1542  */
1543  phpbb.toggleSelectSettings = function(el) {
1544      el.children().each(function() {
1545          var $this = $(this),
1546              $setting = $($this.data('toggle-setting'));
1547          $setting.toggle($this.is(':selected'));
1548  
1549          // Disable any input elements that are not visible right now
1550          if ($this.is(':selected')) {
1551              $($this.data('toggle-setting') + ' input').prop('disabled', false);
1552          } else {
1553              $($this.data('toggle-setting') + ' input').prop('disabled', true);
1554          }
1555      });
1556  };
1557  
1558  /**
1559  * Get function from name.
1560  * Based on http://stackoverflow.com/a/359910
1561  *
1562  * @param {string} functionName Function to get.
1563  * @returns function
1564  */
1565  phpbb.getFunctionByName = function (functionName) {
1566      var namespaces = functionName.split('.'),
1567          func = namespaces.pop(),
1568          context = window;
1569  
1570      for (var i = 0; i < namespaces.length; i++) {
1571          context = context[namespaces[i]];
1572      }
1573      return context[func];
1574  };
1575  
1576  /**
1577  * Register page dropdowns.
1578  */
1579  phpbb.registerPageDropdowns = function() {
1580      var $body = $('body');
1581  
1582      $body.find('.dropdown-container').each(function() {
1583          var $this = $(this),
1584              $trigger = $this.find('.dropdown-trigger:first'),
1585              $contents = $this.find('.dropdown'),
1586              options = {
1587                  direction: 'auto',
1588                  verticalDirection: 'auto'
1589              },
1590              data;
1591  
1592          if (!$trigger.length) {
1593              data = $this.attr('data-dropdown-trigger');
1594              $trigger = data ? $this.children(data) : $this.children('a:first');
1595          }
1596  
1597          if (!$contents.length) {
1598              data = $this.attr('data-dropdown-contents');
1599              $contents = data ? $this.children(data) : $this.children('div:first');
1600          }
1601  
1602          if (!$trigger.length || !$contents.length) {
1603              return;
1604          }
1605  
1606          if ($this.hasClass('dropdown-up')) {
1607              options.verticalDirection = 'up';
1608          }
1609          if ($this.hasClass('dropdown-down')) {
1610              options.verticalDirection = 'down';
1611          }
1612          if ($this.hasClass('dropdown-left')) {
1613              options.direction = 'left';
1614          }
1615          if ($this.hasClass('dropdown-right')) {
1616              options.direction = 'right';
1617          }
1618  
1619          phpbb.registerDropdown($trigger, $contents, options);
1620      });
1621  
1622      // Hide active dropdowns when click event happens outside
1623      $body.click(function(e) {
1624          var $parents = $(e.target).parents();
1625          if (!$parents.is(phpbb.dropdownVisibleContainers)) {
1626              $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1627          }
1628      });
1629  };
1630  
1631  /**
1632   * Handle avatars to be lazy loaded.
1633   */
1634  phpbb.lazyLoadAvatars = function loadAvatars() {
1635      $('.avatar[data-src]').each(function () {
1636          var $avatar = $(this);
1637  
1638          $avatar
1639              .attr('src', $avatar.data('src'))
1640              .removeAttr('data-src');
1641      });
1642  };
1643  
1644  $(window).load(phpbb.lazyLoadAvatars);
1645  
1646  /**
1647  * Apply code editor to all textarea elements with data-bbcode attribute
1648  */
1649  $(function() {
1650      $('textarea[data-bbcode]').each(function() {
1651          phpbb.applyCodeEditor(this);
1652      });
1653  
1654      phpbb.registerPageDropdowns();
1655  
1656      $('#color_palette_placeholder').each(function() {
1657          phpbb.registerPalette($(this));
1658      });
1659  
1660      // Update browser history URL to point to specific post in viewtopic.php
1661      // when using view=unread#unread link.
1662      phpbb.history.replaceUrl($('#unread[data-url]').data('url'));
1663  
1664      // Hide settings that are not selected via select element.
1665      $('select[data-togglable-settings]').each(function() {
1666          var $this = $(this);
1667  
1668          $this.change(function() {
1669              phpbb.toggleSelectSettings($this);
1670          });
1671          phpbb.toggleSelectSettings($this);
1672      });
1673  });
1674  
1675  })(jQuery); // Avoid conflicts with other libraries


Generated: Thu Jan 11 00:25:41 2018 Cross-referenced by PHPXref 0.7.1