[ Index ]

PHP Cross Reference of phpBB-3.2.11-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 === 0) {
 857          hours = '00';
 858          sign = '+';
 859      } else if (hours < 10) {
 860          hours = '0' + hours.toString();
 861      } else {
 862          hours = hours.toString();
 863      }
 864  
 865      if (minutes < 10) {
 866          minutes = '0' + minutes.toString();
 867      } else {
 868          minutes = minutes.toString();
 869      }
 870  
 871      var prefix = 'UTC' + sign + hours + ':' + minutes;
 872      var prefixLength = prefix.length;
 873      var selectorOptions = $('option', '#tz_date');
 874      var i;
 875  
 876      var $tzSelectDateSuggest = $('#tz_select_date_suggest');
 877  
 878      for (i = 0; i < selectorOptions.length; ++i) {
 879          var option = selectorOptions[i];
 880  
 881          if (option.value.substring(0, prefixLength) === prefix) {
 882              if ($('#tz_date').val() !== option.value && !forceSelector) {
 883                  // We do not select the option for the user, but notify him,
 884                  // that we would suggest a different setting.
 885                  phpbb.timezoneSwitchDate(true);
 886                  $tzSelectDateSuggest.css('display', 'inline');
 887              } else {
 888                  option.selected = true;
 889                  phpbb.timezoneSwitchDate(!forceSelector);
 890                  $tzSelectDateSuggest.css('display', 'none');
 891              }
 892  
 893              var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion');
 894  
 895              $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML));
 896              $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9)));
 897              $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML);
 898  
 899              // Found the suggestion, there cannot be more, so return from here.
 900              return;
 901          }
 902      }
 903  };
 904  
 905  phpbb.ajaxCallbacks = {};
 906  
 907  /**
 908   * Adds an AJAX callback to be used by phpbb.ajaxify.
 909   *
 910   * See the phpbb.ajaxify comments for information on stuff like parameters.
 911   *
 912   * @param {string} id The name of the callback.
 913   * @param {function} callback The callback to be called.
 914   */
 915  phpbb.addAjaxCallback = function(id, callback) {
 916      if (typeof callback === 'function') {
 917          phpbb.ajaxCallbacks[id] = callback;
 918      }
 919      return this;
 920  };
 921  
 922  /**
 923   * This callback handles live member searches.
 924   */
 925  phpbb.addAjaxCallback('member_search', function(res) {
 926      phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick'));
 927  });
 928  
 929  /**
 930   * This callback alternates text - it replaces the current text with the text in
 931   * the alt-text data attribute, and replaces the text in the attribute with the
 932   * current text so that the process can be repeated.
 933   */
 934  phpbb.addAjaxCallback('alt_text', function() {
 935      var $anchor,
 936          updateAll = $(this).data('update-all'),
 937          altText;
 938  
 939      if (updateAll !== undefined && updateAll.length) {
 940          $anchor = $(updateAll);
 941      } else {
 942          $anchor = $(this);
 943      }
 944  
 945      $anchor.each(function() {
 946          var $this = $(this);
 947          altText = $this.attr('data-alt-text');
 948          $this.attr('data-alt-text', $.trim($this.text()));
 949          $this.attr('title', altText);
 950          $this.children('span').text(altText);
 951      });
 952  });
 953  
 954  /**
 955   * This callback is based on the alt_text callback.
 956   *
 957   * It replaces the current text with the text in the alt-text data attribute,
 958   * and replaces the text in the attribute with the current text so that the
 959   * process can be repeated.
 960   * Additionally it replaces the class of the link's parent
 961   * and changes the link itself.
 962   */
 963  phpbb.addAjaxCallback('toggle_link', function() {
 964      var $anchor,
 965          updateAll = $(this).data('update-all') ,
 966          toggleText,
 967          toggleUrl,
 968          toggleClass;
 969  
 970      if (updateAll !== undefined && updateAll.length) {
 971          $anchor = $(updateAll);
 972      } else {
 973          $anchor = $(this);
 974      }
 975  
 976      $anchor.each(function() {
 977          var $this = $(this);
 978  
 979          // Toggle link url
 980          toggleUrl = $this.attr('data-toggle-url');
 981          $this.attr('data-toggle-url', $this.attr('href'));
 982          $this.attr('href', toggleUrl);
 983  
 984          // Toggle class of link parent
 985          toggleClass = $this.attr('data-toggle-class');
 986          $this.attr('data-toggle-class', $this.children().attr('class'));
 987          $this.children('.icon').attr('class', toggleClass);
 988  
 989          // Toggle link text
 990          toggleText = $this.attr('data-toggle-text');
 991          $this.attr('data-toggle-text', $this.children('span').text());
 992          $this.attr('title', $.trim(toggleText));
 993          $this.children('span').text(toggleText);
 994      });
 995  });
 996  
 997  /**
 998  * Automatically resize textarea
 999  *
1000  * This function automatically resizes textarea elements when user
1001  * types text.
1002  *
1003  * @param {jQuery} $items jQuery object(s) to resize
1004  * @param {object} [options] Optional parameter that adjusts default
1005  *     configuration. See configuration variable
1006  *
1007  * Optional parameters:
1008  *    minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500
1009  *    minHeight {number} Minimum height of textarea. Default = 200
1010  *    maxHeight {number} Maximum height of textarea. Default = 500
1011  *    heightDiff {number} Minimum difference between window and textarea height. Default = 200
1012  *    resizeCallback {function} Function to call after resizing textarea
1013  *    resetCallback {function} Function to call when resize has been canceled
1014  
1015  *        Callback function format: function(item) {}
1016  *            this points to DOM object
1017  *            item is a jQuery object, same as this
1018  */
1019  phpbb.resizeTextArea = function($items, options) {
1020      // Configuration
1021      var configuration = {
1022          minWindowHeight: 500,
1023          minHeight: 200,
1024          maxHeight: 500,
1025          heightDiff: 200,
1026          resizeCallback: function() {},
1027          resetCallback: function() {}
1028      };
1029  
1030      if (phpbb.isTouch) {
1031          return;
1032      }
1033  
1034      if (arguments.length > 1) {
1035          configuration = $.extend(configuration, options);
1036      }
1037  
1038  	function resetAutoResize(item) {
1039          var $item = $(item);
1040          if ($item.hasClass('auto-resized')) {
1041              $(item)
1042                  .css({ height: '', resize: '' })
1043                  .removeClass('auto-resized');
1044              configuration.resetCallback.call(item, $item);
1045          }
1046      }
1047  
1048  	function autoResize(item) {
1049  		function setHeight(height) {
1050              height += parseInt($item.css('height'), 10) - $item.innerHeight();
1051              $item
1052                  .css({ height: height + 'px', resize: 'none' })
1053                  .addClass('auto-resized');
1054              configuration.resizeCallback.call(item, $item);
1055          }
1056  
1057          var windowHeight = $(window).height();
1058  
1059          if (windowHeight < configuration.minWindowHeight) {
1060              resetAutoResize(item);
1061              return;
1062          }
1063  
1064          var maxHeight = Math.min(
1065                  Math.max(windowHeight - configuration.heightDiff, configuration.minHeight),
1066                  configuration.maxHeight
1067              ),
1068              $item = $(item),
1069              height = parseInt($item.innerHeight(), 10),
1070              scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0;
1071  
1072          if (height < 0) {
1073              return;
1074          }
1075  
1076          if (height > maxHeight) {
1077              setHeight(maxHeight);
1078          } else if (scrollHeight > (height + 5)) {
1079              setHeight(Math.min(maxHeight, scrollHeight));
1080          }
1081      }
1082  
1083      $items.on('focus change keyup', function() {
1084          $(this).each(function() {
1085              autoResize(this);
1086          });
1087      }).change();
1088  
1089      $(window).resize(function() {
1090          $items.each(function() {
1091              if ($(this).hasClass('auto-resized')) {
1092                  autoResize(this);
1093              }
1094          });
1095      });
1096  };
1097  
1098  /**
1099  * Check if cursor in textarea is currently inside a bbcode tag
1100  *
1101  * @param {object} textarea Textarea DOM object
1102  * @param {Array} startTags List of start tags to look for
1103  *        For example, Array('[code]', '[code=')
1104  * @param {Array} endTags List of end tags to look for
1105  *        For example, Array('[/code]')
1106  *
1107  * @returns {boolean} True if cursor is in bbcode tag
1108  */
1109  phpbb.inBBCodeTag = function(textarea, startTags, endTags) {
1110      var start = textarea.selectionStart,
1111          lastEnd = -1,
1112          lastStart = -1,
1113          i, index, value;
1114  
1115      if (typeof start !== 'number') {
1116          return false;
1117      }
1118  
1119      value = textarea.value.toLowerCase();
1120  
1121      for (i = 0; i < startTags.length; i++) {
1122          var tagLength = startTags[i].length;
1123          if (start >= tagLength) {
1124              index = value.lastIndexOf(startTags[i], start - tagLength);
1125              lastStart = Math.max(lastStart, index);
1126          }
1127      }
1128      if (lastStart === -1) {
1129          return false;
1130      }
1131  
1132      if (start > 0) {
1133          for (i = 0; i < endTags.length; i++) {
1134              index = value.lastIndexOf(endTags[i], start - 1);
1135              lastEnd = Math.max(lastEnd, index);
1136          }
1137      }
1138  
1139      return (lastEnd < lastStart);
1140  };
1141  
1142  
1143  /**
1144  * Adjust textarea to manage code bbcode
1145  *
1146  * This function allows to use tab characters when typing code
1147  * and keeps indentation of previous line of code when adding new
1148  * line while typing code.
1149  *
1150  * Editor's functionality is changed only when cursor is between
1151  * [code] and [/code] bbcode tags.
1152  *
1153  * @param {object} textarea Textarea DOM object to apply editor to
1154  */
1155  phpbb.applyCodeEditor = function(textarea) {
1156      // list of allowed start and end bbcode code tags, in lower case
1157      var startTags = ['[code]', '[code='],
1158          startTagsEnd = ']',
1159          endTags = ['[/code]'];
1160  
1161      if (!textarea || typeof textarea.selectionStart !== 'number') {
1162          return;
1163      }
1164  
1165      if ($(textarea).data('code-editor') === true) {
1166          return;
1167      }
1168  
1169  	function inTag() {
1170          return phpbb.inBBCodeTag(textarea, startTags, endTags);
1171      }
1172  
1173      /**
1174      * Get line of text before cursor
1175      *
1176      * @param {boolean} stripCodeStart If true, only part of line
1177      *        after [code] tag will be returned.
1178      *
1179      * @returns {string} Line of text
1180      */
1181  	function getLastLine(stripCodeStart) {
1182          var start = textarea.selectionStart,
1183              value = textarea.value,
1184              index = value.lastIndexOf('\n', start - 1);
1185  
1186          value = value.substring(index + 1, start);
1187  
1188          if (stripCodeStart) {
1189              for (var i = 0; i < startTags.length; i++) {
1190                  index = value.lastIndexOf(startTags[i]);
1191                  if (index >= 0) {
1192                      var tagLength = startTags[i].length;
1193  
1194                      value = value.substring(index + tagLength);
1195                      if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) {
1196                          index = value.indexOf(startTagsEnd);
1197  
1198                          if (index >= 0) {
1199                              value = value.substr(index + 1);
1200                          }
1201                      }
1202                  }
1203              }
1204          }
1205  
1206          return value;
1207      }
1208  
1209      /**
1210      * Append text at cursor position
1211      *
1212      * @param {string} text Text to append
1213      */
1214  	function appendText(text) {
1215          var start = textarea.selectionStart,
1216              end = textarea.selectionEnd,
1217              value = textarea.value;
1218  
1219          textarea.value = value.substr(0, start) + text + value.substr(end);
1220          textarea.selectionStart = textarea.selectionEnd = start + text.length;
1221      }
1222  
1223      $(textarea).data('code-editor', true).on('keydown', function(event) {
1224          var key = event.keyCode || event.which;
1225  
1226          // intercept tabs
1227          if (key === keymap.TAB    &&
1228              !event.ctrlKey        &&
1229              !event.shiftKey        &&
1230              !event.altKey        &&
1231              !event.metaKey) {
1232              if (inTag()) {
1233                  appendText('\t');
1234                  event.preventDefault();
1235                  return;
1236              }
1237          }
1238  
1239          // intercept new line characters
1240          if (key === keymap.ENTER) {
1241              if (inTag()) {
1242                  var lastLine = getLastLine(true),
1243                      code = '' + /^\s*/g.exec(lastLine);
1244  
1245                  if (code.length > 0) {
1246                      appendText('\n' + code);
1247                      event.preventDefault();
1248                  }
1249              }
1250          }
1251      });
1252  };
1253  
1254  /**
1255   * Show drag and drop animation when textarea is present
1256   *
1257   * This function will enable the drag and drop animation for a specified
1258   * textarea.
1259   *
1260   * @param {HTMLElement} textarea Textarea DOM object to apply editor to
1261   */
1262  phpbb.showDragNDrop = function(textarea) {
1263      if (!textarea) {
1264          return;
1265      }
1266  
1267      $('body').on('dragenter dragover', function () {
1268          $(textarea).addClass('drag-n-drop');
1269      }).on('dragleave dragout dragend drop', function() {
1270          $(textarea).removeClass('drag-n-drop');
1271      });
1272      $(textarea).on('dragenter dragover', function () {
1273          $(textarea).addClass('drag-n-drop-highlight');
1274      }).on('dragleave dragout dragend drop', function() {
1275          $(textarea).removeClass('drag-n-drop-highlight');
1276      });
1277  };
1278  
1279  /**
1280  * List of classes that toggle dropdown menu,
1281  * list of classes that contain visible dropdown menu
1282  *
1283  * Add your own classes to strings with comma (probably you
1284  * will never need to do that)
1285  */
1286  phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle';
1287  phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible';
1288  
1289  /**
1290  * Dropdown toggle event handler
1291  * This handler is used by phpBB.registerDropdown() and other functions
1292  */
1293  phpbb.toggleDropdown = function() {
1294      var $this = $(this),
1295          options = $this.data('dropdown-options'),
1296          parent = options.parent,
1297          visible = parent.hasClass('dropdown-visible'),
1298          direction;
1299  
1300      if (!visible) {
1301          // Hide other dropdown menus
1302          $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1303  
1304          // Figure out direction of dropdown
1305          direction = options.direction;
1306          var verticalDirection = options.verticalDirection,
1307              offset = $this.offset();
1308  
1309          if (direction === 'auto') {
1310              if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) {
1311                  direction = 'right';
1312              } else {
1313                  direction = 'left';
1314              }
1315          }
1316          parent.toggleClass(options.leftClass, direction === 'left')
1317              .toggleClass(options.rightClass, direction === 'right');
1318  
1319          if (verticalDirection === 'auto') {
1320              var height = $(window).height(),
1321                  top = offset.top - $(window).scrollTop();
1322  
1323              verticalDirection = (top < height * 0.7) ? 'down' : 'up';
1324          }
1325          parent.toggleClass(options.upClass, verticalDirection === 'up')
1326              .toggleClass(options.downClass, verticalDirection === 'down');
1327      }
1328  
1329      options.dropdown.toggle();
1330      parent.toggleClass(options.visibleClass, !visible)
1331          .toggleClass('dropdown-visible', !visible);
1332  
1333      // Check dimensions when showing dropdown
1334      // !visible because variable shows state of dropdown before it was toggled
1335      if (!visible) {
1336          var windowWidth = $(window).width();
1337  
1338          options.dropdown.find('.dropdown-contents').each(function() {
1339              var $this = $(this);
1340  
1341              $this.css({
1342                  marginLeft: 0,
1343                  left: 0,
1344                  marginRight: 0,
1345                  maxWidth: (windowWidth - 4) + 'px'
1346              });
1347  
1348              var offset = $this.offset().left,
1349                  width = $this.outerWidth(true);
1350  
1351              if (offset < 2) {
1352                  $this.css('left', (2 - offset) + 'px');
1353              } else if ((offset + width + 2) > windowWidth) {
1354                  $this.css('margin-left', (windowWidth - offset - width - 2) + 'px');
1355              }
1356  
1357              // Check whether the vertical scrollbar is present.
1358              $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight());
1359  
1360          });
1361          var freeSpace = parent.offset().left - 4;
1362  
1363          if (direction === 'left') {
1364              options.dropdown.css('margin-left', '-' + freeSpace + 'px');
1365  
1366              // Try to position the notification dropdown correctly in RTL-responsive mode
1367              if (options.dropdown.hasClass('dropdown-extended')) {
1368                  var contentWidth,
1369                      fullFreeSpace = freeSpace + parent.outerWidth();
1370  
1371                  options.dropdown.find('.dropdown-contents').each(function() {
1372                      contentWidth = parseInt($(this).outerWidth(), 10);
1373                      $(this).css({ marginLeft: 0, left: 0 });
1374                  });
1375  
1376                  var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px';
1377                  options.dropdown.css({
1378                      width: maxOffset,
1379                      marginLeft: -maxOffset
1380                  });
1381              }
1382          } else {
1383              options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px');
1384          }
1385      }
1386  
1387      // Prevent event propagation
1388      if (arguments.length > 0) {
1389          try {
1390              var e = arguments[0];
1391              e.preventDefault();
1392              e.stopPropagation();
1393          } catch (error) { }
1394      }
1395      return false;
1396  };
1397  
1398  /**
1399  * Toggle dropdown submenu
1400  */
1401  phpbb.toggleSubmenu = function(e) {
1402      $(this).siblings('.dropdown-submenu').toggle();
1403      e.preventDefault();
1404  };
1405  
1406  /**
1407  * Register dropdown menu
1408  * Shows/hides dropdown, decides which side to open to
1409  *
1410  * @param {jQuery} toggle Link that toggles dropdown.
1411  * @param {jQuery} dropdown Dropdown menu.
1412  * @param {Object} options List of options. Optional.
1413  */
1414  phpbb.registerDropdown = function(toggle, dropdown, options) {
1415      var ops = {
1416              parent: toggle.parent(), // Parent item to add classes to
1417              direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right
1418              verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down
1419              visibleClass: 'visible', // Class to add to parent item when dropdown is visible
1420              leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side
1421              rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side
1422              upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item
1423              downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item
1424          };
1425      if (options) {
1426          ops = $.extend(ops, options);
1427      }
1428      ops.dropdown = dropdown;
1429  
1430      ops.parent.addClass('dropdown-container');
1431      toggle.addClass('dropdown-toggle');
1432  
1433      toggle.data('dropdown-options', ops);
1434  
1435      toggle.click(phpbb.toggleDropdown);
1436      $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu);
1437  };
1438  
1439  /**
1440  * Get the HTML for a color palette table.
1441  *
1442  * @param {string} dir Palette direction - either v or h
1443  * @param {int} width Palette cell width.
1444  * @param {int} height Palette cell height.
1445  */
1446  phpbb.colorPalette = function(dir, width, height) {
1447      var r, g, b,
1448          numberList = new Array(6),
1449          color = '',
1450          html = '';
1451  
1452      numberList[0] = '00';
1453      numberList[1] = '40';
1454      numberList[2] = '80';
1455      numberList[3] = 'BF';
1456      numberList[4] = 'FF';
1457  
1458      var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette';
1459      html += '<table class="not-responsive colour-palette ' + tableClass + '" style="width: auto;">';
1460  
1461      for (r = 0; r < 5; r++) {
1462          if (dir === 'h') {
1463              html += '<tr>';
1464          }
1465  
1466          for (g = 0; g < 5; g++) {
1467              if (dir === 'v') {
1468                  html += '<tr>';
1469              }
1470  
1471              for (b = 0; b < 5; b++) {
1472                  color = '' + numberList[r] + numberList[g] + numberList[b];
1473                  html += '<td style="background-color: #' + color + '; width: ' + width + 'px; height: ' +
1474                      height + 'px;"><a href="#" data-color="' + color + '" style="display: block; width: ' +
1475                      width + 'px; height: ' + height + 'px; " alt="#' + color + '" title="#' + color + '"></a>';
1476                  html += '</td>';
1477              }
1478  
1479              if (dir === 'v') {
1480                  html += '</tr>';
1481              }
1482          }
1483  
1484          if (dir === 'h') {
1485              html += '</tr>';
1486          }
1487      }
1488      html += '</table>';
1489      return html;
1490  };
1491  
1492  /**
1493  * Register a color palette.
1494  *
1495  * @param {jQuery} el jQuery object for the palette container.
1496  */
1497  phpbb.registerPalette = function(el) {
1498      var    orientation    = el.attr('data-orientation'),
1499          height        = el.attr('data-height'),
1500          width        = el.attr('data-width'),
1501          target        = el.attr('data-target'),
1502          bbcode        = el.attr('data-bbcode');
1503  
1504      // Insert the palette HTML into the container.
1505      el.html(phpbb.colorPalette(orientation, width, height));
1506  
1507      // Add toggle control.
1508      $('#color_palette_toggle').click(function(e) {
1509          el.toggle();
1510          e.preventDefault();
1511      });
1512  
1513      // Attach event handler when a palette cell is clicked.
1514      $(el).on('click', 'a', function(e) {
1515          var color = $(this).attr('data-color');
1516  
1517          if (bbcode) {
1518              bbfontstyle('[color=#' + color + ']', '[/color]');
1519          } else {
1520              $(target).val(color);
1521          }
1522          e.preventDefault();
1523      });
1524  };
1525  
1526  /**
1527  * Set display of page element
1528  *
1529  * @param {string} id The ID of the element to change
1530  * @param {int} action Set to 0 if element display should be toggled, -1 for
1531  *            hiding the element, and 1 for showing it.
1532  * @param {string} type Display type that should be used, e.g. inline, block or
1533  *            other CSS "display" types
1534  */
1535  phpbb.toggleDisplay = function(id, action, type) {
1536      if (!type) {
1537          type = 'block';
1538      }
1539  
1540      var $element = $('#' + id);
1541  
1542      var display = $element.css('display');
1543      if (!action) {
1544          action = (display === '' || display === type) ? -1 : 1;
1545      }
1546      $element.css('display', ((action === 1) ? type : 'none'));
1547  };
1548  
1549  /**
1550  * Toggle additional settings based on the selected
1551  * option of select element.
1552  *
1553  * @param {jQuery} el jQuery select element object.
1554  */
1555  phpbb.toggleSelectSettings = function(el) {
1556      el.children().each(function() {
1557          var $this = $(this),
1558              $setting = $($this.data('toggle-setting'));
1559          $setting.toggle($this.is(':selected'));
1560  
1561          // Disable any input elements that are not visible right now
1562          if ($this.is(':selected')) {
1563              $($this.data('toggle-setting') + ' input').prop('disabled', false);
1564          } else {
1565              $($this.data('toggle-setting') + ' input').prop('disabled', true);
1566          }
1567      });
1568  };
1569  
1570  /**
1571  * Get function from name.
1572  * Based on http://stackoverflow.com/a/359910
1573  *
1574  * @param {string} functionName Function to get.
1575  * @returns function
1576  */
1577  phpbb.getFunctionByName = function (functionName) {
1578      var namespaces = functionName.split('.'),
1579          func = namespaces.pop(),
1580          context = window;
1581  
1582      for (var i = 0; i < namespaces.length; i++) {
1583          context = context[namespaces[i]];
1584      }
1585      return context[func];
1586  };
1587  
1588  /**
1589  * Register page dropdowns.
1590  */
1591  phpbb.registerPageDropdowns = function() {
1592      var $body = $('body');
1593  
1594      $body.find('.dropdown-container').each(function() {
1595          var $this = $(this),
1596              $trigger = $this.find('.dropdown-trigger:first'),
1597              $contents = $this.find('.dropdown'),
1598              options = {
1599                  direction: 'auto',
1600                  verticalDirection: 'auto'
1601              },
1602              data;
1603  
1604          if (!$trigger.length) {
1605              data = $this.attr('data-dropdown-trigger');
1606              $trigger = data ? $this.children(data) : $this.children('a:first');
1607          }
1608  
1609          if (!$contents.length) {
1610              data = $this.attr('data-dropdown-contents');
1611              $contents = data ? $this.children(data) : $this.children('div:first');
1612          }
1613  
1614          if (!$trigger.length || !$contents.length) {
1615              return;
1616          }
1617  
1618          if ($this.hasClass('dropdown-up')) {
1619              options.verticalDirection = 'up';
1620          }
1621          if ($this.hasClass('dropdown-down')) {
1622              options.verticalDirection = 'down';
1623          }
1624          if ($this.hasClass('dropdown-left')) {
1625              options.direction = 'left';
1626          }
1627          if ($this.hasClass('dropdown-right')) {
1628              options.direction = 'right';
1629          }
1630  
1631          phpbb.registerDropdown($trigger, $contents, options);
1632      });
1633  
1634      // Hide active dropdowns when click event happens outside
1635      $body.click(function(e) {
1636          var $parents = $(e.target).parents();
1637          if (!$parents.is(phpbb.dropdownVisibleContainers)) {
1638              $(phpbb.dropdownHandles).each(phpbb.toggleDropdown);
1639          }
1640      });
1641  };
1642  
1643  /**
1644   * Handle avatars to be lazy loaded.
1645   */
1646  phpbb.lazyLoadAvatars = function loadAvatars() {
1647      $('.avatar[data-src]').each(function () {
1648          var $avatar = $(this);
1649  
1650          $avatar
1651              .attr('src', $avatar.data('src'))
1652              .removeAttr('data-src');
1653      });
1654  };
1655  
1656  $(window).on('load', phpbb.lazyLoadAvatars);
1657  
1658  /**
1659  * Apply code editor to all textarea elements with data-bbcode attribute
1660  */
1661  $(function() {
1662      $('textarea[data-bbcode]').each(function() {
1663          phpbb.applyCodeEditor(this);
1664      });
1665  
1666      phpbb.registerPageDropdowns();
1667  
1668      $('[data-orientation]').each(function() {
1669          phpbb.registerPalette($(this));
1670      });
1671  
1672      // Update browser history URL to point to specific post in viewtopic.php
1673      // when using view=unread#unread link.
1674      phpbb.history.replaceUrl($('#unread[data-url]').data('url'));
1675  
1676      // Hide settings that are not selected via select element.
1677      $('select[data-togglable-settings]').each(function() {
1678          var $this = $(this);
1679  
1680          $this.change(function() {
1681              phpbb.toggleSelectSettings($this);
1682          });
1683          phpbb.toggleSelectSettings($this);
1684      });
1685  });
1686  
1687  })(jQuery); // Avoid conflicts with other libraries


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1