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