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