[ Index ]

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


Generated: Sat Oct 26 11:28:42 2019 Cross-referenced by PHPXref 0.7.1