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