1/** 2 * Debounce and throttle function's decorator plugin 1.0.5 3 * 4 * Copyright (c) 2009 Filatov Dmitry ([email protected]) 5 * Dual licensed under the MIT and GPL licenses: 6 * http://www.opensource.org/licenses/mit-license.php 7 * http://www.gnu.org/licenses/gpl.html 8 * 9 */ 10 11(function($) { 12 13$.extend({ 14 15 debounce : function(fn, timeout, invokeAsap, ctx) { 16 17 if(arguments.length == 3 && typeof invokeAsap != 'boolean') { 18 ctx = invokeAsap; 19 invokeAsap = false; 20 } 21 22 var timer; 23 24 return function() { 25 26 var args = arguments; 27 ctx = ctx || this; 28 29 invokeAsap && !timer && fn.apply(ctx, args); 30 31 clearTimeout(timer); 32 33 timer = setTimeout(function() { 34 !invokeAsap && fn.apply(ctx, args); 35 timer = null; 36 }, timeout); 37 38 }; 39 40 }, 41 42 throttle : function(fn, timeout, ctx) { 43 44 var timer, args, needInvoke; 45 46 return function() { 47 48 args = arguments; 49 needInvoke = true; 50 ctx = ctx || this; 51 52 if(!timer) { 53 (function() { 54 if(needInvoke) { 55 fn.apply(ctx, args); 56 needInvoke = false; 57 timer = setTimeout(arguments.callee, timeout); 58 } 59 else { 60 timer = null; 61 } 62 })(); 63 } 64 65 }; 66 67 } 68 69}); 70 71})(jQuery);; 72 73/* vim: set expandtab sw=4 ts=4 sts=4: */ 74/** 75 * Handles the resizing of a menu according to the available screen width 76 * 77 * Uses themes/original/css/resizable-menu.css.php 78 * 79 * To initialise: 80 * $('#myMenu').menuResizer(function () { 81 * // This function will be called to find out how much 82 * // available horizontal space there is for the menu 83 * return $('body').width() - 5; // Some extra margin for good measure 84 * }); 85 * 86 * To trigger a resize operation: 87 * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize() 88 * 89 * To restore the menu to a state like before it was initialized: 90 * $('#myMenu').menuResizer('destroy'); 91 * 92 * @package PhpMyAdmin 93 */ 94(function ($) { 95 function MenuResizer($container, widthCalculator) { 96 var self = this; 97 self.$container = $container; 98 self.widthCalculator = widthCalculator; 99 // create submenu container 100 var link = $('<a />', {href: '#', 'class': 'tab nowrap'}) 101 .text(PMA_messages['strMore']) 102 .bind('click', false); // same as event.preventDefault() 103 var img = $container.find('li img'); 104 if (img.length) { 105 $(PMA_getImage('b_more.png').toString()).prependTo(link); 106 } 107 var $submenu = $('<li />', {'class': 'submenu'}) 108 .append(link) 109 .append($('<ul />')) 110 .mouseenter(function() { 111 if ($(this).find('ul .tabactive').length == 0) { 112 $(this) 113 .addClass('submenuhover') 114 .find('> a') 115 .addClass('tabactive'); 116 } 117 }) 118 .mouseleave(function() { 119 if ($(this).find('ul .tabactive').length == 0) { 120 $(this) 121 .removeClass('submenuhover') 122 .find('> a') 123 .removeClass('tabactive'); 124 } 125 }); 126 $container.append($submenu); 127 setTimeout(function () { 128 self.resize(); 129 }, 4); 130 } 131 MenuResizer.prototype.resize = function () { 132 var wmax = this.widthCalculator.call(this.$container); 133 var $submenu = this.$container.find('.submenu:last'); 134 var submenu_w = $submenu.outerWidth(true); 135 var $submenu_ul = $submenu.find('ul'); 136 var $li = this.$container.find('> li'); 137 var $li2 = $submenu_ul.find('li'); 138 var more_shown = $li2.length > 0; 139 // Calculate the total width used by all the shown tabs 140 var total_len = more_shown ? submenu_w : 0; 141 var l = $li.length - 1; 142 for (var i = 0; i < l; i++) { 143 total_len += $($li[i]).outerWidth(true); 144 } 145 // Now hide menu elements that don't fit into the menubar 146 var hidden = false; // Whether we have hidden any tabs 147 while (total_len >= wmax && --l >= 0) { // Process the tabs backwards 148 hidden = true; 149 var el = $($li[l]); 150 var el_width = el.outerWidth(true); 151 el.data('width', el_width); 152 if (! more_shown) { 153 total_len -= el_width; 154 el.prependTo($submenu_ul); 155 total_len += submenu_w; 156 more_shown = true; 157 } else { 158 total_len -= el_width; 159 el.prependTo($submenu_ul); 160 } 161 } 162 // If we didn't hide any tabs, then there might be some space to show some 163 if (! hidden) { 164 // Show menu elements that do fit into the menubar 165 for (var i = 0, l = $li2.length; i < l; i++) { 166 total_len += $($li2[i]).data('width'); 167 // item fits or (it is the last item 168 // and it would fit if More got removed) 169 if (total_len < wmax 170 || (i == $li2.length - 1 && total_len - submenu_w < wmax) 171 ) { 172 $($li2[i]).insertBefore($submenu); 173 } else { 174 break; 175 } 176 } 177 } 178 // Show/hide the "More" tab as needed 179 if ($submenu_ul.find('li').length > 0) { 180 $submenu.addClass('shown'); 181 } else { 182 $submenu.removeClass('shown'); 183 } 184 if (this.$container.find('> li').length == 1) { 185 // If there is only the "More" tab left, then we need 186 // to align the submenu to the left edge of the tab 187 $submenu_ul.removeClass().addClass('only'); 188 } else { 189 // Otherwise we align the submenu to the right edge of the tab 190 $submenu_ul.removeClass().addClass('notonly'); 191 } 192 if ($submenu.find('.tabactive').length) { 193 $submenu 194 .addClass('active') 195 .find('> a') 196 .removeClass('tab') 197 .addClass('tabactive'); 198 } else { 199 $submenu 200 .removeClass('active') 201 .find('> a') 202 .addClass('tab') 203 .removeClass('tabactive'); 204 } 205 }; 206 MenuResizer.prototype.destroy = function () { 207 var $submenu = this.$container.find('li.submenu').removeData(); 208 $submenu.find('li').appendTo(this.$container); 209 $submenu.remove(); 210 }; 211 212 /** Public API */ 213 var methods = { 214 init: function(widthCalculator) { 215 return this.each(function () { 216 var $this = $(this); 217 if (! $this.data('menuResizer')) { 218 $this.data( 219 'menuResizer', 220 new MenuResizer($this, widthCalculator) 221 ); 222 } 223 }); 224 }, 225 resize: function () { 226 return this.each(function () { 227 var self = $(this).data('menuResizer'); 228 if (self) { 229 self.resize(); 230 } 231 }); 232 }, 233 destroy: function () { 234 return this.each(function () { 235 var self = $(this).data('menuResizer'); 236 if (self) { 237 self.destroy(); 238 } 239 }); 240 } 241 }; 242 243 /** Extend jQuery */ 244 $.fn.menuResizer = function(method) { 245 if (methods[method]) { 246 return methods[method].call(this); 247 } else if (typeof method === 'function') { 248 return methods.init.apply(this, [method]); 249 } else { 250 $.error('Method ' + method + ' does not exist on jQuery.menuResizer'); 251 } 252 }; 253})(jQuery); 254; 255 256/* vim: set expandtab sw=4 ts=4 sts=4: */ 257/** 258 * Conditionally included if framing is not allowed 259 */ 260if(self == top) { 261 document.documentElement.style.display = 'block' ; 262} else { 263 top.location = self.location ; 264} 265; 266 267/* vim: set expandtab sw=4 ts=4 sts=4: */ 268/** 269 * JavaScript functionality for Routines, Triggers and Events. 270 * 271 * @package PhpMyadmin 272 */ 273/** 274 * @var RTE Contains all the JavaScript functionality 275 * for Routines, Triggers and Events 276 */ 277var RTE = { 278 /** 279 * Construct for the object that provides the 280 * functionality for Routines, Triggers and Events 281 */ 282 object: function (type) { 283 $.extend(this, RTE.COMMON); 284 switch (type) { 285 case 'routine': 286 $.extend(this, RTE.ROUTINE); 287 break; 288 case 'trigger': 289 // nothing extra yet for triggers 290 break; 291 case 'event': 292 $.extend(this, RTE.EVENT); 293 break; 294 default: 295 break; 296 } 297 }, 298 /** 299 * @var string param_template Template for a row in the routine editor
300 */ 301 param_template: '' 302}; 303 304/** 305 * @var RTE.COMMON a JavaScript namespace containing the functionality 306 * for Routines, Triggers and Events 307 * 308 * This namespace is extended by the functionality required 309 * to handle a specific item (a routine, trigger or event) 310 * in the relevant javascript files in this folder 311 */ 312RTE.COMMON = { 313 /** 314 * @var $ajaxDialog Query object containing the reference to the 315 * dialog that contains the editor 316 */ 317 $ajaxDialog: null, 318 /** 319 * @var syntaxHiglighter Reference to the codemirror editor 320 */ 321 syntaxHiglighter: null, 322 /** 323 * @var buttonOptions Object containing options for 324 * the jQueryUI dialog buttons 325 */ 326 buttonOptions: {}, 327 /** 328 * Validate editor form fields. 329 */ 330 validate: function () { 331 /** 332 * @var $elm a jQuery object containing the reference 333 * to an element that is being validated 334 */ 335 var $elm = null; 336 // Common validation. At the very least the name 337 // and the definition must be provided for an item 338 $elm = $('table.rte_table').last().find('input[name=item_name]'); 339 if ($elm.val() === '') { 340 $elm.focus(); 341 alert(PMA_messages['strFormEmpty']); 342 return false; 343 } 344 $elm = $('table.rte_table').find('textarea[name=item_definition]'); 345 if ($elm.val() === '') { 346 if (this.syntaxHiglighter !== null) { 347 this.syntaxHiglighter.focus(); 348 } 349 else { 350 $('textarea[name=item_definition]').last().focus(); 351 } 352 alert(PMA_messages['strFormEmpty']); 353 return false; 354 } 355 // The validation has so far passed, so now 356 // we can validate item-specific fields. 357 return this.validateCustom(); 358 }, // end validate() 359 /** 360 * Validate custom editor form fields. 361 * This function can be overridden by 362 * other files in this folder 363 */ 364 validateCustom: function () { 365 return true; 366 }, // end validateCustom() 367 /** 368 * Execute some code after the ajax 369 * dialog for the ditor is shown. 370 * This function can be overridden by 371 * other files in this folder 372 */ 373 postDialogShow: function () { 374 // Nothing by default 375 }, // end postDialogShow() 376 377 exportDialog: function ($this) { 378 var $msg = PMA_ajaxShowMessage(); 379 // Fire the ajax request straight away 380 $.get($this.attr('href'), {'ajax_request': true}, function (data) { 381 if (data.success === true) { 382 PMA_ajaxRemoveMessage($msg); 383 /** 384 * @var button_options Object containing options 385 * for jQueryUI dialog buttons 386 */ 387 var button_options = {}; 388 button_options[PMA_messages['strClose']] = function () { 389 $(this).dialog("close").remove(); 390 }; 391 /** 392 * Display the dialog to the user 393 */ 394 var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({ 395 width: 500, 396 buttons: button_options, 397 title: data.title 398 }); 399 // Attach syntax highlited editor to export dialog 400 /** 401 * @var $elm jQuery object containing the reference 402 * to the Export textarea. 403 */ 404 var $elm = $ajaxDialog.find('textarea'); 405 /** 406 * @var opts Options to pass to the codemirror editor 407 */ 408 var opts = { 409 lineNumbers: true, 410 matchBrackets: true, 411 indentUnit: 4, 412 mode: "text/x-mysql" 413 }; 414 CodeMirror.fromTextArea($elm[0], opts); 415 } else { 416 PMA_ajaxShowMessage(data.error, false); 417 } 418 }); // end $.get() 419 }, // end exportDialog() 420 editorDialog: function (is_new, $this) { 421 var that = this; 422 /**
423 * @var $edit_row jQuery object containing the reference to 424 * the row of the the item being edited 425 * from the list of items 426 */ 427 var $edit_row = null; 428 if ($this.hasClass('edit_anchor')) { 429 // Remeber the row of the item being edited for later, 430 // so that if the edit is successful, we can replace the 431 // row with info about the modified item. 432 $edit_row = $this.parents('tr'); 433 } 434 /** 435 * @var $msg jQuery object containing the reference to 436 * the AJAX message shown to the user 437 */ 438 var $msg = PMA_ajaxShowMessage(); 439 $.get($this.attr('href'), {'ajax_request': true}, function (data) { 440 if (data.success === true) { 441 // We have successfully fetched the editor form 442 PMA_ajaxRemoveMessage($msg); 443 // Now define the function that is called when 444 // the user presses the "Go" button 445 that.buttonOptions[PMA_messages['strGo']] = function () { 446 // Move the data from the codemirror editor back to the 447 // textarea, where it can be used in the form submission. 448 if (typeof CodeMirror != 'undefined') { 449 that.syntaxHiglighter.save(); 450 } 451 // Validate editor and submit request, if passed. 452 if (that.validate()) { 453 /** 454 * @var data Form data to be sent in the AJAX request 455 */ 456 var data = $('form.rte_form').last().serialize(); 457 $msg = PMA_ajaxShowMessage( 458 PMA_messages['strProcessingRequest'] 459 ); 460 var url = $('form.rte_form').last().attr('action'); 461 $.post(url, data, function (data) { 462 if (data.success === true) { 463 // Item created successfully 464 PMA_ajaxRemoveMessage($msg); 465 PMA_slidingMessage(data.message); 466 that.$ajaxDialog.dialog('close'); 467 // If we are in 'edit' mode, we must 468 // remove the reference to the old row. 469 if (mode === 'edit') { 470 $edit_row.remove(); 471 } 472 // Sometimes, like when moving a trigger from 473 // a table to another one, the new row should 474 // not be inserted into the list. In this case 475 // "data.insert" will be set to false. 476 if (data.insert) { 477 // Insert the new row at the correct 478 // location in the list of items 479 /** 480 * @var text Contains the name of an item from 481 * the list that is used in comparisons 482 * to find the correct location where 483 * to insert a new row. 484 */ 485 var text = ''; 486 /** 487 * @var inserted Whether a new item has been 488 * inserted in the list or not 489 */ 490 var inserted = false; 491 $('table.data').find('tr').each(function () { 492 text = $(this) 493 .children('td') 494 .eq(0) 495 .find('strong') 496 .text() 497 .toUpperCase(); 498 text = $.trim(text); 499 if (text !== '' && text > data.name) { 500 $(this).before(data.new_row); 501 inserted = true; 502 return false;
503 } 504 }); 505 if (! inserted) { 506 // If we didn't manage to insert the row yet, 507 // it must belong at the end of the list, 508 // so we insert it there. 509 $('table.data').append(data.new_row); 510 } 511 // Fade-in the new row 512 $('tr.ajaxInsert') 513 .show('slow') 514 .removeClass('ajaxInsert'); 515 } else if ($('table.data').find('tr').has('td').length === 0) { 516 // If we are not supposed to insert the new row, 517 // we will now check if the table is empty and 518 // needs to be hidden. This will be the case if 519 // we were editing the only item in the list, 520 // which we removed and will not be inserting 521 // something else in its place. 522 $('table.data').hide("slow", function () { 523 $('#nothing2display').show("slow"); 524 }); 525 } 526 // Now we have inserted the row at the correct 527 // position, but surely at least some row classes 528 // are wrong now. So we will itirate throught 529 // all rows and assign correct classes to them 530 /** 531 * @var ct Count of processed rows 532 */ 533 var ct = 0; 534 /** 535 * @var rowclass Class to be attached to the row 536 * that is being processed 537 */ 538 var rowclass = ''; 539 $('table.data').find('tr').has('td').each(function () { 540 rowclass = (ct % 2 === 0) ? 'odd' : 'even'; 541 $(this).removeClass().addClass(rowclass); 542 ct++; 543 }); 544 // If this is the first item being added, remove 545 // the "No items" message and show the list. 546 if ($('table.data').find('tr').has('td').length > 0 547 && $('#nothing2display').is(':visible')) { 548 $('#nothing2display').hide("slow", function () { 549 $('table.data').show("slow"); 550 }); 551 } 552 PMA_reloadNavigation(); 553 } else { 554 PMA_ajaxShowMessage(data.error, false); 555 } 556 }); // end $.post() 557 } // end "if (that.validate())" 558 }; // end of function that handles the submission of the Editor 559 that.buttonOptions[PMA_messages['strClose']] = function () { 560 $(this).dialog("close"); 561 }; 562 /** 563 * Display the dialog to the user 564 */ 565 that.$ajaxDialog = $('<div>' + data.message + '</div>').dialog({ 566 width: 700, 567 minWidth: 500, 568 buttons: that.buttonOptions, 569 title: data.title, 570 modal: true, 571 close: function () { 572 $(this).remove(); 573 } 574 }); 575 that.$ajaxDialog.find('input[name=item_name]').focus(); 576 that.$ajaxDialog.find('input.datefield, input.datetimefield').each(function () { 577 PMA_addDatepicker($(this).css('width', '95%')); 578 }); 579 /** 580 * @var mode Used to remeber whether the editor is in 581 * "Edit" or "Add" mode 582 */ 583 var mode = 'add'; 584 if ($('input[name=editor_process_edit]').length > 0) { 585 mode = 'edit'; 586 } 587 // Attach syntax highlited editor to the definition 588 /**
589 * @var elm jQuery object containing the reference to 590 * the Definition textarea. 591 */ 592 var $elm = $('textarea[name=item_definition]').last(); 593 /** 594 * @var opts Options to pass to the codemirror editor 595 */ 596 var opts = { 597 lineNumbers: true, 598 matchBrackets: true, 599 indentUnit: 4, 600 mode: "text/x-mysql" 601 }; 602 if (typeof CodeMirror != 'undefined') { 603 that.syntaxHiglighter = CodeMirror.fromTextArea($elm[0], opts); 604 } 605 // Execute item-specific code 606 that.postDialogShow(data); 607 } else { 608 PMA_ajaxShowMessage(data.error, false); 609 } 610 }); // end $.get() 611 }, 612 613 dropDialog: function ($this) { 614 /** 615 * @var $curr_row Object containing reference to the current row 616 */ 617 var $curr_row = $this.parents('tr'); 618 /** 619 * @var question String containing the question to be asked for c
619onfirmation 620 */ 621 var question = $('<div/>').text( 622 $curr_row.children('td').children('.drop_sql').html() 623 ); 624 // We ask for confirmation first here, before submitting the ajax request 625 $this.PMA_confirm(question, $this.attr('href'), function (url) { 626 /** 627 * @var msg jQuery object containing the reference to 628 * the AJAX message shown to the user 629 */ 630 var $msg = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 631 $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) { 632 if (data.success === true) { 633 /** 634 * @var $table Object containing reference 635 * to the main list of elements 636 */ 637 var $table = $curr_row.parent(); 638 // Check how many rows will be left after we remove 639 // the one that the user has requested us to remove 640 if ($table.find('tr').length === 3) { 641 // If there are two rows left, it means that they are 642 // the header of the table and the rows that we are 643 // about to remove, so after the removal there will be 644 // nothing to show in the table, so we hide it. 645 $table.hide("slow", function () { 646 $(this).find('tr.even, tr.odd').remove(); 647 $('#nothing2display').show("slow"); 648 }); 649 } else { 650 $curr_row.hide("slow", function () { 651 $(this).remove(); 652 // Now we have removed the row from the list, but maybe 653 // some row classes are wrong now. So we will itirate 654 // throught all rows and assign correct classes to them. 655 /** 656 * @var ct Count of processed rows 657 */ 658 var ct = 0; 659 /** 660 * @var rowclass Class to be attached to the row 661 * that is being processed 662 */ 663 var rowclass = ''; 664 $table.find('tr').has('td').each(function () { 665 rowclass = (ct % 2 === 0) ? 'odd' : 'even'; 666 $(this).removeClass().addClass(rowclass); 667 ct++; 668 }); 669 }); 670 } 671 // Get rid of the "Loading" message 672 PMA_ajaxRemoveMessage($msg); 673 // Show the query that we just executed 674 PMA_slidingMessage(data.sql_query); 675 PMA_reloadNavigation(); 676 } else { 677 PMA_ajaxShowMessage(data.error, false); 678 } 679 }); // end $.get() 680 }); // end $.PMA_confirm() 681 } 682}; // end RTE namespace 683 684/** 685 * @var RTE.EVENT JavaScript functionality for events 686 */ 687RTE.EVENT = { 688 validateCustom: function () { 689 /** 690 * @var elm a jQuery object containing the reference 691 * to an element that is being validated 692 */ 693 var $elm = null; 694 if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') { 695 // The interval field must not be empty for recurring events 696 $elm = this.$ajaxDialog.find('input[name=item_interval_value]'); 697 if ($elm.val() === '') { 698 $elm.focus(); 699 alert(PMA_messages['strFormEmpty']); 700 return false; 701 } 702 } else { 703 // The execute_at field must not be empty for "once off" events 704 $elm = this.$ajaxDialog.find('input[name=item_execute_at]'); 705 if ($elm.val() === '') { 706 $elm.focus(); 707 alert(PMA_messages['strFormEmpty']); 708 return false;
709 } 710 } 711 return true; 712 } 713}; 714 715/** 716 * @var RTE.ROUTINE JavaScript functionality for routines 717 */ 718RTE.ROUTINE = { 719 /** 720 * Overriding the postDialogShow() function defined in common.js 721 * 722 * @param data JSON-encoded data from the ajax request 723 */ 724 postDialogShow: function (data) { 725 // Cache the template for a parameter table row 726 RTE.param_template = data.param_template; 727 var that = this; 728 // Make adjustments in the dialog to make it AJAX compatible 729 $('td.routine_param_remove').show(); 730 $('input[name=routine_removeparameter]').remove(); 731 $('input[name=routine_addparameter]').css('width', '100%'); 732 // Enable/disable the 'options' dropdowns for parameters as necessary 733 $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1'); 734 $('table.routine_params_table').last().find('tr').has('td').each(function () { 735 that.setOptionsForParameter( 736 $(this).find('select[name^=item_param_type]'), 737 $(this).find('input[name^=item_param_length]'), 738 $(this).find('select[name^=item_param_opts_text]'), 739 $(this).find('select[name^=item_param_opts_num]') 740 ); 741 }); 742 // Enable/disable the 'options' dropdowns for 743 // function return value as necessary 744 this.setOptionsForParameter( 745 $('table.rte_table').last().find('select[name=item_returntype]'), 746 $('table.rte_table').last().find('input[name=item_returnlength]'), 747 $('table.rte_table').last().find('select[name=item_returnopts_text]'), 748 $('table.rte_table').last().find('select[name=item_returnopts_num]') 749 ); 750 }, 751 /** 752 * Overriding the validateCustom() function defined in common.js 753 */ 754 validateCustom: function () { 755 /** 756 * @var isSuccess Stores the outcome of the validation 757 */ 758 var isSuccess = true; 759 /** 760 * @var inputname The value of the "name" attribute for 761 * the field that is being processed 762 */ 763 var inputname = ''; 764 this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { 765 // Every parameter of a routine must have 766 // a non-empty direction, name and type 767 if (isSuccess) { 768 $(this).find(':input').each(function () { 769 inputname = $(this).attr('name'); 770 if (inputname.substr(0, 14) === 'item_param_dir' || 771 inputname.substr(0, 15) === 'item_param_name' || 772 inputname.substr(0, 15) === 'item_param_type') { 773 if ($(this).val() === '') { 774 $(this).focus(); 775 isSuccess = false; 776 return false; 777 } 778 } 779 }); 780 } else { 781 return false; 782 } 783 }); 784 if (! isSuccess) { 785 alert(PMA_messages['strFormEmpty']); 786 return false; 787 } 788 this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { 789 // SET, ENUM, VARCHAR and VARBINARY fields must have length/values 790 var $inputtyp = $(this).find('select[name^=item_param_type]'); 791 var $inputlen = $(this).find('input[name^=item_param_length]'); 792 if ($inputtyp.length && $inputlen.length) { 793 if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR') 794 && $inputlen.val() === '') { 795 $inputlen.focus(); 796 isSuccess = false; 797 return false; 798 } 799 } 800 }); 801 if (! isSuccess) { 802 alert(PMA_messages['strFormEmpty']); 803 return false; 804 } 805 if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') { 806 // The length/values of return variable for functions must 807 // be set, if the type is SET, ENUM, VARCHAR or VARBINARY. 808 var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]'); 809 var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]'); 810 if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR') 811 && $returnlen.val() === '') { 812 $returnlen.focus(); 813 alert(PMA_messages['strFormEmpty']); 814 return false;
815 } 816 } 817 if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') { 818 // A function must contain a RETURN statement in its definition 819 if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) { 820 this.syntaxHiglighter.focus(); 821 alert(PMA_messages['MissingReturn']); 822 return false; 823 } 824 } 825 return true; 826 }, 827 /** 828 * Enable/disable the "options" dropdown and "length" input for 829 * parameters and the return variable in the routine editor 830 * as necessary. 831 * 832 * @param type a jQuery object containing the reference 833 * to the "Type" dropdown box 834 * @param len a jQuery object containing the reference 835 * to the "Length" input box 836 * @param text a jQuery object containing the reference 837 * to the dropdown box with options for 838 * parameters of text type 839 * @param num a jQuery object containing the reference 840 * to the dropdown box with options for 841 * parameters of numeric type 842 */ 843 setOptionsForParameter: function ($type, $len, $text, $num) { 844 /** 845 * @var no_opts a jQuery object containing the reference 846 * to an element to be displayed when no 847 * options are available 848 */ 849 var $no_opts = $text.parent().parent().find('.no_opts'); 850 /** 851 * @var no_len a jQuery object containing the reference 852 * to an element to be displayed when no 853 * "length/values" field is available 854 */ 855 var $no_len = $len.parent().parent().find('.no_len'); 856 857 // Process for parameter options 858 switch ($type.val()) { 859 case 'TINYINT': 860 case 'SMALLINT': 861 case 'MEDIUMINT': 862 case 'INT': 863 case 'BIGINT': 864 case 'DECIMAL': 865 case 'FLOAT': 866 case 'DOUBLE': 867 case 'REAL': 868 $text.parent().hide(); 869 $num.parent().show(); 870 $no_opts.hide(); 871 break; 872 case 'TINYTEXT': 873 case 'TEXT': 874 case 'MEDIUMTEXT': 875 case 'LONGTEXT': 876 case 'CHAR': 877 case 'VARCHAR': 878 case 'SET': 879 case 'ENUM': 880 $text.parent().show(); 881 $num.parent().hide(); 882 $no_opts.hide(); 883 break; 884 default: 885 $text.parent().hide(); 886 $num.parent().hide(); 887 $no_opts.show(); 888 break; 889 } 890 // Process for parameter length 891 switch ($type.val()) { 892 case 'DATE': 893 case 'DATETIME': 894 case 'TIME': 895 case 'TINYBLOB': 896 case 'TINYTEXT': 897 case 'BLOB': 898 case 'TEXT': 899 case 'MEDIUMBLOB': 900 case 'MEDIUMTEXT': 901 case 'LONGBLOB': 902 case 'LONGTEXT': 903 $text.closest('tr').find('a:first').hide(); 904 $len.parent().hide(); 905 $no_len.show(); 906 break; 907 default: 908 if ($type.val() == 'ENUM' || $type.val() == 'SET') { 909 $text.closest('tr').find('a:first').show(); 910 } else { 911 $text.closest('tr').find('a:first').hide(); 912 } 913 $len.parent().show(); 914 $no_len.hide(); 915 break; 916 } 917 }, 918 executeDialog: function ($this) { 919 var that = this; 920 /** 921 * @var msg jQuery object containing the reference to 922 * the AJAX message shown to the user 923 */ 924 var $msg = PMA_ajaxShowMessage(); 925 $.get($this.attr('href'), {'ajax_request': true}, function (data) { 926 if (data.success === true) { 927 PMA_ajaxRemoveMessage($msg); 928 // If 'data.dialog' is true we show a dialog with a form 929 // to get the input parameters for routine, otherwise 930 // we just show the results of the query 931 if (data.dialog) { 932 // Define the function that is called when 933 // the user presses the "Go" button 934 that.buttonOptions[PMA_messages['strGo']] = function () { 935 /** 936 * @var data Form data to be sent in the AJAX request 937 */ 938 var data = $('form.rte_form').last().serialize();
939 $msg = PMA_ajaxShowMessage( 940 PMA_messages['strProcessingRequest'] 941 ); 942 $.post('db_routines.php', data, function (data) { 943 if (data.success === true) { 944 // Routine executed successfully 945 PMA_ajaxRemoveMessage($msg); 946 PMA_slidingMessage(data.message); 947 $ajaxDialog.dialog('close'); 948 } else { 949 PMA_ajaxShowMessage(data.error, false); 950 } 951 }); 952 }; 953 that.buttonOptions[PMA_messages['strClose']] = function () { 954 $(this).dialog("close"); 955 }; 956 /** 957 * Display the dialog to the user 958 */ 959 $ajaxDialog = $('<div>' + data.message + '</div>').dialog({ 960 width: 650, 961 buttons: that.buttonOptions, 962 title: data.title, 963 modal: true, 964 close: function () { 965 $(this).remove(); 966 } 967 }); 968 $ajaxDialog.find('input[name^=params]').first().focus(); 969 /** 970 * Attach the datepickers to the relevant form fields 971 */ 972 $ajaxDialog.find('input.datefield, input.datetimefield').each(function () { 973 PMA_addDatepicker($(this).css('width', '95%')); 974 }); 975 } else { 976 // Routine executed successfully 977 PMA_slidingMessage(data.message); 978 } 979 } else { 980 PMA_ajaxShowMessage(data.error, false); 981 } 982 }); // end $.get() 983 } 984}; 985 986/** 987 * Attach Ajax event handlers for the Routines, Triggers and Events editor 988 * 989 * FIXME: submit on ENTER keypress in dialog 990 */ 991$(function () { 992 /** 993 * Attach Ajax event handlers for the Add/Edit functionality. 994 */ 995 $('a.ajax.add_anchor, a.ajax.edit_anchor').live('click', function (event) { 996 event.preventDefault(); 997 var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?')); 998 if (type.indexOf('routine') != -1) { 999 type = 'routine'; 1000 } else if (type.indexOf('trigger') != -1) { 1001 type = 'trigger'; 1002 } else if (type.indexOf('event') != -1) { 1003 type = 'event'; 1004 } else { 1005 type = ''; 1006 } 1007 var dialog = new RTE.object(type); 1008 dialog.editorDialog($(this).hasClass('add_anchor'), $(this)); 1009 }); // end $.live() 1010 1011 /** 1012 * Attach Ajax event handlers for the Execute routine functionality 1013 */ 1014 $('a.ajax.exec_anchor').live('click', function (event) { 1015 event.preventDefault(); 1016 var dialog = new RTE.object('routine'); 1017 dialog.executeDialog($(this)); 1018 }); // end $.live() 1019 1020 /** 1021 * Attach Ajax event handlers for Export of Routines, Triggers and Events 1022 */ 1023 $('a.ajax.export_anchor').live('click', function (event) { 1024 event.preventDefault(); 1025 var dialog = new RTE.object(); 1026 dialog.exportDialog($(this)); 1027 }); // end $.live() 1028 1029 /** 1030 * Attach Ajax event handlers for Drop functionality 1031 * of Routines, Triggers and Events. 1032 */ 1033 $('a.ajax.drop_anchor').live('click', function (event) { 1034 event.preventDefault(); 1035 var dialog = new RTE.object(); 1036 dialog.dropDialog($(this)); 1037 }); // end $.live() 1038 1039 /** 1040 * Attach Ajax event handlers for the "Change event/routine type" 1041 * functionality in the events editor, so that the correct 1042 * rows are shown in the editor when changing the event type 1043 */ 1044 $('select[name=item_type]').live('change', function () { 1045 $(this) 1046 .closest('table') 1047 .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, td.routine_direction_cell')
1048 .toggle(); 1049 }); // end $.live() 1050 1051 /** 1052 * Attach Ajax event handlers for the "Change parameter type" 1053 * functionality in the routines editor, so that the correct 1054 * option/length fields, if any, are shown when changing 1055 * a parameter type 1056 */ 1057 $('select[name^=item_param_type]').live('change', function () { 1058 /** 1059 * @var row jQuery object containing the reference to 1060 * a row in the routine parameters table 1061 */ 1062 var $row = $(this).parents('tr').first(); 1063 var rte = new RTE.object('routine'); 1064 rte.setOptionsForParameter( 1065 $row.find('select[name^=item_param_type]'), 1066 $row.find('input[name^=item_param_length]'), 1067 $row.find('select[name^=item_param_opts_text]'), 1068 $row.find('select[name^=item_param_opts_num]') 1069 ); 1070 }); // end $.live() 1071 1072 /** 1073 * Attach Ajax event handlers for the "Change the type of return 1074 * variable of function" functionality, so that the correct fields, 1075 * if any, are shown when changing the function return type type 1076 */ 1077 $('select[name=item_returntype]').live('change', function () { 1078 var rte = new RTE.object('routine'); 1079 var $table = $(this).closest('table.rte_table'); 1080 rte.setOptionsForParameter( 1081 $table.find('select[name=item_returntype]'), 1082 $table.find('input[name=item_returnlength]'), 1083 $table.find('select[name=item_returnopts_text]'), 1084 $table.find('select[name=item_returnopts_num]') 1085 ); 1086 }); // end $.live() 1087 1088 /** 1089 * Attach Ajax event handlers for the "Add parameter to routine" functionality 1090 */ 1091 $('input[name=routine_addparameter]').live('click', function (event) { 1092 event.preventDefault(); 1093 /** 1094 * @var routine_params_table jQuery object containing the reference 1095 * to the routine parameters table 1096 */ 1097 var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table'); 1098 /** 1099 * @var new_param_row A string containing the HTML code for the 1100 * new row for the routine paramaters table 1101 */ 1102 var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1); 1103 // Append the new row to the parameters table 1104 $routine_params_table.append(new_param_row); 1105 // Make sure that the row is correctly shown according to the type of routine 1106 if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') { 1107 $('tr.routine_return_row').show(); 1108 $('td.routine_direction_cell').hide(); 1109 } 1110 /** 1111 * @var newrow jQuery object containing the reference to the newly 1112 * inserted row in the routine parameters table 1113 */ 1114 var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last(); 1115 // Enable/disable the 'options' dropdowns for parameters as necessary 1116 var rte = new RTE.object('routine'); 1117 rte.setOptionsForParameter( 1118 $newrow.find('select[name^=item_param_type]'), 1119 $newrow.find('input[name^=item_param_length]'), 1120 $newrow.find('select[name^=item_param_opts_text]'), 1121 $newrow.find('select[name^=item_param_opts_num]') 1122 ); 1123 }); // end $.live() 1124 1125 /** 1126 * Attach Ajax event handlers for the 1127 * "Remove parameter from routine" functionality 1128 */ 1129 $('a.routine_param_remove_anchor').live('click', function (event) { 1130 event.preventDefault(); 1131 $(this).parent().parent().remove(); 1132 // After removing a parameter, the indices of the name attributes in 1133 // the input fields lose the correct order and need to be reordered. 1134 /** 1135 * @var index Counter used for reindexing the input 1136 * fields in the routine parameters table 1137 */ 1138 var index = 0;
1139 $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').each(function () { 1140 $(this).find(':input').each(function () { 1141 /** 1142 * @var inputname The value of the name attribute of 1143 * the input field being reindexed 1144 */ 1145 var inputname = $(this).attr('name'); 1146 if (inputname.substr(0, 14) === 'item_param_dir') { 1147 $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']'); 1148 } else if (inputname.substr(0, 15) === 'item_param_name') { 1149 $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); 1150 } else if (inputname.substr(0, 15) === 'item_param_type') { 1151 $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); 1152 } else if (inputname.substr(0, 17) === 'item_param_length') { 1153 $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']'); 1154 $(this).attr('id', 'item_param_length_' + index); 1155 } else if (inputname.substr(0, 20) === 'item_param_opts_text') { 1156 $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']'); 1157 } else if (inputname.substr(0, 19) === 'item_param_opts_num') { 1158 $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']'); 1159 } 1160 }); 1161 index++; 1162 }); 1163 }); // end $.live() 1164}); // end of $() 1165; 1166 1167/* vim: set expandtab sw=4 ts=4 sts=4: */ 1168/** 1169 * general function, usually for data manipulation pages 1170 * 1171 */ 1172 1173/** 1174 * @var $table_clone reference to the action links on the tbl_structure page 1175 */ 1176var $table_clone = false; 1177 1178/** 1179 * @var sql_box_locked lock for the sqlbox textarea in the querybox/querywindow 1180 */ 1181var sql_box_locked = false; 1182 1183/** 1184 * @var array holds elements which content should only selected once 1185 */ 1186var only_once_elements = []; 1187 1188/** 1189 * @var int ajax_message_count Number of AJAX messages shown since page load 1190 */ 1191var ajax_message_count = 0; 1192 1193/** 1194 * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab 1195 */ 1196var codemirror_editor = false; 1197 1198/** 1199 * @var codemirror_editor object containing CodeMirror editor of the inline query editor 1200 */ 1201var codemirror_inline_editor = false; 1202 1203/** 1204 * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests 1205 */ 1206var chart_activeTimeouts = {}; 1207 1208/** 1209 * Make sure that ajax requests will not be cached 1210 * by appending a random variable to their parameters 1211 */ 1212$.ajaxPrefilter(function (options, originalOptions, jqXHR) { 1213 var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000); 1214 if (typeof options.data == "string") { 1215 options.data += "&_nocache=" + nocache; 1216 } else if (typeof options.data == "object") { 1217 options.data = $.extend(originalOptions.data, {'_nocache':nocache}); 1218 } 1219}); 1220 1221/** 1222 * Add a hidden field to the form to indicate that this will be an 1223 * Ajax request (only if this hidden field does not exist) 1224 * 1225 * @param object the form 1226 */ 1227function PMA_prepareForAjaxRequest($form) 1228{ 1229 if (! $form.find('input:hidden').is('#ajax_request_hidden')) { 1230 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />'); 1231 } 1232} 1233 1234/** 1235 * Generate a new password and copy it to the password input areas 1236 * 1237 * @param object the form that holds the password fields 1238 * 1239 * @return boolean always true 1240 */ 1241function suggestPassword(passwd_form) 1242{ 1243 // restrict the password to just letters and numbers to avoid problems: 1244 // "editors and viewers regard the password as multiple words and 1245 // things like double click no longer work" 1246 var pwchars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ"; 1247 var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :) 1248 var passwd = passwd_form.generated_pw; 1249 var randomWords = new Int32Array(passwordlength); 1250 1251 passwd.value = ''; 1252 1253 // First we're going to try to use a built-in CSPRNG 1254 if (window.crypto && window.crypto.getRandomValues) { 1255 window.crypto.getRandomValues(randomWords); 1256 } 1257 // Because of course IE calls it msCrypto instead of being standard 1258 else if (window.msCrypto && window.msCrypto.getRandomValues) { 1259 window.msCrypto.getRandomValues(randomWords); 1260 } else { 1261 // Fallback to Math.random 1262 for (var i = 0; i < passwordlength; i++) { 1263 randomWords[i] = Math.floor(Math.random() * pwchars.length); 1264 } 1265 } 1266 1267 for (var i = 0; i < passwordlength; i++) { 1268 passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length); 1269 } 1270 1271 passwd_form.text_pma_pw.value = passwd.value; 1272 passwd_form.text_pma_pw2.value = passwd.value; 1273 return true; 1274} 1275 1276/** 1277 * Version string to integer conversion. 1278 */ 1279function parseVersionString (str) 1280{ 1281 if (typeof(str) != 'string') { return false; } 1282 var add = 0; 1283 // Parse possible alpha/beta/rc/ 1284 var state = str.split('-'); 1285 if (state.length >= 2) { 1286 if (state[1].substr(0, 2) == 'rc') { 1287 add = - 20 - parseInt(state[1].substr(2)); 1288 } else if (state[1].substr(0, 4) == 'beta') { 1289 add = - 40 - parseInt(state[1].substr(4)); 1290 } else if (state[1].substr(0, 5) == 'alpha') { 1291 add = - 60 - parseInt(state[1].substr(5)); 1292 } else if (state[1].substr(0, 3) == 'dev') { 1293 /* We don't handle dev, it's git snapshot */ 1294 add = 0; 1295 } 1296 } 1297 // Parse version 1298 var x = str.split('.'); 1299 // Use 0 for non existing parts 1300 var maj = parseInt(x[0]) || 0; 1301 var min = parseInt(x[1]) || 0; 1302 var pat = parseInt(x[2]) || 0; 1303 var hotfix = parseInt(x[3]) || 0; 1304 return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add; 1305} 1306 1307/** 1308 * Indicates current available version on main page. 1309 */ 1310function PMA_current_version(data) 1311{ 1312 if (data && data.version && data.date) { 1313 var current = parseVersionString(pmaversion); 1314 var latest = parseVersionString(data['version']); 1315 var version_information_message = PMA_messages['strLatestAvailable'] + ' ' + escapeHtml(data['version']); 1316 if (latest > current) { 1317 var message = $.sprintf( 1318 PMA_messages['strNewerVersion'], 1319 escapeHtml(data['version']), 1320 escapeHtml(data['date']) 1321 ); 1322 if (Math.floor(latest / 10000) === Math.floor(current / 10000)) { 1323 /* Security update */ 1324 var klass = 'error'; 1325 } else { 1326 var klass = 'notice'; 1327 } 1328 $('#maincontainer').after('<div class="' + klass + '">' + message + '</div>'); 1329 } 1330 if (latest === current) { 1331 version_information_message = ' (' + PMA_messages['strUpToDate'] + ')'; 1332 } 1333 $('#li_pma_version').append(version_information_message); 1334 } 1335} 1336 1337/**
1338 * Loads Git revision data from ajax for index.php 1339 */ 1340function PMA_display_git_revision() 1341{ 1342 $('#is_git_revision').remove(); 1343 $.get( 1344 "index.php", 1345 { 1346 "server": PMA_commonParams.get('server'), 1347 "token": PMA_commonParams.get('token'), 1348 "git_revision": true, 1349 "ajax_request": true 1350 }, 1351 function (data) { 1352 if (data.success == true) { 1353 $(data.message).insertAfter('#li_pma_version'); 1354 } 1355 } 1356 ); 1357} 1358 1359/** 1360 * for libraries/display_change_password.lib.php 1361 * libraries/user_password.php 1362 * 1363 */ 1364 1365function displayPasswordGenerateButton() 1366{ 1367 $('#tr_element_before_generate_password').parent().append('<tr class="vmiddle"><td>' + PMA_messages['strGeneratePassword'] + '</td><td><input type="button" class="button" id="button_generate_password" value="' + PMA_messages['strGenerate'] + '" onclick="suggestPassword(this.form)" /><input type="text" name="generated_pw" id="generated_pw" /></td></tr>'); 1368 $('#div_element_before_generate_password').parent().append('<div class="item"><label for="button_generate_password">' + PMA_messages['strGeneratePassword'] + ':</label><span class="options"><input type="button" class="button" id="button_generate_password" value="' + PMA_messages['strGenerate'] + '" onclick="suggestPassword(this.form)" /></span><input type="text" name="generated_pw" id="generated_pw" /></div>'); 1369} 1370 1371/* 1372 * Adds a date/time picker to an element 1373 * 1374 * @param object $this_element a jQuery object pointing to the element 1375 */ 1376function PMA_addDatepicker($this_element, options) 1377{ 1378 var showTimeOption = false; 1379 if ($this_element.is('.datetimefield')) { 1380 showTimeOption = true; 1381 } 1382 1383 var defaultOptions = { 1384 showOn: 'button', 1385 buttonImage: themeCalendarImage, // defined in js/messages.php 1386 buttonImageOnly: true, 1387 stepMinutes: 1, 1388 stepHours: 1, 1389 showSecond: true, 1390 showTimepicker: showTimeOption, 1391 showButtonPanel: false, 1392 dateFormat: 'yy-mm-dd', // yy means year with four digits 1393 timeFormat: 'HH:mm:ss', 1394 altFieldTimeOnly: false, 1395 showAnim: '', 1396 beforeShow: function(input, inst) { 1397 // Remember that we came from the datepicker; this is used 1398 // in tbl_change.js by verificationsAfterFieldChange() 1399 $this_element.data('comes_from', 'datepicker'); 1400 1401 // Fix wrong timepicker z-index, doesn't work without timeout 1402 setTimeout(function() { 1403 $('#ui-timepicker-div').css('z-index',$('#ui-datepicker-div').css('z-index')); 1404 }, 0); 1405 }, 1406 onClose: function(dateText, dp_inst) { 1407 // The value is no more from the date picker 1408 $this_element.data('comes_from', ''); 1409 } 1410 }; 1411 if ( showTimeOption || (typeof(options) != 'undefined' && options.showTimepicker) ) { 1412 $this_element.datetimepicker($.extend(defaultOptions, options)); 1413 } else { 1414 $this_element.datepicker($.extend(defaultOptions, options)); 1415 } 1416} 1417 1418/** 1419 * selects the content of a given object, f.e. a textarea 1420 * 1421 * @param object element element of which the content will be selected 1422 * @param var lock variable which holds the lock for this element 1423 * or true, if no lock exists 1424 * @param boolean only_once if true this is only done once 1425 * f.e. only on first focus 1426 */ 1427function selectContent( element, lock, only_once ) 1428{ 1429 if ( only_once && only_once_elements[element.name] ) { 1430 return; 1431 } 1432 1433 only_once_elements[element.name] = true; 1434 1435 if ( lock ) { 1436 return; 1437 } 1438 1439 element.select(); 1440} 1441 1442/** 1443 * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query. 1444 * This function is called while clicking links 1445 * 1446 * @param object the link 1447 * @param object the sql query to submit 1448 * 1449 * @return boolean whether to run the query or not 1450 */ 1451function confirmLink(theLink, theSqlQuery) 1452{ 1453 // Confirmation is not required in the configuration file 1454 // or browser is Opera (crappy js implementation) 1455 if (PMA_messages['strDoYouReally'] == '' || typeof(window.opera) != 'undefined') { 1456 return true; 1457 } 1458 1459 var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], theSqlQuery)); 1460 if (is_confirmed) { 1461 if ( $(theLink).hasClass('formLinkSubmit') ) { 1462 var name = 'is_js_confirmed'; 1463 if ($(theLink).attr('href').indexOf('usesubform') != -1) { 1464 name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]'; 1465 } 1466 1467 $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />'); 1468 } else if ( typeof(theLink.href) != 'undefined' ) { 1469 theLink.href += '&is_js_confirmed=1'; 1470 } else if ( typeof(theLink.form) != 'undefined' ) { 1471 theLink.form.action += '?is_js_confirmed=1'; 1472 } 1473 } 1474 1475 return is_confirmed; 1476} // end of the 'confirmLink()' function 1477 1478/** 1479 * Displays an error message if a "DROP DATABASE" statement is submitted
1480 * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before 1481 * sumitting it if required. 1482 * This function is called by the 'checkSqlQuery()' js function. 1483 * 1484 * @param object the form 1485 * @param object the sql query textarea 1486 * 1487 * @return boolean whether to run the query or not 1488 * 1489 * @see checkSqlQuery() 1490 */ 1491function confirmQuery(theForm1, sqlQuery1) 1492{ 1493 // Confirmation is not required in the configuration file 1494 if (PMA_messages['strDoYouReally'] == '') { 1495 return true; 1496 } 1497 1498 // "DROP DATABASE" statement isn't allowed 1499 if (PMA_messages['strNoDropDatabases'] != '') { 1500 var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i'); 1501 if (drop_re.test(sqlQuery1.value)) { 1502 alert(PMA_messages['strNoDropDatabases']); 1503 theForm1.reset(); 1504 sqlQuery1.focus(); 1505 return false; 1506 } // end if 1507 } // end if 1508 1509 // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement 1510 // 1511 // TODO: find a way (if possible) to use the parser-analyser 1512 // for this kind of verification 1513 // For now, I just added a ^ to check for the statement at 1514 // beginning of expression 1515 1516 var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i'); 1517 var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i'); 1518 var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i'); 1519 var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i'); 1520 1521 if (do_confirm_re_0.test(sqlQuery1.value) 1522 || do_confirm_re_1.test(sqlQuery1.value) 1523 || do_confirm_re_2.test(sqlQuery1.value) 1524 || do_confirm_re_3.test(sqlQuery1.value)) { 1525 var message = (sqlQuery1.value.length > 100) 1526 ? sqlQuery1.value.substr(0, 100) + '\n ...' 1527 : sqlQuery1.value; 1528 var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], message)); 1529 // statement is confirmed -> update the 1530 // "is_js_confirmed" form field so the confirm test won't be 1531 // run on the server side and allows to submit the form 1532 if (is_confirmed) { 1533 theForm1.elements['is_js_confirmed'].value = 1; 1534 return true; 1535 } 1536 // statement is rejected -> do not submit the form 1537 else { 1538 window.focus(); 1539 sqlQuery1.focus(); 1540 return false; 1541 } // end if (handle confirm box result) 1542 } // end if (display confirm box) 1543 1544 return true; 1545} // end of the 'confirmQuery()' function 1546 1547/** 1548 * Displays an error message if the user submitted the sql query form with no 1549 * sql query, else checks for "DROP/DELETE/ALTER" statements 1550 * 1551 * @param object the form 1552 * 1553 * @return boolean always false 1554 * 1555 * @see confirmQuery() 1556 */ 1557function checkSqlQuery(theForm) 1558{ 1559 1560 // First check if codemirror is active. 1561 if (codemirror_editor) { 1562 theForm.elements['sql_query'].value = codemirror_editor.getValue(); 1563 } 1564 1565 var sqlQuery = theForm.elements['sql_query']; 1566 1567 var isEmpty = 1; 1568 1569 var space_re = new RegExp('\\s+'); 1570 if (typeof(theForm.elements['sql_file']) != 'undefined' && 1571 theForm.elements['sql_file'].value.replace(space_re, '') != '') { 1572 return true; 1573 } 1574 if (typeof(theForm.elements['sql_localfile']) != 'undefined' && 1575 theForm.elements['sql_localfile'].value.replace(space_re, '') != '') { 1576 return true; 1577 } 1578 if (isEmpty && typeof(theForm.elements['id_bookmark']) != 'undefined' && 1579 (theForm.elements['id_bookmark'].value != null || theForm.elements['id_bookmark'].value != '') && 1580 theForm.elements['id_bookmark'].selectedIndex != 0 1581 ) { 1582 return true; 1583 } 1584 // Checks for "DROP/DELETE/ALTER" statements 1585 if (sqlQuery.value.replace(space_re, '') != '') { 1586 if (confirmQuery(theForm, sqlQuery)) { 1587 return true; 1588 } else { 1589 return false; 1590 } 1591 } 1592 theForm.reset(); 1593 isEmpty = 1; 1594 1595 if (isEmpty) { 1596 sqlQuery.select(); 1597 alert(PMA_messages['strFormEmpty']); 1598 sqlQuery.focus(); 1599 return false;
1600 } 1601 1602 return true; 1603} // end of the 'checkSqlQuery()' function 1604 1605/** 1606 * Check if a form's element is empty. 1607 * An element containing only spaces is also considered empty 1608 * 1609 * @param object the form 1610 * @param string the name of the form field to put the focus on 1611 * 1612 * @return boolean whether the form field is empty or not 1613 */ 1614function emptyCheckTheField(theForm, theFieldName) 1615{ 1616 var theField = theForm.elements[theFieldName]; 1617 var space_re = new RegExp('\\s+'); 1618 return (theField.value.replace(space_re, '') == '') ? 1 : 0; 1619} // end of the 'emptyCheckTheField()' function 1620 1621 1622/** 1623 * Check whether a form field is empty or not 1624 * 1625 * @param object the form 1626 * @param string the name of the form field to put the focus on 1627 * 1628 * @return boolean whether the form field is empty or not 1629 */ 1630function emptyFormElements(theForm, theFieldName) 1631{ 1632 var theField = theForm.elements[theFieldName]; 1633 var isEmpty = emptyCheckTheField(theForm, theFieldName); 1634 1635 1636 return isEmpty; 1637} // end of the 'emptyFormElements()' function 1638 1639 1640/** 1641 * Ensures a value submitted in a form is numeric and is in a range 1642 * 1643 * @param object the form 1644 * @param string the name of the form field to check 1645 * @param integer the minimum authorized value 1646 * @param integer the maximum authorized value 1647 * 1648 * @return boolean whether a valid number has been submitted or not 1649 */ 1650function checkFormElementInRange(theForm, theFieldName, message, min, max) 1651{ 1652 var theField = theForm.elements[theFieldName]; 1653 var val = parseInt(theField.value); 1654 1655 if (typeof(min) == 'undefined') { 1656 min = 0; 1657 } 1658 if (typeof(max) == 'undefined') { 1659 max = Number.MAX_VALUE; 1660 } 1661 1662 // It's not a number 1663 if (isNaN(val)) { 1664 theField.select(); 1665 alert(PMA_messages['strNotNumber']); 1666 theField.focus(); 1667 return false; 1668 } 1669 // It's a number but it is not between min and max 1670 else if (val < min || val > max) { 1671 theField.select(); 1672 alert($.sprintf(message, val)); 1673 theField.focus(); 1674 return false; 1675 } 1676 // It's a valid number 1677 else { 1678 theField.value = val; 1679 } 1680 return true; 1681 1682} // end of the 'checkFormElementInRange()' function 1683 1684 1685function checkTableEditForm(theForm, fieldsCnt) 1686{ 1687 // TODO: avoid sending a message if user just wants to add a line 1688 // on the form but has not completed at least one field name 1689 1690 var atLeastOneField = 0; 1691 var i, elm, elm2, elm3, val, id; 1692 1693 for (i=0; i<fieldsCnt; i++) 1694 { 1695 id = "#field_" + i + "_2"; 1696 elm = $(id); 1697 val = elm.val(); 1698 if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') { 1699 elm2 = $("#field_" + i + "_3"); 1700 val = parseInt(elm2.val()); 1701 elm3 = $("#field_" + i + "_1"); 1702 if (isNaN(val) && elm3.val() != "") { 1703 elm2.select(); 1704 alert(PMA_messages['strNotNumber']); 1705 elm2.focus(); 1706 return false; 1707 } 1708 } 1709 1710 if (atLeastOneField == 0) { 1711 id = "field_" + i + "_1"; 1712 if (!emptyCheckTheField(theForm, id)) { 1713 atLeastOneField = 1; 1714 } 1715 } 1716 } 1717 if (atLeastOneField == 0) { 1718 var theField = theForm.elements["field_0_1"]; 1719 alert(PMA_messages['strFormEmpty']); 1720 theField.focus(); 1721 return false; 1722 } 1723 1724 // at least this section is under jQuery 1725 if ($("input.textfield[name='table']").val() == "") { 1726 alert(PMA_messages['strFormEmpty']); 1727 $("input.textfield[name='table']").focus(); 1728 return false; 1729 } 1730 1731 1732 return true; 1733} // enf of the 'checkTableEditForm()' function 1734 1735/** 1736 * Unbind all event handlers before tearing down a page 1737 */ 1738AJAX.registerTeardown('functions.js', function() { 1739 $('input:checkbox.checkall').die('click'); 1740}); 1741AJAX.registerOnload('functions.js', function() { 1742 /** 1743 * Row marking in horizontal mode (use "live" so that it works also for 1744 * next pages reached via AJAX); a tr may have the class noclick to remove 1745 * this behavior. 1746 */ 1747 1748 $('input:checkbox.checkall').live('click', function(e) { 1749 var $tr = $(this).closest('tr'); 1750 1751 // make the table unselectable (to prevent default highlighting when shift+click) 1752 //$tr.parents('table').noSelect(); 1753 1754 if (!e.shiftKey || last_clicked_row == -1) { 1755 // usual click 1756 1757 // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently 1758 var $checkbox = $tr.find(':checkbox'); 1759 if ($checkbox.length) { 1760 // checkbox in a row, add or remove class depending on checkbox state 1761 var checked = $checkbox.prop('checked'); 1762 if (!$(e.target).is(':checkbox, label')) { 1763 checked = !checked; 1764 $checkbox.prop('checked', checked).trigger('change'); 1765 } 1766 if (checked) { 1767 $tr.addClass('marked'); 1768 } else { 1769 $tr.removeClass('marked'); 1770 } 1771 last_click_checked = checked; 1772 } else { 1773 // normal data table, just toggle class 1774 $tr.toggleClass('marked'); 1775 last_click_checked = false;
1776 } 1777 1778 // remember the last clicked row 1779 last_clicked_row = last_click_checked ? $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1; 1780 last_shift_clicked_row = -1; 1781 } else { 1782 // handle the shift click 1783 PMA_clearSelection(); 1784 var start, end; 1785 1786 // clear last shift click result 1787 if (last_shift_clicked_row >= 0) { 1788 if (last_shift_clicked_row >= last_clicked_row) { 1789 start = last_clicked_row; 1790 end = last_shift_clicked_row; 1791 } else { 1792 start = last_shift_clicked_row; 1793 end = last_clicked_row; 1794 } 1795 $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)') 1796 .slice(start, end + 1) 1797 .removeClass('marked') 1798 .find(':checkbox') 1799 .prop('checked', false) 1800 .trigger('change'); 1801 } 1802 1803 // handle new shift click 1804 var curr_row = $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr); 1805 if (curr_row >= last_clicked_row) { 1806 start = last_clicked_row; 1807 end = curr_row; 1808 } else { 1809 start = curr_row; 1810 end = last_clicked_row; 1811 } 1812 $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)') 1813 .slice(start, end + 1) 1814 .addClass('marked') 1815 .find(':checkbox') 1816 .prop('checked', true) 1817 .trigger('change'); 1818 1819 // remember the last shift clicked row 1820 last_shift_clicked_row = curr_row; 1821 } 1822 }); 1823 1824 addDateTimePicker(); 1825 1826 /** 1827 * Add attribute to text boxes for iOS devices (based on bugID: 3508912) 1828 */ 1829 if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) { 1830 $('input[type=text]').attr('autocapitalize','off').attr('autocorrect','off'); 1831 } 1832}); 1833 1834/** 1835 * True if last click is to check a row. 1836 */ 1837var last_click_checked = false; 1838 1839/** 1840 * Zero-based index of last clicked row. 1841 * Used to handle the shift + click event in the code above. 1842 */ 1843var last_clicked_row = -1; 1844 1845/** 1846 * Zero-based index of last shift clicked row. 1847 */ 1848var last_shift_clicked_row = -1; 1849 1850/** 1851 * Row highlighting in horizontal mode (use "live" 1852 * so that it works also for pages reached via AJAX) 1853 */ 1854/*AJAX.registerOnload('functions.js', function() { 1855 $('tr.odd, tr.even').live('hover',function(event) { 1856 var $tr = $(this); 1857 $tr.toggleClass('hover',event.type=='mouseover'); 1858 $tr.children().toggleClass('hover',event.type=='mouseover'); 1859 }); 1860})*/ 1861 1862/** 1863 * This array is used to remember mark status of rows in browse mode 1864 */ 1865var marked_row = []; 1866 1867/** 1868 * marks all rows and selects its first checkbox inside the given element 1869 * the given element is usaly a table or a div containing the table or tables 1870 * 1871 * @param container DOM element 1872 */ 1873function markAllRows(container_id) 1874{ 1875 1876 $("#" + container_id).find("input:checkbox:enabled").prop('checked', true) 1877 .trigger("change") 1878 .parents("tr").addClass("marked"); 1879 return true; 1880} 1881 1882/** 1883 * marks all rows and selects its first checkbox inside the given element 1884 * the given element is usaly a table or a div containing the table or tables 1885 * 1886 * @param container DOM element 1887 */ 1888function unMarkAllRows(container_id) 1889{ 1890 1891 $("#" + container_id).find("input:checkbox:enabled").prop('checked', false) 1892 .trigger("change") 1893 .parents("tr").removeClass("marked"); 1894 return true; 1895} 1896 1897/** 1898 * Checks/unchecks all checkbox in given conainer (f.e. a form, fieldset or div) 1899 * 1900 * @param string container_id the container id 1901 * @param boolean state new value for checkbox (true or false) 1902 * @return boolean always true 1903 */ 1904function setCheckboxes(container_id, state) 1905{ 1906 1907 $("#" + container_id).find("input:checkbox").prop('checked', state); 1908 return true; 1909} // end of the 'setCheckboxes()' function 1910 1911/** 1912 * Checks/unchecks all options of a <select> element 1913 * 1914 * @param string the form name 1915 * @param string the element name 1916 * @param boolean whether to check or to uncheck options 1917 * 1918 * @return boolean always true 1919 */ 1920function setSelectOptions(the_form, the_select, do_check) 1921{ 1922 $("form[name='"+ the_form +"'] select[name='"+the_select+"']").find("option").prop('selected', do_check); 1923 return true; 1924} // end of the 'setSelectOptions()' function 1925 1926/** 1927 * Sets current value for query box. 1928 */ 1929function setQuery(query) 1930{ 1931 if (codemirror_editor) { 1932 codemirror_editor.setValue(query); 1933 codemirror_editor.focus(); 1934 } else { 1935 document.sqlform.sql_query.value = query; 1936 document.sqlform.sql_query.focus(); 1937 } 1938} 1939 1940 1941/** 1942 * Create quick sql statements. 1943 * 1944 */ 1945function insertQuery(queryType) 1946{ 1947 if (queryType == "clear") { 1948 setQuery(''); 1949 return; 1950 } 1951 1952 var query = ""; 1953 var myListBox = document.sqlform.dummy; 1954 var table = document.sqlform.table.value; 1955 1956 if (myListBox.options.length > 0) { 1957 sql_box_locked = true; 1958 var chaineAj = ""; 1959 var valDis = "";
1960 var editDis = ""; 1961 var NbSelect = 0; 1962 for (var i=0; i < myListBox.options.length; i++) { 1963 NbSelect++; 1964 if (NbSelect > 1) { 1965 chaineAj += ", "; 1966 valDis += ","; 1967 editDis += ","; 1968 } 1969 chaineAj += myListBox.options[i].value; 1970 valDis += "[value-" + NbSelect + "]"; 1971 editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]"; 1972 } 1973 if (queryType == "selectall") { 1974 query = "SELECT * FROM `" + table + "` WHERE 1"; 1975 } else if (queryType == "select") { 1976 query = "SELECT " + chaineAj + " FROM `" + table + "` WHERE 1"; 1977 } else if (queryType == "insert") { 1978 query = "INSERT INTO `" + table + "`(" + chaineAj + ") VALUES (" + valDis + ")"; 1979 } else if (queryType == "update") { 1980 query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1"; 1981 } else if (queryType == "delete") { 1982 query = "DELETE FROM `" + table + "` WHERE 1"; 1983 } 1984 setQuery(query); 1985 sql_box_locked = false; 1986 } 1987} 1988 1989 1990/** 1991 * Inserts multiple fields. 1992 * 1993 */ 1994function insertValueQuery() 1995{ 1996 var myQuery = document.sqlform.sql_query; 1997 var myListBox = document.sqlform.dummy; 1998 1999 if (myListBox.options.length > 0) { 2000 sql_box_locked = true; 2001 var chaineAj = ""; 2002 var NbSelect = 0; 2003 for (var i=0; i<myListBox.options.length; i++) { 2004 if (myListBox.options[i].selected) { 2005 NbSelect++; 2006 if (NbSelect > 1) { 2007 chaineAj += ", "; 2008 } 2009 chaineAj += myListBox.options[i].value; 2010 } 2011 } 2012 2013 /* CodeMirror support */ 2014 if (codemirror_editor) { 2015 codemirror_editor.replaceSelection(chaineAj); 2016 //IE support 2017 } else if (document.selection) { 2018 myQuery.focus(); 2019 var sel = document.selection.createRange(); 2020 sel.text = chaineAj; 2021 document.sqlform.insert.focus(); 2022 } 2023 //MOZILLA/NETSCAPE support 2024 else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") { 2025 var startPos = document.sqlform.sql_query.selectionStart; 2026 var endPos = document.sqlform.sql_query.selectionEnd; 2027 var chaineSql = document.sqlform.sql_query.value; 2028 2029 myQuery.value = chaineSql.substring(0, startPos) + chaineAj + chaineSql.substring(endPos, chaineSql.length); 2030 } else { 2031 myQuery.value += chaineAj; 2032 } 2033 sql_box_locked = false; 2034 } 2035} 2036 2037/** 2038 * Add a date/time picker to each element that needs it 2039 * (only when jquery-ui-timepicker-addon.js is loaded) 2040 */ 2041function addDateTimePicker() { 2042 if ($.timepicker != undefined) { 2043 $('input.datefield, input.datetimefield').each(function() { 2044 PMA_addDatepicker($(this)); 2045 }); 2046 } 2047} 2048 2049/** 2050 * Refresh/resize the WYSIWYG scratchboard 2051 */ 2052function refreshLayout() 2053{ 2054 var $elm = $('#pdflayout'); 2055 var orientation = $('#orientation_opt').val(); 2056 if ($('#paper_opt').length==1) { 2057 var paper = $('#paper_opt').val(); 2058 }else{ 2059 var paper = 'A4'; 2060 } 2061 if (orientation == 'P') { 2062 var posa = 'x'; 2063 var posb = 'y'; 2064 } else { 2065 var posa = 'y'; 2066 var posb = 'x'; 2067 } 2068 $elm.css('width', pdfPaperSize(paper, posa) + 'px'); 2069 $elm.css('height', pdfPaperSize(paper, posb) + 'px'); 2070} 2071 2072/** 2073 * Initializes positions of elements. 2074 */ 2075function TableDragInit() { 2076 $('.pdflayout_table').each(function () { 2077 var $this = $(this); 2078 var number = $this.data('number'); 2079 var x = $('#c_table_' + number + '_x').val(); 2080 var y = $('#c_table_' + number + '_y').val(); 2081 $this.css('left', x + 'px'); 2082 $this.css('top', y + 'px'); 2083 /* Make elements draggable */ 2084 $this.draggable({ 2085 containment: "parent", 2086 drag: function (evt, ui) { 2087 var number = $this.data('number'); 2088 $('#c_table_' + number + '_x').val(parseInt(ui.position.left)); 2089 $('#c_table_' + number + '_y').val(parseInt(ui.position.top)); 2090 } 2091 }); 2092 }); 2093} 2094 2095/** 2096 * Resets drag and drop positions. 2097 */ 2098function resetDrag() { 2099 $('.pdflayout_table').each(function () { 2100 var $this = $(this); 2101 var x = $this.data('x'); 2102 var y = $this.data('y'); 2103 $this.css('left', x + 'px'); 2104 $this.css('top', y + 'px'); 2105 }); 2106} 2107 2108/** 2109 * User schema handlers. 2110 */ 2111$(function () { 2112 /* Move in scratchboard on manual change */ 2113 $('.position-change').live('change', function () { 2114 var $this = $(this); 2115 var $elm = $('#table_' + $this.data('number')); 2116 $elm.css($this.data('axis'), $this.val() + 'px'); 2117 }); 2118 /* Refresh on paper size/orientation change */ 2119 $('.paper-change').live('change', function () { 2120 var $elm = $('#pdflayout'); 2121 if ($elm.css('visibility') == 'visible') { 2122 refreshLayout(); 2123 TableDragInit(); 2124 } 2125 }); 2126 /* Show/hide the WYSIWYG scratchboard */ 2127 $('#toggle-dragdrop').live('click', function () { 2128 var $elm = $('#pdflayout'); 2129 if ($elm.css('visibility') == 'hidden') { 2130 refreshLayout(); 2131 TableDragInit(); 2132 $elm.css('visibility', 'visible'); 2133 $elm.css('display', 'block'); 2134 $('#showwysiwyg').val('1'); 2135 } else { 2136 $elm.css('visibility', 'hidden'); 2137 $elm.css('display', 'none'); 2138 $('#showwysiwyg').val('0'); 2139 } 2140 }); 2141 /* Reset scratchboard */ 2142 $('#reset-dragdrop').live('click', function () { 2143 resetDrag(); 2144 }); 2145}); 2146 2147/** 2148 * Returns paper sizes for a given format 2149 */ 2150function pdfPaperSize(format, axis) 2151{ 2152 switch (format.toUpperCase()) { 2153 case '4A0': 2154 if (axis == 'x') { 2155 return 4767.87; 2156 } else { 2157 return 6740.79;
2158 } 2159 break; 2160 case '2A0': 2161 if (axis == 'x') { 2162 return 3370.39; 2163 } else { 2164 return 4767.87; 2165 } 2166 break; 2167 case 'A0': 2168 if (axis == 'x') { 2169 return 2383.94; 2170 } else { 2171 return 3370.39; 2172 } 2173 break; 2174 case 'A1': 2175 if (axis == 'x') { 2176 return 1683.78; 2177 } else { 2178 return 2383.94; 2179 } 2180 break; 2181 case 'A2': 2182 if (axis == 'x') { 2183 return 1190.55; 2184 } else { 2185 return 1683.78; 2186 } 2187 break; 2188 case 'A3': 2189 if (axis == 'x') { 2190 return 841.89; 2191 } else { 2192 return 1190.55; 2193 } 2194 break; 2195 case 'A4': 2196 if (axis == 'x') { 2197 return 595.28; 2198 } else { 2199 return 841.89; 2200 } 2201 break; 2202 case 'A5': 2203 if (axis == 'x') { 2204 return 419.53; 2205 } else { 2206 return 595.28; 2207 } 2208 break; 2209 case 'A6': 2210 if (axis == 'x') { 2211 return 297.64; 2212 } else { 2213 return 419.53; 2214 } 2215 break; 2216 case 'A7': 2217 if (axis == 'x') { 2218 return 209.76; 2219 } else { 2220 return 297.64; 2221 } 2222 break; 2223 case 'A8': 2224 if (axis == 'x') { 2225 return 147.40; 2226 } else { 2227 return 209.76; 2228 } 2229 break; 2230 case 'A9': 2231 if (axis == 'x') { 2232 return 104.88; 2233 } else { 2234 return 147.40; 2235 } 2236 break; 2237 case 'A10': 2238 if (axis == 'x') { 2239 return 73.70; 2240 } else { 2241 return 104.88; 2242 } 2243 break; 2244 case 'B0': 2245 if (axis == 'x') { 2246 return 2834.65; 2247 } else { 2248 return 4008.19; 2249 } 2250 break; 2251 case 'B1': 2252 if (axis == 'x') { 2253 return 2004.09; 2254 } else { 2255 return 2834.65; 2256 } 2257 break; 2258 case 'B2': 2259 if (axis == 'x') { 2260 return 1417.32; 2261 } else { 2262 return 2004.09; 2263 } 2264 break; 2265 case 'B3': 2266 if (axis == 'x') { 2267 return 1000.63; 2268 } else { 2269 return 1417.32; 2270 } 2271 break; 2272 case 'B4': 2273 if (axis == 'x') { 2274 return 708.66; 2275 } else { 2276 return 1000.63; 2277 } 2278 break; 2279 case 'B5': 2280 if (axis == 'x') { 2281 return 498.90; 2282 } else { 2283 return 708.66; 2284 } 2285 break; 2286 case 'B6': 2287 if (axis == 'x') { 2288 return 354.33; 2289 } else { 2290 return 498.90; 2291 } 2292 break; 2293 case 'B7': 2294 if (axis == 'x') { 2295 return 249.45; 2296 } else { 2297 return 354.33; 2298 } 2299 break; 2300 case 'B8': 2301 if (axis == 'x') { 2302 return 175.75; 2303 } else { 2304 return 249.45; 2305 } 2306 break; 2307 case 'B9': 2308 if (axis == 'x') { 2309 return 124.72; 2310 } else { 2311 return 175.75; 2312 } 2313 break; 2314 case 'B10': 2315 if (axis == 'x') { 2316 return 87.87; 2317 } else { 2318 return 124.72; 2319 } 2320 break; 2321 case 'C0': 2322 if (axis == 'x') { 2323 return 2599.37; 2324 } else { 2325 return 3676.54; 2326 } 2327 break; 2328 case 'C1': 2329 if (axis == 'x') { 2330 return 1836.85; 2331 } else { 2332 return 2599.37; 2333 } 2334 break; 2335 case 'C2': 2336 if (axis == 'x') { 2337 return 1298.27; 2338 } else { 2339 return 1836.85; 2340 } 2341 break; 2342 case 'C3': 2343 if (axis == 'x') { 2344 return 918.43; 2345 } else { 2346 return 1298.27; 2347 } 2348 break; 2349 case 'C4': 2350 if (axis == 'x') { 2351 return 649.13; 2352 } else { 2353 return 918.43; 2354 } 2355 break; 2356 case 'C5': 2357 if (axis == 'x') { 2358 return 459.21; 2359 } else { 2360 return 649.13; 2361 } 2362 break; 2363 case 'C6': 2364 if (axis == 'x') { 2365 return 323.15; 2366 } else { 2367 return 459.21; 2368 } 2369 break; 2370 case 'C7': 2371 if (axis == 'x') { 2372 return 229.61; 2373 } else { 2374 return 323.15; 2375 } 2376 break; 2377 case 'C8': 2378 if (axis == 'x') { 2379 return 161.57; 2380 } else { 2381 return 229.61; 2382 } 2383 break; 2384 case 'C9': 2385 if (axis == 'x') { 2386 return 113.39; 2387 } else { 2388 return 161.57; 2389 } 2390 break; 2391 case 'C10': 2392 if (axis == 'x') { 2393 return 79.37; 2394 } else { 2395 return 113.39; 2396 } 2397 break; 2398 case 'RA0': 2399 if (axis == 'x') { 2400 return 2437.80; 2401 } else { 2402 return 3458.27; 2403 } 2404 break; 2405 case 'RA1': 2406 if (axis == 'x') { 2407 return 1729.13; 2408 } else { 2409 return 2437.80; 2410 } 2411 break; 2412 case 'RA2': 2413 if (axis == 'x') { 2414 return 1218.90; 2415 } else { 2416 return 1729.13; 2417 } 2418 break; 2419 case 'RA3': 2420 if (axis == 'x') { 2421 return 864.57; 2422 } else { 2423 return 1218.90; 2424 } 2425 break; 2426 case 'RA4': 2427 if (axis == 'x') { 2428 return 609.45; 2429 } else { 2430 return 864.57; 2431 } 2432 break; 2433 case 'SRA0': 2434 if (axis == 'x') { 2435 return 2551.18; 2436 } else { 2437 return 3628.35; 2438 } 2439 break; 2440 case 'SRA1': 2441 if (axis == 'x') { 2442 return 1814.17; 2443 } else { 2444 return 2551.18; 2445 } 2446 break; 2447 case 'SRA2': 2448 if (axis == 'x') { 2449 return 1275.59; 2450 } else { 2451 return 1814.17; 2452 } 2453 break; 2454 case 'SRA3': 2455 if (axis == 'x') { 2456 return 907.09; 2457 } else { 2458 return 1275.59; 2459 } 2460 break; 2461 case 'SRA4': 2462 if (axis == 'x') { 2463 return 637.80; 2464 } else { 2465 return 907.09; 2466 } 2467 break; 2468 case 'LETTER': 2469 if (axis == 'x') { 2470 return 612.00; 2471 } else { 2472 return 792.00; 2473 } 2474 break; 2475 case 'LEGAL': 2476 if (axis == 'x') { 2477 return 612.00; 2478 } else { 2479 return 1008.00; 2480 } 2481 break; 2482 case 'EXECUTIVE': 2483 if (axis == 'x') { 2484 return 521.86; 2485 } else { 2486 return 756.00; 2487 } 2488 break; 2489 case 'FOLIO': 2490 if (axis == 'x') { 2491 return 612.00; 2492 } else { 2493 return 936.00; 2494 } 2495 break; 2496 } // end switch 2497 2498 return 0; 2499} 2500 2501/** 2502 * Unbind all event handlers before tearing down a page 2503 */ 2504AJAX.registerTeardown('functions.js', function() { 2505 $("a.inline_edit_sql").die('click'); 2506 $("input#sql_query_edit_save").die('click'); 2507 $("input#sql_query_edit_discard").die('click'); 2508 $('input.sqlbutton').unbind('click'); 2509 $("#export_type").unbind('change'); 2510 $('#sqlquery').unbind('keydown'); 2511 $('#sql_query_edit').unbind('keydown'); 2512 2513 if (codemirror_inline_editor) { 2514 // Copy the sql query to the text area to preserve it. 2515 $('#sql_query_edit').text(codemirror_inline_editor.getValue()); 2516 $(codemirror_inline_editor.getWrapperElement()).unbind('keydown'); 2517 codemirror_inline_editor.toTextArea(); 2518 codemirror_inline_editor = false;
2519 } 2520 if (codemirror_editor) { 2521 $(codemirror_editor.getWrapperElement()).unbind('keydown'); 2522 } 2523}); 2524 2525/** 2526 * Jquery Coding for inline editing SQL_QUERY 2527 */ 2528AJAX.registerOnload('functions.js', function() { 2529 // If we are coming back to the page by clicking forward button 2530 // of the browser, bind the code mirror to inline query editor. 2531 bindCodeMirrorToInlineEditor(); 2532 $("a.inline_edit_sql").live('click', function() { 2533 if ($('#sql_query_edit').length) { 2534 // An inline query editor is already open, 2535 // we don't want another copy of it 2536 return false; 2537 } 2538 2539 var $form = $(this).prev('form'); 2540 var sql_query = $form.find("input[name='sql_query']").val(); 2541 var $inner_sql = $(this).parent().prev().find('.inner_sql'); 2542 var old_text = $inner_sql.html(); 2543 2544 var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + escapeHtml(sql_query) + "</textarea>\n"; 2545 new_content += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages['strGo'] + "\"/>\n"; 2546 new_content += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages['strCancel'] + "\"/>\n"; 2547 var $editor_area = $('div#inline_editor'); 2548 if ($editor_area.length == 0) { 2549 $editor_area = $('<div id="inline_editor_outer"></div>'); 2550 $editor_area.insertBefore($inner_sql); 2551 } 2552 $editor_area.html(new_content); 2553 $inner_sql.hide(); 2554 2555 bindCodeMirrorToInlineEditor(); 2556 return false; 2557 }); 2558 2559 $("input#sql_query_edit_save").live('click', function() { 2560 if (codemirror_inline_editor) { 2561 var sql_query = codemirror_inline_editor.getValue(); 2562 } else { 2563 var sql_query = $(this).prev().val(); 2564 } 2565 2566 var $form = $("a.inline_edit_sql").prev('form'); 2567 var $fake_form = $('<form>', {action: 'import.php', method: 'post'}) 2568 .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone()) 2569 .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1})) 2570 .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0})) 2571 .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query})); 2572 if (! checkSqlQuery($fake_form[0])) { 2573 return false; 2574 } 2575 $fake_form.appendTo($('body')).submit(); 2576 }); 2577 2578 $("input#sql_query_edit_discard").live('click', function() { 2579 $('div#inline_editor_outer') 2580 .empty() 2581 .siblings('.inner_sql').show(); 2582 }); 2583 2584 $('input.sqlbutton').click(function(evt) { 2585 insertQuery(evt.target.id); 2586 return false; 2587 }); 2588 2589 $("#export_type").change(function() { 2590 if ($("#export_type").val()=='svg') { 2591 $("#show_grid_opt").prop("disabled",true); 2592 $("#orientation_opt").prop("disabled",true); 2593 $("#with_doc").prop("disabled",true); 2594 $("#show_table_dim_opt").removeProp("disabled"); 2595 $("#all_tables_same_width").removeProp("disabled"); 2596 $("#paper_opt").removeProp("disabled"); 2597 $("#show_color_opt").removeProp("disabled"); 2598 //$(this).css("background-color","yellow"); 2599 } else if ($("#export_type").val()=='dia') { 2600 $("#show_grid_opt").prop("disabled",true); 2601 $("#with_doc").prop("disabled",true); 2602 $("#show_table_dim_opt").prop("disabled",true); 2603 $("#all_tables_same_width").prop("disabled",true); 2604 $("#paper_opt").removeProp("disabled"); 2605 $("#show_color_opt").removeProp("disabled"); 2606 $("#orientation_opt").removeProp("disabled"); 2607 } else if ($("#export_type").val()=='eps') { 2608 $("#show_grid_opt").prop("disabled",true); 2609 $("#orientation_opt").removeProp("disabled"); 2610 $("#with_doc").prop("disabled",true); 2611 $("#show_table_dim_opt").prop("disabled",true); 2612 $("#all_tables_same_width").prop("disabled",true); 2613 $("#paper_opt").prop("disabled",true); 2614 $("#show_color_opt").prop("disabled",true); 2615 } else if ($("#export_type").val()=='pdf') { 2616 $("#show_grid_opt").removeProp("disabled"); 2617 $("#orientation_opt").removeProp("disabled"); 2618 $("#with_doc").removeProp("disabled"); 2619 $("#show_table_dim_opt").removeProp("disabled"); 2620 $("#all_tables_same_width").removeProp("disabled"); 2621 $("#paper_opt").removeProp("disabled"); 2622 $("#show_color_opt").removeProp("disabled"); 2623 } else { 2624 // nothing 2625 } 2626 }); 2627 2628 if ($('#input_username')) { 2629 if ($('#input_username').val() == '') { 2630 $('#input_username').focus(); 2631 } else { 2632 $('#input_password').focus(); 2633 } 2634 } 2635}); 2636 2637/** 2638 * Binds the CodeMirror to the text area used to inline edit a query. 2639 */ 2640function bindCodeMirrorToInlineEditor() { 2641 var $inline_editor = $('#sql_query_edit');
2642 if ($inline_editor.length > 0) { 2643 if (typeof CodeMirror !== 'undefined') { 2644 var height = $('#sql_query_edit').css('height'); 2645 codemirror_inline_editor = CodeMirror.fromTextArea($inline_editor[0], { 2646 lineNumbers: true, 2647 matchBrackets: true, 2648 indentUnit: 4, 2649 mode: "text/x-mysql", 2650 lineWrapping: true 2651 }); 2652 codemirror_inline_editor.getScrollerElement().style.height = height; 2653 codemirror_inline_editor.refresh(); 2654 codemirror_inline_editor.focus(); 2655 $(codemirror_inline_editor.getWrapperElement()).bind( 2656 'keydown', 2657 catchKeypressesFromSqlTextboxes 2658 ); 2659 } else { 2660 $inline_editor.focus().bind( 2661 'keydown', 2662 catchKeypressesFromSqlTextboxes 2663 ); 2664 } 2665 } 2666} 2667 2668function catchKeypressesFromSqlTextboxes(event) { 2669 // ctrl-enter is 10 in chrome and ie, but 13 in ff 2670 if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) { 2671 if ($('#sql_query_edit').length > 0) { 2672 $("#sql_query_edit_save").trigger('click'); 2673 } else if ($('#sqlquery').length > 0) { 2674 $("#button_submit_query").trigger('click'); 2675 } 2676 } 2677} 2678 2679/** 2680 * Show a message on the top of the page for an Ajax request 2681 * 2682 * Sample usage: 2683 * 2684 * 1) var $msg = PMA_ajaxShowMessage(); 2685 * This will show a message that reads "Loading...". Such a message will not 2686 * disappear automatically and cannot be dismissed by the user. To remove this 2687 * message either the PMA_ajaxRemoveMessage($msg) function must be called or
2688 * another message must be show with PMA_ajaxShowMessage() function. 2689 * 2690 * 2) var $msg = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 2691 * This is a special case. The behaviour is same as above, 2692 * just with a different message 2693 * 2694 * 3) var $msg = PMA_ajaxShowMessage('The operation was successful'); 2695 * This will show a message that will disappear automatically and it can also 2696 * be dismissed by the user. 2697 * 2698 * 4) var $msg = PMA_ajaxShowMessage('Some error', false); 2699 * This will show a message that will not disappear automatically, but it 2700 * can be dismissed by the user after he has finished reading it. 2701 * 2702 * @param string message string containing the message to be shown. 2703 * optional, defaults to 'Loading...' 2704 * @param mixed timeout number of milliseconds for the message to be visible 2705 * optional, defaults to 5000. If set to 'false', the 2706 * notification will never disappear 2707 * @return jQuery object jQuery Element that holds the message div 2708 * this object can be passed to PMA_aja
2708xRemoveMessage() 2709 * to remove the notification 2710 */ 2711function PMA_ajaxShowMessage(message, timeout) 2712{ 2713 /** 2714 * @var self_closing Whether the notification will automatically disappear 2715 */ 2716 var self_closing = true; 2717 /** 2718 * @var dismissable Whether the user will be able to remove 2719 * the notification by clicking on it 2720 */ 2721 var dismissable = true; 2722 // Handle the case when a empty data.message is passed. 2723 // We don't want the empty message 2724 if (message == '') { 2725 return true; 2726 } else if (! message) { 2727 // If the message is undefined, show the default 2728 message = PMA_messages['strLoading']; 2729 dismissable = false; 2730 self_closing = false; 2731 } else if (message == PMA_messages['strProcessingRequest']) { 2732 // This is another case where the message should not disappear 2733 dismissable = false; 2734 self_closing = false; 2735 } 2736 // Figure out whether (or after how long) to remove the notification 2737 if (timeout == undefined) { 2738 timeout = 5000; 2739 } else if (timeout === false) { 2740 self_closing = false; 2741 } 2742 // Create a parent element for the AJAX messages, if necessary 2743 if ($('#loading_parent').length == 0) { 2744 $('<div id="loading_parent"></div>') 2745 .prependTo("body"); 2746 } 2747 // Update message count to create distinct message elements every time 2748 ajax_message_count++; 2749 // Remove all old messages, if any 2750 $("span.ajax_notification[id^=ajax_message_num]").remove(); 2751 /** 2752 * @var $retval a jQuery object containing the reference 2753 * to the created AJAX message 2754 */ 2755 var $retval = $( 2756 '<span class="ajax_notification" id="ajax_message_num_' 2757 + ajax_message_count + 2758 '"></span>' 2759 ) 2760 .hide() 2761 .appendTo("#loading_parent") 2762 .html(message) 2763 .show(); 2764 // If the notification is self-closing we should create a callback to remove it 2765 if (self_closing) { 2766 $retval 2767 .delay(timeout) 2768 .fadeOut('medium', function() { 2769 if ($(this).is('.dismissable')) { 2770 $(this).tooltip('destroy'); 2771 } 2772 // Remove the notification 2773 $(this).remove(); 2774 }); 2775 } 2776 // If the notification is dismissable we need to add the relevant class to it 2777 // and add a tooltip so that the users know that it can be removed 2778 if (dismissable) { 2779 $retval.addClass('dismissable').css('cursor', 'pointer'); 2780 /** 2781 * Add a tooltip to the notification to let the user know that (s)he 2782 * can dismiss the ajax notification by clicking on it. 2783 */ 2784 PMA_tooltip( 2785 $retval, 2786 'span', 2787 PMA_messages['strDismiss'] 2788 ); 2789 } 2790 2791 return $retval; 2792} 2793 2794/** 2795 * Removes the message shown for an Ajax operation when it's completed 2796 * 2797 * @param jQuery object jQuery Element that holds the notification 2798 * 2799 * @return nothing 2800 */ 2801function PMA_ajaxRemoveMessage($this_msgbox) 2802{ 2803 if ($this_msgbox != undefined && $this_msgbox instanceof jQuery) { 2804 $this_msgbox 2805 .stop(true, true) 2806 .fadeOut('medium'); 2807 if ($this_msgbox.is('.dismissable')) { 2808 if ($.isFunction($this_msgbox.tooltip)) { 2809 $this_msgbox.tooltip('destroy'); 2810 } 2811 } else { 2812 $this_msgbox.remove(); 2813 } 2814 } 2815} 2816 2817// This event only need to be fired once after the initial page load 2818$(function() { 2819 /** 2820 * Allows the user to dismiss a notification
2821 * created with PMA_ajaxShowMessage() 2822 */ 2823 $('span.ajax_notification.dismissable').live('click', function () { 2824 PMA_ajaxRemoveMessage($(this)); 2825 }); 2826 /** 2827 * The below two functions hide the "Dismiss notification" tooltip when a user 2828 * is hovering a link or button that is inside an ajax message 2829 */ 2830 $('span.ajax_notification a, span.ajax_notification button, span.ajax_notification input') 2831 .live('mouseover', function () { 2832 $(this).parents('span.ajax_notification').tooltip('disable'); 2833 }) 2834 .live('mouseout', function () { 2835 $(this).parents('span.ajax_notification').tooltip('enable'); 2836 }); 2837}); 2838 2839/** 2840 * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected 2841 */ 2842function PMA_showNoticeForEnum(selectElement) 2843{ 2844 var enum_notice_id = selectElement.attr("id").split("_")[1]; 2845 enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2]) + 1); 2846 var selectedType = selectElement.val(); 2847 if (selectedType == "ENUM" || selectedType == "SET") { 2848 $("p#enum_notice_" + enum_notice_id).show(); 2849 } else { 2850 $("p#enum_notice_" + enum_notice_id).hide(); 2851 } 2852} 2853 2854/* 2855 * Creates a Profiling Chart with jqplot. Used in sql.js 2856 * and in server_status_monitor.js 2857 */ 2858function PMA_createProfilingChartJqplot(target, data) 2859{ 2860 return $.jqplot(target, [data], 2861 { 2862 seriesDefaults: { 2863 renderer: $.jqplot.PieRenderer, 2864 rendererOptions: { 2865 showDataLabels: true 2866 } 2867 }, 2868 legend: { 2869 show: true, 2870 location: 'e' 2871 }, 2872 // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette 2873 seriesColors: [ 2874 '#fce94f', 2875 '#fcaf3e', 2876 '#e9b96e', 2877 '#8ae234', 2878 '#729fcf', 2879 '#ad7fa8', 2880 '#ef2929', 2881 '#eeeeec', 2882 '#888a85', 2883 '#c4a000', 2884 '#ce5c00', 2885 '#8f5902', 2886 '#4e9a06', 2887 '#204a87', 2888 '#5c3566', 2889 '#a40000', 2890 '#babdb6', 2891 '#2e3436' 2892 ] 2893 } 2894 ); 2895} 2896 2897/** 2898 * Formats a profiling duration nicely (in us and ms time). Used in server_status.js 2899 * 2900 * @param integer Number to be formatted, should be in the range of microsecond to second 2901 * @param integer Accuracy, how many numbers right to the comma should be 2902 * @return string The formatted number 2903 */ 2904function PMA_prettyProfilingNum(num, acc) 2905{ 2906 if (!acc) { 2907 acc = 2; 2908 } 2909 acc = Math.pow(10, acc); 2910 if (num * 1000 < 0.1) { 2911 num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ'; 2912 } else if (num < 0.1) { 2913 num = Math.round(acc * (num * 1000)) / acc + 'm'; 2914 } else { 2915 num = Math.round(acc * num) / acc; 2916 } 2917 2918 return num + 's'; 2919} 2920 2921 2922/** 2923 * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode! 2924 * 2925 * @param string Query to be formatted 2926 * @return string The formatted query 2927 */ 2928function PMA_SQLPrettyPrint(string) 2929{ 2930 if (typeof CodeMirror == 'undefined') { 2931 return string; 2932 } 2933 2934 var mode = CodeMirror.getMode({},"text/x-mysql"); 2935 var stream = new CodeMirror.StringStream(string); 2936 var state = mode.startState(); 2937 var token, tokens = []; 2938 var output = ''; 2939 var tabs = function(cnt) { 2940 var ret = ''; 2941 for (var i=0; i<4*cnt; i++) { 2942 ret += " "; 2943 } 2944 return ret; 2945 }; 2946 2947 // "root-level" statements 2948 var statements = { 2949 'select': ['select', 'from','on','where','having','limit','order by','group by'], 2950 'update': ['update', 'set','where'], 2951 'insert into': ['insert into', 'values'] 2952 }; 2953 // don't put spaces before these tokens 2954 var spaceExceptionsBefore = { ';':true, ',': true, '.': true, '(': true }; 2955 // don't put spaces after these tokens 2956 var spaceExceptionsAfter = { '.': true }; 2957 2958 // Populate tokens array 2959 var str=''; 2960 while (! stream.eol()) { 2961 stream.start = stream.pos; 2962 token = mode.token(stream, state); 2963 if (token != null) { 2964 tokens.push([token, stream.current().toLowerCase()]); 2965 } 2966 } 2967 2968 var currentStatement = tokens[0][1]; 2969
2970 if (! statements[currentStatement]) { 2971 return string; 2972 } 2973 // Holds all currently opened code blocks (statement, function or generic) 2974 var blockStack = []; 2975 // Holds the type of block from last iteration (the current is in blockStack[0]) 2976 var previousBlock; 2977 // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock 2978 var newBlock, endBlock; 2979 // How much to indent in the current line 2980 var indentLevel = 0; 2981 // Holds the "root-level" statements 2982 var statementPart, lastStatementPart = statements[currentStatement][0]; 2983 2984 blockStack.unshift('statement'); 2985 2986 // Iterate through every token and format accordingly 2987 for (var i = 0; i < tokens.length; i++) { 2988 previousBlock = blockStack[0]; 2989 2990 // New block => push to stack 2991 if (tokens[i][1] == '(') { 2992 if (i < tokens.length - 1 && tokens[i+1][0] == 'statement-verb') { 2993 blockStack.unshift(newBlock = 'statement'); 2994 } else if (i > 0 && tokens[i-1][0] == 'builtin') { 2995 blockStack.unshift(newBlock = 'function'); 2996 } else { 2997 blockStack.unshift(newBlock = 'generic'); 2998 } 2999 } else { 3000 newBlock = null; 3001 } 3002 3003 // Block end => pop from stack 3004 if (tokens[i][1] == ')') { 3005 endBlock = blockStack[0]; 3006 blockStack.shift(); 3007 } else { 3008 endBlock = null; 3009 } 3010 3011 // A subquery is starting 3012 if (i > 0 && newBlock == 'statement') { 3013 indentLevel++; 3014 output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i+1][1].toUpperCase() + "\n" + tabs(indentLevel + 1); 3015 currentStatement = tokens[i+1][1]; 3016 i++; 3017 continue; 3018 } 3019 3020 // A subquery is ending 3021 if (endBlock == 'statement' && indentLevel > 0) { 3022 output += "\n" + tabs(indentLevel); 3023 indentLevel--; 3024 } 3025 3026 // One less indentation for statement parts (from, where, order by, etc.) and a newline 3027 statementPart = statements[currentStatement].indexOf(tokens[i][1]); 3028 if (statementPart != -1) { 3029 if (i > 0) { 3030 output += "\n"; 3031 } 3032 output += tabs(indentLevel) + tokens[i][1].toUpperCase(); 3033 output += "\n" + tabs(indentLevel + 1); 3034 lastStatementPart = tokens[i][1]; 3035 } 3036 // Normal indentatin and spaces for everything else 3037 else { 3038 if (! spaceExceptionsBefore[tokens[i][1]] 3039 && ! (i > 0 && spaceExceptionsAfter[tokens[i-1][1]]) 3040 && output.charAt(output.length -1) != ' ' ) { 3041 output += " "; 3042 } 3043 if (tokens[i][0] == 'keyword') { 3044 output += tokens[i][1].toUpperCase(); 3045 } else { 3046 output += tokens[i][1]; 3047 } 3048 } 3049 3050 // split columns in select and 'update set' clauses, but only inside statements blocks 3051 if (( lastStatementPart == 'select' || lastStatementPart == 'where' || lastStatementPart == 'set') 3052 && tokens[i][1]==',' && blockStack[0] == 'statement') { 3053 3054 output += "\n" + tabs(indentLevel + 1); 3055 } 3056 3057 // split conditions in where clauses, but only inside statements blocks 3058 if (lastStatementPart == 'where' 3059 && (tokens[i][1]=='and' || tokens[i][1]=='or' || tokens[i][1]=='xor')) { 3060 3061 if (blockStack[0] == 'statement') { 3062 output += "\n" + tabs(indentLevel + 1); 3063 } 3064 // Todo: Also split and or blocks in newlines & identation++ 3065 //if (blockStack[0] == 'generic') 3066 // output += ... 3067 } 3068 } 3069 return output; 3070} 3071 3072/** 3073 * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not 3074 * return a jQuery object yet and hence cannot be chained 3075 * 3076 * @param string question 3077 * @param string url URL to be passed to the callbackFn to make 3078 * an Ajax call to 3079 * @param function callbackFn callback to execute after user clicks on OK 3080 */ 3081 3082jQuery.fn.PMA_confirm = function(question, url, callbackFn) { 3083 var confirmState = PMA_commonParams.get('confirm');
3084 // when the Confirm directive is set to false in config.inc.php 3085 // and not changed in user prefs, confirmState is "" 3086 // when it's unticked in user prefs, confirmState is 1 3087 if (confirmState === "" || confirmState === "1") { 3088 // user does not want to confirm 3089 if ($.isFunction(callbackFn)) { 3090 callbackFn.call(this, url); 3091 return true; 3092 } 3093 } 3094 if (PMA_messages['strDoYouReally'] == '') { 3095 return true; 3096 } 3097 3098 /** 3099 * @var button_options Object that stores the options passed to jQueryUI 3100 * dialog 3101 */ 3102 var button_options = {}; 3103 button_options[PMA_messages['strOK']] = function() { 3104 $(this).dialog("close"); 3105 3106 if ($.isFunction(callbackFn)) { 3107 callbackFn.call(this, url); 3108 } 3109 }; 3110 button_options[PMA_messages['strCancel']] = function() { 3111 $(this).dialog("close"); 3112 }; 3113 3114 $('<div/>', {'id':'confirm_dialog'}) 3115 .prepend(question) 3116 .dialog({ 3117 buttons: button_options, 3118 close: function () { 3119 $(this).remove(); 3120 }, 3121 modal: true 3122 }); 3123}; 3124 3125/** 3126 * jQuery function to sort a table's body after a new row has been appended to it. 3127 * Also fixes the even/odd classes of the table rows at the end. 3128 * 3129 * @param string text_selector string to select the sortKey's text 3130 * 3131 * @return jQuery Object for chaining purposes 3132 */ 3133jQuery.fn.PMA_sort_table = function(text_selector) { 3134 return this.each(function() { 3135 3136 /** 3137 * @var table_body Object referring to the table's <tbody> element 3138 */ 3139 var table_body = $(this); 3140 /** 3141 * @var rows Object referring to the collection of rows in {@link table_body} 3142 */ 3143 var rows = $(this).find('tr').get(); 3144 3145 //get the text of the field that we will sort by 3146 $.each(rows, function(index, row) { 3147 row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase()); 3148 }); 3149 3150 //get the sorted order 3151 rows.sort(function(a, b) { 3152 if (a.sortKey < b.sortKey) { 3153 return -1; 3154 } 3155 if (a.sortKey > b.sortKey) { 3156 return 1; 3157 } 3158 return 0; 3159 }); 3160 3161 //pull out each row from the table and then append it according to it's order 3162 $.each(rows, function(index, row) { 3163 $(table_body).append(row); 3164 row.sortKey = null; 3165 }); 3166 3167 //Re-check the classes of each row 3168 $(this).find('tr:odd') 3169 .removeClass('even').addClass('odd') 3170 .end() 3171 .find('tr:even') 3172 .removeClass('odd').addClass('even'); 3173 }); 3174}; 3175 3176/** 3177 * Unbind all event handlers before tearing down a page 3178 */ 3179AJAX.registerTeardown('functions.js', function() { 3180 $("#create_table_form_minimal.ajax").die('submit'); 3181 $("form.create_table_form.ajax").die('submit'); 3182 $("form.create_table_form.ajax input[name=submit_num_fields]").die('click'); 3183 $("form.create_table_form.ajax input").die('keyup'); 3184}); 3185 3186/** 3187 * jQuery coding for 'Create Table'. Used on db_operations.php, 3188 * db_structure.php and db_tracking.php (i.e., wherever 3189 * libraries/display_create_table.lib.php is used) 3190 * 3191 * Attach Ajax Event handlers for Create Table 3192 */ 3193AJAX.registerOnload('functions.js', function() { 3194 /** 3195 * Attach event handler for submission of create table form (save) 3196 */ 3197 $("form.create_table_form.ajax").live('submit', function(event) { 3198 event.preventDefault(); 3199 3200 /** 3201 * @var the_form object referring to the create table form 3202 */ 3203 var $form = $(this); 3204 3205 /* 3206 * First validate the form; if there is a problem, avoid submitting it 3207 * 3208 * checkTableEditForm() needs a pure element and not a jQuery object, 3209 * this is why we pass $form[0] as a parameter (the jQuery object 3210 * is actually an array of DOM elements) 3211 */ 3212 3213 if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) { 3214 PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 3215 PMA_prepareForAjaxRequest($form); 3216 //User wants to submit the form 3217 $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function(data) { 3218 if (data.success == true) { 3219 $('#properties_message') 3220 .removeClass('error') 3221 .html(''); 3222 PMA_ajaxShowMessage(data.message); 3223 // Only if the create table dialog (distinct panel) exists 3224 if ($("#create_table_dialog").length > 0) { 3225 $("#create_table_dialog").dialog("close").remove(); 3226 } 3227 $('#tableslistcontainer').before(data.formatted_sql); 3228 3229 /** 3230 * @var tables_table Object referring to the <tbody> element that holds the list of tables 3231 */ 3232 var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row"); 3233 // this is the first table created in this db 3234 if (tables_table.length == 0) { 3235 PMA_commonActions.refreshMain( 3236 PMA_commonParams.get('opendb_url') 3237 ); 3238 } else { 3239 /** 3240 * @var curr_last_row Object referring to the last <tr> element in {@link tables_table} 3241 */
3242 var curr_last_row = $(tables_table).find('tr:last'); 3243 /** 3244 * @var curr_last_row_index_string String containing the index of {@link curr_last_row} 3245 */ 3246 var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0]; 3247 /** 3248 * @var curr_last_row_index Index of {@link curr_last_row} 3249 */ 3250 var curr_last_row_index = parseFloat(curr_last_row_index_string); 3251 /** 3252 * @var new_last_row_index Index of the new row to be appended to {@link tables_table} 3253 */ 3254 var new_last_row_index = curr_last_row_index + 1; 3255 /** 3256 * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table} 3257 */ 3258 var new_last_row_id = 'checkbox_tbl_' + new_last_row_index; 3259 3260 data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id); 3261 //append to table 3262 $(data.new_table_string) 3263 .appendTo(tables_table); 3264 3265 //Sort the table 3266 $(tables_table).PMA_sort_table('th'); 3267 3268 // Adjust summary row 3269 PMA_adjustTotals(); 3270 } 3271 3272 //Refresh navigation as a new table has been added 3273 PMA_reloadNavigation(); 3274 } else { 3275 PMA_ajaxShowMessage( 3276 '<div class="error">' + data.error + '</div>', 3277 false 3278 ); 3279 } 3280 }); // end $.post() 3281 } // end if (checkTableEditForm() ) 3282 }); // end create table form (save) 3283 3284 /** 3285 * Attach event handler for create table form (add fields) 3286 */ 3287 $("form.create_table_form.ajax input[name=submit_num_fields]").live('click', function(event) { 3288 event.preventDefault(); 3289 /** 3290 * @var the_form object referring to the create table form 3291 */ 3292 var $form = $(this).closest('form'); 3293 3294 var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 3295 PMA_prepareForAjaxRequest($form); 3296 3297 //User wants to add more fields to the table 3298 $.post($form.attr('action'), $form.serialize() + "&submit_num_fields=1", function(data) { 3299 if (data.success) { 3300 $("#page_content").html(data.message); 3301 PMA_verifyColumnsProperties(); 3302 PMA_ajaxRemoveMessage($msgbox); 3303 } else { 3304 PMA_ajaxShowMessage(data.error); 3305 } 3306 }); //end $.post() 3307 }); // end create table form (add fields) 3308 3309 $("form.create_table_form.ajax input").live('keydown', function (event) { 3310 if (event.keyCode == 13) { 3311 event.preventDefault(); 3312 event.stopImmediatePropagation(); 3313 $(this) 3314 .closest('form') 3315 .append('<input type="hidden" name="do_save_data" value="1" />') 3316 .submit(); 3317 } 3318 }); 3319}); 3320 3321 3322/** 3323 * Unbind all event handlers before tearing down a page 3324 */ 3325AJAX.registerTeardown('functions.js', function() { 3326 $("#copyTable.ajax").die('submit'); 3327 $("#moveTableForm").die('submit'); 3328 $("#tableOptionsForm").die('submit'); 3329 $("#tbl_maintenance li a.maintain_action.ajax").die('click'); 3330}); 3331/** 3332 * jQuery coding for 'Table operations'. Used on tbl_operations.php 3333 * Attach Ajax Event handlers for Table operations 3334 */ 3335AJAX.registerOnload('functions.js', function() { 3336 /** 3337 *Ajax action for submitting the "Copy table" 3338 **/ 3339 $("#copyTable.ajax").live('submit', function(event) { 3340 event.preventDefault(); 3341 var $form = $(this); 3342 PMA_prepareForAjaxRequest($form); 3343 $.post($form.attr('action'), $form.serialize()+"&submit_copy=Go", function(data) { 3344 if (data.success == true) { 3345 if ($form.find("input[name='switch_to_new']").prop('checked')) { 3346 PMA_commonParams.set( 3347 'db', 3348 data.db 3349 ); 3350 PMA_commonParams.set( 3351 'table', 3352 $form.find("input[name='new_name']").val() 3353 ); 3354 PMA_commonActions.refreshMain(false, function () { 3355 PMA_ajaxShowMessage(data.message); 3356 }); 3357 } else { 3358 PMA_ajaxShowMessage(data.message); 3359 } 3360 // Refresh navigation when the table is copied 3361 PMA_reloadNavigation(); 3362 } else { 3363 PMA_ajaxShowMessage(data.error, false); 3364 } 3365 }); // end $.post() 3366 });//end of copyTable ajax submit 3367 3368 /** 3369 *Ajax action for submitting the "Move table" 3370 */ 3371 $("#moveTableForm").live('submit', function(event) { 3372 event.preventDefault(); 3373 var $form = $(this); 3374 var db = $form.find('select[name=target_db]').val(); 3375 var tbl = $form.find('input[name=new_name]').val(); 3376 PMA_prepareForAjaxRequest($form); 3377 $.post($form.attr('action'), $form.serialize()+"&submit_move=1", function(data) { 3378 if (data.success == true) { 3379 PMA_commonParams.set('db', db); 3380 PMA_commonParams.set('table', tbl); 3381 PMA_commonActions.refreshMain(false, function () { 3382 PMA_ajaxShowMessage(data.message); 3383 }); 3384 // Refresh navigation when the table is copied 3385 PMA_reloadNavigation(); 3386 } else { 3387 PMA_ajaxShowMessage(data.error, false); 3388 } 3389 }); // end $.post() 3390 }); 3391 3392 /** 3393 * Ajax action for submitting the "Table options" 3394 */ 3395 $("#tableOptionsForm").live('submit', function(event) { 3396 event.preventDefault(); 3397 event.stopPropagation(); 3398 var $form = $(this); 3399 var $tblNameField = $form.find('input[name=new_name]'); 3400 if ($tblNameField.val() !== $tblNameField[0].defaultValue) { 3401 // reload page and navigation if the table has been renamed 3402 PMA_prepareForAjaxRequest($form); 3403 var tbl = $tblNameField.val(); 3404 $.post($form.attr('action'), $form.serialize(), function(data) { 3405 if (data.success == true) { 3406 PMA_commonParams.set('table', tbl); 3407 PMA_commonActions.refreshMain(false, function() { 3408 $('#page_content').html(data.message); 3409 }); 3410 } else { 3411 PMA_ajaxShowMessage(data.error, false); 3412 } 3413 }); // end $.post() 3414 } else { 3415 $form.removeClass('ajax').submit().addClass('ajax'); 3416 } 3417 }); 3418 3419 /** 3420 *Ajax events for actions in the "Table maintenance" 3421 **/ 3422 $("#tbl_maintenance li a.maintain_action.ajax").live('click', function(event) { 3423 event.preventDefault(); 3424 if ($("#sqlqueryresults").length != 0) { 3425 $("#sqlqueryresults").remove(); 3426 } 3427 if ($("#result_query").length != 0) { 3428 $("#result_query").remove(); 3429 } 3430 //variables which stores the common attributes 3431 $.post($(this).attr('href'), { ajax_request: 1 }, function(data) { 3432 function scrollToTop() { 3433 $('html, body').animate({ scrollTop: 0 }); 3434 } 3435 if (data.success == true && data.sql_query != undefined) { 3436 PMA_ajaxShowMessage(data.message); 3437 $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content"); 3438 $("#sqlqueryresults").html(data.sql_query); 3439 scrollToTop(); 3440 } else if (data.success == true) { 3441 var $temp_div = $("<div id='temp_div'></div>"); 3442 $temp_div.html(data.message); 3443 var $success = $temp_div.find("#result_query .success"); 3444 PMA_ajaxShowMessage($success); 3445 $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content"); 3446 $("#sqlqueryresults").html(data.message); 3447 PMA_init_slider(); 3448 $("#sqlqueryresults").children("fieldset,br").remove(); 3449 scrollToTop(); 3450 } else { 3451 var $temp_div = $("<div id='temp_div'></div>"); 3452 $temp_div.html(data.error); 3453 var $error = $temp_div.find("code").addClass("error"); 3454 PMA_ajaxShowMessage($error, false); 3455 } 3456 }); // end $.post() 3457 });
3457//end of table maintanance ajax click 3458}); //end $(document).ready for 'Table operations' 3459 3460/** 3461 * Unbind all event handlers before tearing down a page 3462 */ 3463AJAX.registerTeardown('functions.js', function() { 3464 $("#drop_db_anchor.ajax").die('click'); 3465}); 3466/** 3467 * Attach Ajax event handlers for Drop Database. Moved here from db_structure.js 3468 * as it was also required on db_create.php 3469 */ 3470AJAX.registerOnload('functions.js', function() { 3471 $("#drop_db_anchor.ajax").live('click', function(event) { 3472 event.preventDefault(); 3473 /** 3474 * @var question String containing the question to be asked for confirmation 3475 */ 3476 var question = PMA_messages.strDropDatabaseStrongWarning + ' '; 3477 question += $.sprintf( 3478 PMA_messages.strDoYouReally, 3479 'DROP DATABASE ' + escapeHtml(PMA_commonParams.get('db')) 3480 ); 3481 $(this).PMA_confirm(question, $(this).attr('href'), function(url) { 3482 PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 3483 $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) { 3484 if (data.success) { 3485 //Database deleted successfully, refresh both the frames 3486 PMA_reloadNavigation(); 3487 PMA_commonParams.set('db', ''); 3488 PMA_commonActions.refreshMain( 3489 'server_databases.php', 3490 function () { 3491 PMA_ajaxShowMessage(data.message); 3492 } 3493 ); 3494 } else { 3495 PMA_ajaxShowMessage(data.error, false); 3496 } 3497 }); 3498 }); 3499 }); 3500}); // end of $() for Drop Database 3501 3502/** 3503 * Validates the password field in a form 3504 * 3505 * @see PMA_messages['strPasswordEmpty'] 3506 * @see PMA_messages['strPasswordNotSame'] 3507 * @param object $the_form The form to be validated 3508 * @return bool 3509 */ 3510function PMA_checkPassword($the_form) 3511{ 3512 // Did the user select 'no password'? 3513 if ($the_form.find('#nopass_1').is(':checked')) { 3514 return true; 3515 } else { 3516 var $pred = $the_form.find('#select_pred_password'); 3517 if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) { 3518 return true; 3519 } 3520 } 3521 3522 var $password = $the_form.find('input[name=pma_pw]'); 3523 var $password_repeat = $the_form.find('input[name=pma_pw2]'); 3524 var alert_msg = false; 3525 3526 if ($password.val() == '') { 3527 alert_msg = PMA_messages['strPasswordEmpty']; 3528 } else if ($password.val() != $password_repeat.val()) { 3529 alert_msg = PMA_messages['strPasswordNotSame']; 3530 } 3531 3532 if (alert_msg) { 3533 alert(alert_msg); 3534 $password.val(''); 3535 $password_repeat.val(''); 3536 $password.focus(); 3537 return false; 3538 } 3539 return true; 3540} 3541 3542/** 3543 * Unbind all event handlers before tearing down a page 3544 */ 3545AJAX.registerTeardown('functions.js', function() { 3546 $('#change_password_anchor.ajax').die('click'); 3547}); 3548/** 3549 * Attach Ajax event handlers for 'Change Password' on index.php 3550 */ 3551AJAX.registerOnload('functions.js', function() { 3552 3553 /** 3554 * Attach Ajax event handler on the change password anchor 3555 */ 3556 $('#change_password_anchor.ajax').live('click', function(event) { 3557 event.preventDefault(); 3558 3559 var $msgbox = PMA_ajaxShowMessage(); 3560 3561 /** 3562 * @var button_options Object containing options to be passed to jQueryUI's dialog 3563 */ 3564 var button_options = {}; 3565 button_options[PMA_messages['strGo']] = function() { 3566 3567 event.preventDefault(); 3568 3569 /** 3570 * @var $the_form Object referring to the change password form 3571 */ 3572 var $the_form = $("#change_password_form"); 3573 3574 if (! PMA_checkPassword($the_form)) { 3575 return false; 3576 } 3577 3578 /** 3579 * @var this_value String containing the value of the submit button. 3580 * Need to append this for the change password form on Server Privileges 3581 * page to work 3582 */ 3583 var this_value = $(this).val(); 3584
3585 var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 3586 $the_form.append('<input type="hidden" name="ajax_request" value="true" />'); 3587 3588 $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw='+ this_value, function(data) { 3589 if (data.success == true) { 3590 $("#page_content").prepend(data.message); 3591 $("#change_password_dialog").hide().remove(); 3592 $("#edit_user_dialog").dialog("close").remove(); 3593 PMA_ajaxRemoveMessage($msgbox); 3594 } 3595 else { 3596 PMA_ajaxShowMessage(data.error, false); 3597 } 3598 }); // end $.post() 3599 }; 3600 3601 button_options[PMA_messages['strCancel']] = function() { 3602 $(this).dialog('close'); 3603 }; 3604 $.get($(this).attr('href'), {'ajax_request': true}, function(data) { 3605 if (data.success) { 3606 $('<div id="change_password_dialog"></div>') 3607 .dialog({ 3608 title: PMA_messages['strChangePassword'], 3609 width: 600, 3610 close: function(ev, ui) { 3611 $(this).remove(); 3612 }, 3613 buttons : button_options, 3614 modal: true 3615 }) 3616 .append(data.message); 3617 // for this dialog, we remove the fieldset wrapping due to double headings 3618 $("fieldset#fieldset_change_password") 3619 .find("legend").remove().end() 3620 .find("table.noclick").unwrap().addClass("some-margin") 3621 .find("input#text_pma_pw").focus(); 3622 displayPasswordGenerateButton(); 3623 $('#fieldset_change_password_footer').hide(); 3624 PMA_ajaxRemoveMessage($msgbox); 3625 $('#change_password_form').bind('submit', function (e) { 3626 e.preventDefault(); 3627 $(this) 3628 .closest('.ui-dialog') 3629 .find('.ui-dialog-buttonpane .ui-button') 3630 .first() 3631 .click(); 3632 }); 3633 } else { 3634 PMA_ajaxShowMessage(data.error, false); 3635 } 3636 }); // end $.get() 3637 }); // end handler for change password anchor 3638}); // end $() for Change Password 3639 3640/** 3641 * Unbind all event handlers before tearing down a page 3642 */ 3643AJAX.registerTeardown('functions.js', function() { 3644 $("select.column_type").die('change'); 3645 $("select.default_type").die('change'); 3646 $('input.allow_null').die('change'); 3647}); 3648/** 3649 * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when 3650 * the page loads and when the selected data type changes 3651 */ 3652AJAX.registerOnload('functions.js', function() { 3653 // is called here for normal page loads and also when opening 3654 // the Create table dialog 3655 PMA_verifyColumnsProperties(); 3656 // 3657 // needs live() to work also in the Create Table dialog 3658 $("select.column_type").live('change', function() { 3659 PMA_showNoticeForEnum($(this)); 3660 }); 3661 $("select.default_type").live('change', function() { 3662 PMA_hideShowDefaultValue($(this)); 3663 }); 3664 $('input.allow_null').live('change', function() { 3665 PMA_validateDefaultValue($(this)); 3666 }); 3667}); 3668 3669function PMA_verifyColumnsProperties() 3670{ 3671 $("select.column_type").each(function() { 3672 PMA_showNoticeForEnum($(this)); 3673 }); 3674 $("select.default_type").each(function() { 3675 PMA_hideShowDefaultValue($(this)); 3676 }); 3677} 3678 3679/** 3680 * Hides/shows the default value input field, depending on the default type 3681 * Ticks the NULL checkbox if NULL is chosen as default value. 3682 */ 3683function PMA_hideShowDefaultValue($default_type) 3684{ 3685 if ($default_type.val() == 'USER_DEFINED') { 3686 $default_type.siblings('.default_value').show().focus(); 3687 } else { 3688 $default_type.siblings('.default_value').hide(); 3689 if ($default_type.val() == 'NULL') { 3690 var $null_checkbox = $default_type.closest('tr').find('.allow_null'); 3691 $null_checkbox.prop('checked', true); 3692 } 3693 } 3694} 3695 3696/** 3697 * If the column does not allow NULL values, makes sure that default is not NULL 3698 */ 3699function PMA_validateDefaultValue($null_checkbox) 3700{ 3701 if (! $null_checkbox.prop('checked')) { 3702 var $default = $null_checkbox.closest('tr').find('.default_t
3702ype'); 3703 if ($default.val() == 'NULL') { 3704 $default.val('NONE'); 3705 } 3706 } 3707} 3708 3709 3710/** 3711 * Unbind all event handlers before tearing down a page 3712 */ 3713AJAX.registerTeardown('functions.js', function() { 3714 $("a.open_enum_editor").die('click'); 3715 $("input.add_value").die('click'); 3716 $("#enum_editor td.drop").die('click'); 3717}); 3718/** 3719 * @var $enum_editor_dialog An object that points to the jQuery 3720 * dialog of the ENUM/SET editor 3721 */ 3722var $enum_editor_dialog = null; 3723/** 3724 * Opens the ENUM/SET editor and controls its functions 3725 */ 3726AJAX.registerOnload('functions.js', function() { 3727 $("a.open_enum_editor").live('click', function() { 3728 // Get the name of the column that is being edited 3729 var colname = $(this).closest('tr').find('input:first').val(); 3730 // And use it to make up a title for the page 3731 if (colname.length < 1) { 3732 var title = PMA_messages['enum_newColumnVals']; 3733 } else { 3734 var title = PMA_messages['enum_columnVals'].replace( 3735 /%s/, 3736 '"' + escapeHtml(decodeURIComponent(colname)) + '"' 3737 ); 3738 } 3739 // Get the values as a string 3740 var inputstring = $(this) 3741 .closest('td') 3742 .find("input") 3743 .val(); 3744 // Escape html entities 3745 inputstring = $('<div/>') 3746 .text(inputstring) 3747 .html(); 3748 // Parse the values, escaping quotes and 3749 // slashes on the fly, into an array 3750 var values = []; 3751 var in_string = false; 3752 var curr, next, buffer = ''; 3753 for (var i=0; i<inputstring.length; i++) { 3754 curr = inputstring.charAt(i); 3755 next = i == inputstring.length ? '' : inputstring.charAt(i+1); 3756 if (! in_string && curr == "'") { 3757 in_string = true; 3758 } else if (in_string && curr == "\\" && next == "\\") { 3759 buffer += "\"; 3760 i++; 3761 } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) { 3762 buffer += "'"; 3763 i++; 3764 } else if (in_string && curr == "'") { 3765 in_string = false; 3766 values.push(buffer); 3767 buffer = ''; 3768 } else if (in_string) { 3769 buffer += curr; 3770 } 3771 } 3772 if (buffer.length > 0) { 3773 // The leftovers in the buffer are the last value (if any) 3774 values.push(buffer); 3775 } 3776 var fields = ''; 3777 // If there are no values, maybe the user is about to make a 3778 // new list so we add a few for him/her to get started with. 3779 if (values.length == 0) { 3780 values.push('','','',''); 3781 } 3782 // Add the parsed values to the editor 3783 var drop_icon = PMA_getImage('b_drop.png'); 3784 for (var i=0; i<values.length; i++) { 3785 fields += "<tr><td>" 3786 + "<input type='text' value='" + values[i] + "'/>" 3787 + "</td><td class='drop'>" 3788 + drop_icon 3789 + "</td></tr>"; 3790 } 3791 /** 3792 * @var dialog HTML code for the ENUM/SET dialog 3793 */ 3794 var dialog = "<div id='enum_editor'>" 3795 + "<fieldset>" 3796 + "<legend>" + title + "</legend>" 3797 + "<p>" + PMA_getImage('s_notice.png') 3798 + PMA_messages['enum_hint'] + "</p>" 3799 + "<table class='values'>" + fields + "</table>" 3800 + "</fieldset><fieldset class='tblFooters'>" 3801 + "<table class='add'><tr><td>" 3802 + "<div class='slider'></div>" 3803 + "</td><td>" 3804 + "<form><div><input type='submit' class='add_value' value='" 3805 + $.sprintf(PMA_messages['enum_addValue'], 1) 3806 + "'/></div></form>" 3807 + "</td></tr></table>" 3808 + "<input type='hidden' value='" // So we know which column's data is being edited 3809 + $(this).closest('td').find("input").attr("id") 3810 + "' />" 3811 + "</fieldset>" 3812 + "</div>"; 3813 /** 3814 * @var Defines functions to be called when the buttons in 3815 * the buttonOptions jQuery dialog bar are pressed 3816 */ 3817 var buttonOptions = {}; 3818 buttonOptions[PMA_messages['strGo']] = function () { 3819 // When the submit button is clicked, 3820 // put the data back into the original form 3821 var value_array = []; 3822 $(this).find(".values input").each(function(index, elm) { 3823 var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''"); 3824 value_array.push("'" + val + "'"); 3825 }); 3826 // get the Length/Values text field where this value belongs 3827 var values_id = $(this).find("input[type='hidden']").val(); 3828 $("input#" + values_id).val(value_array.join(",")); 3829 $(this).dialog("close"); 3830 }; 3831 buttonOptions[PMA_messages['strClose']] = function () { 3832 $(this).dialog("close"); 3833 }; 3834 // Show the dialog 3835 var width = parseInt( 3836 (parseInt($('html').css('font-size'), 10)/13)*340, 3837 10 3838 ); 3839 if (! width) { 3840 width = 340; 3841 } 3842 $enum_editor_dialog = $(dialog).dialog({ 3843 minWidth: width, 3844 modal: true, 3845 title: PMA_messages['enum_editor'], 3846 buttons: buttonOptions, 3847 open: function() { 3848 // Focus the "Go" button after opening the dialog 3849 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus(); 3850 }, 3851 close: function() { 3852 $(this).remove(); 3853 } 3854 }); 3855 // slider for choosing how many fields to add 3856 $enum_editor_dialog.find(".slider").slider({ 3857 animate: true, 3858 range: "min", 3859 value: 1, 3860 min: 1, 3861 max: 9, 3862 slide: function( event, ui ) { 3863 $(this).closest('table').find('input[type=submit]').val( 3864 $.sprintf(PMA_messages['enum_addValue'], ui.value) 3865 ); 3866 } 3867 }); 3868 // Focus the slider, otherwise it looks nearly transparent 3869 $('a.ui-slider-handle').addClass('ui-state-focus'); 3870 return false;
3871 }); 3872 3873 // When "add a new value" is clicked, append an empty text field 3874 $("input.add_value").live('click', function(e) { 3875 e.preventDefault(); 3876 var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value'); 3877 while (num_new_rows--) { 3878 $enum_editor_dialog.find('.values') 3879 .append( 3880 "<tr style='display: none;'><td>" 3881 + "<input type='text' />" 3882 + "</td><td class='drop'>" 3883 + PMA_getImage('b_drop.png') 3884 + "</td></tr>" 3885 ) 3886 .find('tr:last') 3887 .show('fast'); 3888 } 3889 }); 3890 3891 // Removes the specified row from the enum editor 3892 $("#enum_editor td.drop").live('click', function() { 3893 $(this).closest('tr').hide('fast', function () { 3894 $(this).remove(); 3895 }); 3896 }); 3897}); 3898 3899/** 3900 * Ensures indexes names are valid according to their type and, for a primary 3901 * key, lock index name to 'PRIMARY' 3902 * @param string form_id Variable which parses the form name as 3903 * the input 3904 * @return boolean false if there is no index form, true else 3905 */ 3906function checkIndexName(form_id) 3907{ 3908 if ($("#"+form_id).length == 0) { 3909 return false; 3910 } 3911 3912 // Gets the elements pointers 3913 var $the_idx_name = $("#input_index_name"); 3914 var $the_idx_type = $("#select_index_type"); 3915 3916 // Index is a primary key 3917 if ($the_idx_type.find("option:selected").val() == 'PRIMARY') { 3918 $the_idx_name.val('PRIMARY'); 3919 $the_idx_name.prop("disabled", true); 3920 } 3921 3922 // Other cases 3923 else { 3924 if ($the_idx_name.val() == 'PRIMARY') { 3925 $the_idx_name.val(""); 3926 } 3927 $the_idx_name.prop("disabled", false); 3928 } 3929 3930 return true; 3931} // end of the 'checkIndexName()' function 3932 3933AJAX.registerTeardown('functions.js', function() { 3934 $('#index_frm input[type=submit]').die('click'); 3935}); 3936AJAX.registerOnload('functions.js', function() { 3937 /** 3938 * Handler for adding more columns to an index in the editor 3939 */ 3940 $('#index_frm input[type=submit]').live('click', function(event) { 3941 event.preventDefault(); 3942 var rows_to_add = $(this) 3943 .closest('fieldset') 3944 .find('.slider') 3945 .slider('value'); 3946 while (rows_to_add--) { 3947 var $newrow = $('#index_columns') 3948 .find('tbody > tr:first') 3949 .clone() 3950 .appendTo( 3951 $('#index_columns').find('tbody') 3952 ); 3953 $newrow.find(':input').each(function() { 3954 $(this).val(''); 3955 }); 3956 // focus index size input on column picked 3957 $newrow.find('select').change(function() { 3958 if ($(this).find("option:selected").val() == '') { 3959 return true; 3960 } 3961 $(this).closest("tr").find("input").focus(); 3962 }); 3963 } 3964 }); 3965}); 3966 3967function indexEditorDialog(url, title, callback_success, callback_failure) 3968{ 3969 /*Remove the hidden dialogs if there are*/ 3970 if ($('#edit_index_dialog').length != 0) { 3971 $('#edit_index_dialog').remove(); 3972 } 3973 var $div = $('<div id="edit_index_dialog"></div>'); 3974 3975 /** 3976 * @var button_options Object that stores the options 3977 * passed to jQueryUI dialog 3978 */ 3979 var button_options = {}; 3980 button_options[PMA_messages['strGo']] = function() { 3981 /** 3982 * @var the_form object referring to the export form 3983 */ 3984 var $form = $("#index_frm"); 3985 var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 3986 PMA_prepareForAjaxRequest($form); 3987 //User wants to submit the form 3988 $.post($form.attr('action'), $form.serialize()+"&do_save_data=1", function(data) { 3989 if ($("#sqlqueryresults").length != 0) { 3990 $("#sqlqueryresults").remove(); 3991 } 3992 if (data.success == true) { 3993 PMA_ajaxShowMessage(data.message); 3994 if ($('#result_query').length) { 3995 $('#result_query').remove(); 3996 } 3997 if (data.sql_query) { 3998 $('<div id="result_query"></div>') 3999 .html(data.sql_query) 4000 .prependTo('#page_content'); 4001 } 4002 $("#result_query .notice").remove(); 4003 $("#result_query").prepend(data.message); 4004 /*Reload the field form*/ 4005 $("#table_index").remove(); 4006 var $temp_div = $("<div id='temp_div'><div>").append(data.index_table); 4007 $temp_div.find("#table_index").insertAfter("#index_header"); 4008 if ($("#edit_index_dialog").length > 0) { 4009 $("#edit_index_dialog").dialog("close"); 4010 } 4011 $('div.no_indexes_defined').hide(); 4012 if (callback_success) { 4013 callback_success(); 4014 } 4015 PMA_reloadNavigation(); 4016 } else { 4017 var $temp_div = $("<div id='temp_div'><div>").append(data.error); 4018 if ($temp_div.find(".error code").length != 0) { 4019 var $error = $temp_div.find(".error code").addClass("error"); 4020 } else { 4021 var $error = $temp_div; 4022 } 4023 if (callback_failure) { 4024 callback_failure(); 4025 } 4026 PMA_ajaxShowMessage($error, false); 4027 } 4028 }); // end $.post() 4029 }; 4030 button_options[PMA_messages['strCancel']] = function() { 4031 $(this).dialog('close'); 4032 };
4033 var $msgbox = PMA_ajaxShowMessage(); 4034 $.get("tbl_indexes.php", url, function(data) { 4035 if (data.success == false) { 4036 //in the case of an error, show the error message returned. 4037 PMA_ajaxShowMessage(data.error, false); 4038 } else { 4039 PMA_ajaxRemoveMessage($msgbox); 4040 // Show dialog if the request was successful 4041 $div 4042 .append(data.message) 4043 .dialog({ 4044 title: title, 4045 width: 450, 4046 open: PMA_verifyColumnsProperties, 4047 modal: true, 4048 buttons: button_options, 4049 close: function () { 4050 $(this).remove(); 4051 } 4052 }); 4053 checkIndexType(); 4054 checkIndexName("index_frm"); 4055 PMA_showHints($div); 4056 // Add a slider for selecting how many columns to add to the index 4057 $div.find('.slider').slider({ 4058 animate: true, 4059 value: 1, 4060 min: 1, 4061 max: 16, 4062 slide: function( event, ui ) { 4063 $(this).closest('fieldset').find('input[type=submit]').val( 4064 $.sprintf(PMA_messages['strAddToIndex'], ui.value) 4065 ); 4066 } 4067 }); 4068 // focus index size input on column picked 4069 $div.find('table#index_columns select').change(function() { 4070 if ($(this).find("option:selected").val() == '') { 4071 return true; 4072 } 4073 $(this).closest("tr").find("input").focus(); 4074 }); 4075 // Focus the slider, otherwise it looks nearly transparent 4076 $('a.ui-slider-handle').addClass('ui-state-focus'); 4077 // set focus on index name input, if empty 4078 var input = $div.find('input#input_index_name'); 4079 input.val() || input.focus(); 4080 } 4081 }); // end $.get() 4082} 4083 4084/** 4085 * Function to display tooltips that were 4086 * generated on the PHP side by PMA_Util::showHint() 4087 * 4088 * @param object $div a div jquery object which specifies the 4089 * domain for searching for tooltips. If we 4090 * omit this parameter the function searches 4091 * in the whole body 4092 **/ 4093function PMA_showHints($div) 4094{ 4095 if ($div == undefined || ! $div instanceof jQuery || $div.length == 0) { 4096 $div = $("body"); 4097 } 4098 $div.find('.pma_hint').each(function () { 4099 PMA_tooltip( 4100 $(this).children('img'), 4101 'img', 4102 $(this).children('span').html() 4103 ); 4104 }); 4105} 4106 4107AJAX.registerOnload('functions.js', function() { 4108 PMA_showHints(); 4109}); 4110 4111function PMA_mainMenuResizerCallback() { 4112 // 5 px margin for jumping menu in Chrome 4113 return $(document.body).width() - 5; 4114} 4115// This must be fired only once after the inital page load 4116$(function() { 4117 // Initialise the menu resize plugin 4118 $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); 4119 // register resize event 4120 $(window).resize(function (){ 4121 $('#topmenu').menuResizer('resize'); 4122 }); 4123}); 4124 4125/** 4126 * Get the row number from the classlist (for example, row_1) 4127 */ 4128function PMA_getRowNumber(classlist) 4129{ 4130 return parseInt(classlist.split(/\s+row_/)[1]); 4131} 4132 4133/** 4134 * Changes status of slider 4135 */ 4136function PMA_set_status_label($element) 4137{ 4138 var text = $element.css('display') == 'none' 4139 ? '+ ' 4140 : '- '; 4141 $element.closest('.slide-wrapper').prev().find('span').text(text); 4142} 4143 4144/** 4145 * var toggleButton This is a function that creates a toggle 4146 * sliding button given a jQuery reference 4147 * to the correct DOM element 4148 */ 4149var toggleButton = function ($obj) { 4150 // In rtl mode the toggle switch is flipped horizontally 4151 // so we need to take that into account 4152 if ($('span.text_direction', $obj).text() == 'ltr') { 4153 var right = 'right'; 4154 } else { 4155 var right = 'left'; 4156 } 4157 /** 4158 * var h Height of the button, used to scale the 4159 * background image and position the layers 4160 */ 4161 var h = $obj.height(); 4162 $('img', $obj).height(h); 4163 $('table', $obj).css('bottom', h-1); 4164 /** 4165 * var on Width of the "ON" part of the toggle switch 4166 * var off Width of the "OFF" part of the toggle switch 4167 */ 4168 var on = $('td.toggleOn', $obj).width(); 4169 var off = $('td.toggleOff', $obj).width(); 4170 // Make the "ON" and "OFF" parts of the switch the same size 4171 // + 2 pixels to avoid overflowed 4172 $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2); 4173 $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2); 4174 /** 4175 * var w Width of the central part of the switch 4176 */ 4177 var w = parseInt(($('img', $obj).height() / 16) * 22, 10); 4178 // Resize the central part of the switch on the top 4179 // layer to match the background 4180 $('table td:nth-child(2) > div', $obj).width(w); 4181 /** 4182 * var imgw Width of the background image 4183 * var tblw Width of the foreground layer 4184 * var offset By how many pixels to move the background 4185 * image, so that it matches the top layer 4186 */ 4187 var imgw = $('img', $obj).width(); 4188 var tblw = $('table', $obj).width(); 4189 var offset = parseInt(((imgw - tblw) / 2), 10); 4190 // Move the background to match the layout of the top layer 4191 $obj.find('img').css(right, offset); 4192 /** 4193 * var offw Outer width of the "ON" part of the toggle switch 4194 * var btnw Outer width of the central part of the switch 4195 */ 4196 var offw = $('td.toggleOff', $obj).outerWidth(); 4197 var btnw = $('table td:nth-child(2)', $obj).outerWidth(); 4198 // Resize the main div so that exactly one side of
4199 // the switch plus the central part fit into it. 4200 $obj.width(offw + btnw + 2); 4201 /** 4202 * var move How many pixels to move the 4203 * switch by when toggling 4204 */ 4205 var move = $('td.toggleOff', $obj).outerWidth(); 4206 // If the switch is initialized to the 4207 // OFF state we need to move it now. 4208 if ($('div.container', $obj).hasClass('off')) { 4209 if (right == 'right') { 4210 $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0); 4211 } else { 4212 $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0); 4213 } 4214 } 4215 // Attach an 'onclick' event to the switch 4216 $('div.container', $obj).click(function () { 4217 if ($(this).hasClass('isActive')) { 4218 return false; 4219 } else { 4220 $(this).addClass('isActive'); 4221 } 4222 var $msg = PMA_ajaxShowMessage(); 4223 var $container = $(this); 4224 var callback = $('span.callback', this).text(); 4225 // Perform the actual toggle 4226 if ($(this).hasClass('on')) { 4227 if (right == 'right') { 4228 var operator = '-='; 4229 } else { 4230 var operator = '+='; 4231 } 4232 var url = $(this).find('td.toggleOff > span').text(); 4233 var removeClass = 'on'; 4234 var addClass = 'off'; 4235 } else { 4236 if (right == 'right') { 4237 var operator = '+='; 4238 } else { 4239 var operator = '-='; 4240 } 4241 var url = $(this).find('td.toggleOn > span').text(); 4242 var removeClass = 'off'; 4243 var addClass = 'on'; 4244 } 4245 $.post(url, {'ajax_request': true}, function(data) { 4246 if (data.success == true) { 4247 PMA_ajaxRemoveMessage($msg); 4248 $container 4249 .removeClass(removeClass) 4250 .addClass(addClass) 4251 .animate({'left': operator + move + 'px'}, function () { 4252 $container.removeClass('isActive'); 4253 }); 4254 eval(callback); 4255 } else { 4256 PMA_ajaxShowMessage(data.error, false); 4257 $container.removeClass('isActive'); 4258 } 4259 }); 4260 }); 4261}; 4262 4263/** 4264 * Unbind all event handlers before tearing down a page 4265 */ 4266AJAX.registerTeardown('functions.js', function() { 4267 $('div.container').unbind('click'); 4268}); 4269/** 4270 * Initialise all toggle buttons 4271 */ 4272AJAX.registerOnload('functions.js', function () { 4273 $('div.toggleAjax').each(function () { 4274 var $button = $(this).show(); 4275 $button.find('img').each(function() { 4276 if (this.complete) { 4277 toggleButton($button); 4278 } else { 4279 $(this).load(function () { 4280 toggleButton($button); 4281 }); 4282 } 4283 }); 4284 }); 4285}); 4286 4287/** 4288 * Unbind all event handlers before tearing down a page 4289 */ 4290AJAX.registerTeardown('functions.js', function() { 4291 $('.vpointer').die('hover'); 4292 $('.vmarker').die('click'); 4293 $('#pageselector').die('change'); 4294 $('a.formLinkSubmit').die('click'); 4295 $('#update_recent_tables').unbind('ready'); 4296}); 4297/** 4298 * Vertical pointer 4299 */ 4300AJAX.registerOnload('functions.js', function() { 4301 $('.vpointer').live('hover', 4302 //handlerInOut 4303 function(e) { 4304 var $this_td = $(this); 4305 var row_num = PMA_getRowNumber($this_td.attr('class')); 4306 // for all td of the same vertical row, toggle hover 4307 $('.vpointer').filter('.row_' + row_num).toggleClass('hover'); 4308 } 4309 ); 4310 4311 4312 /** 4313 * Vertical marker 4314 */ 4315 $('.vmarker').live('click', function(e) { 4316 // do not trigger when clicked on anchor 4317 if ($(e.target).is('a, img, a *')) { 4318 return; 4319 } 4320 4321 var $this_td = $(this); 4322 var row_num = PMA_getRowNumber($this_td.attr('class')); 4323 4324 // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently 4325 var $checkbox = $('.vmarker').filter('.row_' + row_num + ':first').find(':checkbox'); 4326 if ($checkbox.length) { 4327 // checkbox in a row, add or remove class depending on checkbox state 4328 var checked = $checkbox.prop('checked'); 4329 if (!$(e.target).is(':checkbox, label')) { 4330 checked = !checked; 4331 $checkbox.prop('checked', checked); 4332 } 4333 // for all td of the same vertical row, toggle the marked class 4334 if (checked) { 4335 $('.vmarker').filter('.row_' + row_num).addClass('marked'); 4336 } else { 4337 $('.vmarker').filter('.row_' + row_num).removeClass('marked'); 4338 } 4339 } else { 4340 // normaln data table, just toggle class 4341 $('.vmarker').filter('.row_' + row_num).toggleClass('marked'); 4342 } 4343 }); 4344 4345 /** 4346 * Autosubmit page selector 4347 */ 4348 $('select.pageselector').live('change', function(event) { 4349 event.stopPropagation(); 4350 // Check where to load the new content 4351 if ($(this).closest("#pma_navigation").length == 0) { 4352 // For the main page we don't need to do anything, 4353 $(this).closest("form").submit(); 4354 } else { 4355 // but for the navigation we need to manually replace the content 4356 PMA_navigationTreePagination($(this)); 4357 } 4358 }); 4359 4360 /** 4361 * Load version information asynchronously. 4362 */ 4363 if ($('li.jsversioncheck').length > 0) { 4364 $.ajax({ 4365 dataType: "json", 4366 url: 'version_check.php', 4367 method: "POST", 4368 data: {
4369 "server": PMA_commonParams.get('server'), 4370 "token": PMA_commonParams.get('token'), 4371 }, 4372 success: PMA_current_version 4373 }); 4374 } 4375 4376 if ($('#is_git_revision').length > 0) { 4377 setTimeout(PMA_display_git_revision, 10); 4378 } 4379 4380 /** 4381 * Slider effect. 4382 */ 4383 PMA_init_slider(); 4384 4385 /** 4386 * Enables the text generated by PMA_Util::linkOrButton() to be clickable 4387 */ 4388 $('a.formLinkSubmit').live('click', function(e) { 4389 4390 if ($(this).attr('href').indexOf('=') != -1) { 4391 var data = $(this).attr('href').substr($(this).attr('href').indexOf('#')+1).split('=', 2); 4392 $(this).parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>'); 4393 } 4394 $(this).parents('form').submit(); 4395 return false; 4396 }); 4397 4398 if ($('#update_recent_tables').length) { 4399 $.get( 4400 $('#update_recent_tables').attr('href'), 4401 function (data) { 4402 if (data.success == true) { 4403 $('#recentTable').html(data.options); 4404 } 4405 } 4406 ); 4407 } 4408 4409}); // end of $() 4410 4411 4412/** 4413 * Initializes slider effect. 4414 */ 4415function PMA_init_slider() 4416{ 4417 $('div.pma_auto_slider').each(function() { 4418 var $this = $(this); 4419 if ($this.data('slider_init_done')) { 4420 return; 4421 } 4422 var $wrapper = $('<div>', {'class': 'slide-wrapper'}); 4423 $wrapper.toggle($this.is(':visible')); 4424 $('<a>', {href: '#'+this.id, "class":'ajax'}) 4425 .text(this.title) 4426 .prepend($('<span>')) 4427 .insertBefore($this) 4428 .click(function() { 4429 var $wrapper = $this.closest('.slide-wrapper'); 4430 var visible = $this.is(':visible'); 4431 if (!visible) { 4432 $wrapper.show(); 4433 } 4434 $this[visible ? 'hide' : 'show']('blind', function() { 4435 $wrapper.toggle(!visible); 4436 PMA_set_status_label($this); 4437 }); 4438 return false; 4439 }); 4440 $this.wrap($wrapper); 4441 PMA_set_status_label($this); 4442 $this.data('slider_init_done', 1); 4443 }); 4444} 4445 4446/** 4447 * Initializes slider effect. 4448 */ 4449AJAX.registerOnload('functions.js', function() { 4450 PMA_init_slider(); 4451}); 4452 4453/** 4454 * Restores sliders to the state they were in before initialisation. 4455 */ 4456AJAX.registerTeardown('functions.js', function() { 4457 $('div.pma_auto_slider').each(function() { 4458 var $this = $(this); 4459 $this.removeData(); 4460 $this.parent().replaceWith($this); 4461 $this.parent().children('a').remove(); 4462 }); 4463}); 4464 4465/** 4466 * Creates a message inside an object with a sliding effect 4467 * 4468 * @param msg A string containing the text to display 4469 * @param $obj a jQuery object containing the reference 4470 * to the element where to put the message 4471 * This is optional, if no element is 4472 * provided, one will be created below the 4473 * navigation links at the top of the page 4474 * 4475 * @return bool True on success, false on failure 4476 */ 4477function PMA_slidingMessage(msg, $obj) 4478{ 4479 if (msg == undefined || msg.length == 0) { 4480 // Don't show an empty message 4481 return false; 4482 } 4483 if ($obj == undefined || ! $obj instanceof jQuery || $obj.length == 0) { 4484 // If the second argument was not supplied, 4485 // we might have to create a new DOM node. 4486 if ($('#PMA_slidingMessage').length == 0) { 4487 $('#page_content').prepend( 4488 '<span id="PMA_slidingMessage" ' 4489 + 'style="display: inline-block;"></span>' 4490 ); 4491 } 4492 $obj = $('#PMA_slidingMessage'); 4493 } 4494 if ($obj.has('div').length > 0) { 4495 // If there already is a message inside the 4496 // target object, we must get rid of it 4497 $obj 4498 .find('div') 4499 .first() 4500 .fadeOut(function () { 4501 $obj 4502 .children() 4503 .remove(); 4504 $obj 4505 .append('<div style="display: none;">' + msg + '</div>') 4506 .animate({ 4507 height: $obj.find('div').first().height() 4508 }) 4509 .find('div') 4510 .first() 4511 .fadeIn(); 4512 }); 4513 } else { 4514 // Object does not already have a message 4515 // inside it, so we simply slide it down 4516 var h = $obj 4517 .width('100%') 4518 .html('<div style="display: none;">' + msg + '</div>') 4519 .find('div') 4520 .first() 4521 .height(); 4522 $obj 4523 .find('div') 4524 .first() 4525 .css('height', 0) 4526 .show() 4527 .animate({ 4528 height: h 4529 }, function() { 4530 // Set the height of the parent 4531 // to the height of the child 4532 $obj 4533 .height( 4534 $obj 4535 .find('div') 4536 .first() 4537 .height() 4538 ); 4539 }); 4540 } 4541 return true; 4542} // end PMA_slidingMessage() 4543 4544/** 4545 * Unbind all event handlers before tearing down a page 4546 */ 4547AJAX.registerTeardown('functions.js', function() { 4548 $("#drop_tbl_anchor.ajax").die('click'); 4549 $("#truncate_tbl_anchor.ajax").die('click'); 4550}); 4551/** 4552 * Attach Ajax event handlers for Drop Table. 4553 */ 4554AJAX.registerOnload('functions.js', function() { 4555 $("#drop_tbl_anchor.ajax").live('click', function(event) { 4556 event.preventDefault(); 4557 /** 4558 * @var question String containing the question to be asked for c
4558onfirmation 4559 */ 4560 var question = PMA_messages.strDropTableStrongWarning + ' '; 4561 question += $.sprintf( 4562 PMA_messages.strDoYouReally, 4563 'DROP TABLE ' + escapeHtml(PMA_commonParams.get('table')) 4564 ); 4565 4566 $(this).PMA_confirm(question, $(this).attr('href'), function(url) { 4567 4568 var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 4569 $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) { 4570 if (data.success == true) { 4571 PMA_ajaxRemoveMessage($msgbox); 4572 // Table deleted successfully, refresh both the frames 4573 PMA_reloadNavigation(); 4574 PMA_commonParams.set('table', ''); 4575 PMA_commonActions.refreshMain( 4576 PMA_commonParams.get('opendb_url'), 4577 function () { 4578 PMA_ajaxShowMessage(data.message); 4579 } 4580 ); 4581 } else { 4582 PMA_ajaxShowMessage(data.error, false); 4583 } 4584 }); // end $.get() 4585 }); // end $.PMA_confirm() 4586 }); //end of Drop Table Ajax action 4587 4588 $("#truncate_tbl_anchor.ajax").live('click', function(event) { 4589 event.preventDefault(); 4590 /** 4591 * @var question String containing the question to be asked for c
4591onfirmation 4592 */ 4593 var question = PMA_messages.strTruncateTableStrongWarning + ' '; 4594 question += $.sprintf( 4595 PMA_messages.strDoYouReally, 4596 'TRUNCATE ' + escapeHtml(PMA_commonParams.get('table')) 4597 ); 4598 $(this).PMA_confirm(question, $(this).attr('href'), function(url) { 4599 PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']); 4600 $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function(data) { 4601 if ($("#sqlqueryresults").length != 0) { 4602 $("#sqlqueryresults").remove(); 4603 } 4604 if ($("#result_query").length != 0) { 4605 $("#result_query").remove(); 4606 } 4607 if (data.success == true) { 4608 PMA_ajaxShowMessage(data.message); 4609 $("<div id='sqlqueryresults'></div>").prependTo("#page_content"); 4610 $("#sqlqueryresults").html(data.sql_query); 4611 } else { 4612 PMA_ajaxShowMessage(data.error, false); 4613 } 4614 }); // end $.get() 4615 }); // end $.PMA_confirm() 4616 }); //end of Truncate Table Ajax action 4617}); // end of $() for Truncate Table 4618 4619/** 4620 * Attach CodeMirror2 editor to SQL edit area. 4621 */ 4622AJAX.registerOnload('functions.js', function() { 4623 var $elm = $('#sqlquery'); 4624 if ($elm.length > 0) { 4625 if (typeof CodeMirror != 'undefined') { 4626 // for codemirror 4627 codemirror_editor = CodeMirror.fromTextArea($elm[0], { 4628 lineNumbers: true, 4629 matchBrackets: true, 4630 indentUnit: 4, 4631 mode: "text/x-mysql", 4632 lineWrapping: true 4633 }); 4634 codemirror_editor.focus(); 4635 $(codemirror_editor.getWrapperElement()).bind( 4636 'keydown', 4637 catchKeypressesFromSqlTextboxes 4638 ); 4639 } else { 4640 // without codemirror 4641 $elm.focus().bind('keydown', catchKeypressesFromSqlTextboxes); 4642 } 4643 } 4644}); 4645AJAX.registerTeardown('functions.js', function() { 4646 if (codemirror_editor) { 4647 $('#sqlquery').text(codemirror_editor.getValue()); 4648 codemirror_editor.toTextArea(); 4649 codemirror_editor = false; 4650 } 4651}); 4652 4653/** 4654 * jQuery plugin to cancel selection in HTML code. 4655 */ 4656(function ($) { 4657 $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas 4658 var prevent = (p == null) ? true : p; 4659 if (prevent) { 4660 return this.each(function () { 4661 if ($.browser.msie || $.browser.safari) { 4662 $(this).bind('selectstart', function () { 4663 return false; 4664 }); 4665 } else if ($.browser.mozilla) { 4666 $(this).css('MozUserSelect', 'none'); 4667 $('body').trigger('focus'); 4668 } else if ($.browser.opera) { 4669 $(this).bind('mousedown', function () { 4670 return false; 4671 }); 4672 } else { 4673 $(this).attr('unselectable', 'on'); 4674 } 4675 }); 4676 } else { 4677 return this.each(function () { 4678 if ($.browser.msie || $.browser.safari) { 4679 $(this).unbind('selectstart'); 4680 } else if ($.browser.mozilla) { 4681 $(this).css('MozUserSelect', 'inherit'); 4682 } else if ($.browser.opera) { 4683 $(this).unbind('mousedown'); 4684 } else { 4685 $(this).removeAttr('unselectable'); 4686 } 4687 }); 4688 } 4689 }; //end noSelect 4690})(jQuery); 4691 4692/** 4693 * jQuery plugin to correctly filter input fields by value, needed 4694 * because some nasty values may break selector syntax 4695 */ 4696(function ($) { 4697 $.fn.filterByValue = function (value) { 4698 return this.filter(function () { 4699 return $(this).val() === value; 4700 }); 4701 }; 4702})(jQuery); 4703 4704/** 4705 * Create a jQuery UI tooltip 4706 * 4707 * @param $elements jQuery object representing the elements 4708 * @param item the item 4709 * (see http://api.jqueryui.com/tooltip/#option-items) 4710 * @param myContent content of the tooltip 4711 * @param additionalOptions to override the default options 4712 * 4713 */ 4714function PMA_tooltip($elements, item, myContent, additionalOptions) 4715{ 4716 if ($('#no_hint').length > 0) { 4717 return; 4718 } 4719 4720 var defaultOptions = { 4721 content: myContent, 4722 items: item, 4723 tooltipClass: "tooltip", 4724 track: true, 4725 show: false, 4726 hide: false 4727 }; 4728 4729 $elements.tooltip($.extend(true, defaultOptions, additionalOptions)); 4730} 4731 4732/** 4733 * Return value of a cell in a table. 4734 */ 4735function PMA_getCellValue(td) { 4736 var $td = $(td); 4737 if ($td.is('.null')) { 4738 return ''; 4739 } else if (! $td.is('.to_be_saved') && $td.data('original_data')) { 4740 return $td.data('original_data'); 4741 } else { 4742 return $td.text(); 4743 } 4744} 4745 4746/** 4747 * Unbind all event handlers before tearing down a page 4748 */ 4749AJAX.registerTeardown('functions.js', function() { 4750 $('a.themeselect').die('click'); 4751 $('.autosubmit').die('change'); 4752 $('a.take_theme').unbind('click'); 4753}); 4754 4755AJAX.registerOnload('functions.js', function() { 4756 /** 4757 * Theme selector. 4758 */ 4759 $('a.themeselect').live('click', function(e) { 4760 window.open( 4761 e.target, 4762 'themes', 4763 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes' 4764 ); 4765 return false;
4766 }); 4767 4768 /** 4769 * Automatic form submission on change. 4770 */ 4771 $('.autosubmit').live('change', function(e) { 4772 $(this).closest('form').submit(); 4773 }); 4774 4775 /** 4776 * Theme changer. 4777 */ 4778 $('a.take_theme').click(function(e) { 4779 var what = this.name; 4780 if (window.opener && window.opener.document.forms['setTheme'].elements['set_theme']) { 4781 window.opener.document.forms['setTheme'].elements['set_theme'].value = what; 4782 window.opener.document.forms['setTheme'].submit(); 4783 window.close(); 4784 return false; 4785 } 4786 return true; 4787 }); 4788}); 4789 4790/** 4791 * Clear text selection 4792 */ 4793function PMA_clearSelection() { 4794 if (document.selection && document.selection.empty) { 4795 document.selection.empty(); 4796 } else if (window.getSelection) { 4797 var sel = window.getSelection(); 4798 if (sel.empty) { 4799 sel.empty(); 4800 } 4801 if (sel.removeAllRanges) { 4802 sel.removeAllRanges(); 4803 } 4804 } 4805} 4806 4807/** 4808 * HTML escaping 4809 */ 4810function escapeHtml(unsafe) { 4811 return unsafe 4812 .replace(/&/g, "&") 4813 .replace(/</g, "<") 4814 .replace(/>/g, ">") 4815 .replace(/"/g, """) 4816 .replace(/'/g, "'"); 4817} 4818 4819/** 4820 * Print button 4821 */ 4822function printPage() 4823{ 4824 // Do print the page 4825 if (typeof(window.print) != 'undefined') { 4826 window.print(); 4827 } 4828} 4829 4830/** 4831 * Unbind all event handlers before tearing down a page 4832 */ 4833AJAX.registerTeardown('functions.js', function() { 4834 $('input#print').unbind('click'); 4835 $('span a.create_view.ajax').die('click'); 4836 $('#createViewDialog').find('input, select').die('keydown'); 4837}); 4838 4839AJAX.registerOnload('functions.js', function() { 4840 $('input#print').click(printPage); 4841 /** 4842 * Ajaxification for the "Create View" action 4843 */ 4844 $('span a.create_view.ajax').live('click', function (e) { 4845 e.preventDefault(); 4846 PMA_createViewDialog($(this)); 4847 }); 4848 /** 4849 * Attach Ajax event handlers for input fields in the editor 4850 * and used to submit the Ajax request when the ENTER key is pressed. 4851 */ 4852 $('#createViewDialog').find('input, select').live('keydown', function (e) { 4853 if (e.which === 13) { // 13 is the ENTER key 4854 e.preventDefault(); 4855 $(this).closest('.ui-dialog').find('.ui-button:first').click(); 4856 } 4857 }); // end $.live() 4858}); 4859 4860function PMA_createViewDialog($this) 4861{ 4862 var $msg = PMA_ajaxShowMessage(); 4863 var syntaxHighlighter = null; 4864 $.get($this.attr('href') + '&ajax_request=1', function (data) { 4865 if (data.success == true) { 4866 PMA_ajaxRemoveMessage($msg); 4867 var buttonOptions = {}; 4868 buttonOptions[PMA_messages['strGo']] = function () { 4869 if (typeof CodeMirror !== 'undefined') { 4870 syntaxHighlighter.save(); 4871 } 4872 $msg = PMA_ajaxShowMessage(); 4873 $.get('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) { 4874 PMA_ajaxRemoveMessage($msg); 4875 if (data.success === true) { 4876 $('#createViewDialog').dialog("close"); 4877 $('#result_query').html(data.message); 4878 PMA_reloadNavigation(); 4879 } else { 4880 PMA_ajaxShowMessage(data.error, false); 4881 } 4882 }); 4883 }; 4884 buttonOptions[PMA_messages['strClose']] = function () { 4885 $(this).dialog("close"); 4886 }; 4887 var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({ 4888 width: 500, 4889 minWidth: 300, 4890 maxWidth: 620, 4891 modal: true, 4892 buttons: buttonOptions, 4893 title: $('legend', $(data.message)).html(), 4894 close: function () { 4895 $(this).remove(); 4896 } 4897 }); 4898 $dialog.find('legend').remove(); 4899 // Attach syntax highlited editor 4900 if (typeof CodeMirror !== 'undefined') { 4901 var $elm = $dialog.find('textarea'); 4902 var opts = {lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql"}; 4903 syntaxHighlighter = CodeMirror.fromTextArea($elm[0], opts); 4904 } 4905 $('input:visible[type=text]', $dialog).first().focus(); 4906 } else { 4907 PMA_ajaxShowMessage(data.error); 4908 } 4909 }); 4910} 4911 4912/** 4913 * Makes the breadcrumbs and the menu bar float at the top of the viewport 4914 */ 4915$(function () { 4916 if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length == 0) { 4917 var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right'; 4918 $("#floating_menubar") 4919 .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width()) 4920 .css(left, 0) 4921 .css({ 4922 'position': 'fixed', 4923 'top': 0, 4924 'width': '100%', 4925 'z-index': 500 4926 }) 4927 .append($('#serverinfo')) 4928 .append($('#topmenucontainer')); 4929 // Allow the DOM to render, then adjust the padding on the body 4930 setTimeout(function () { 4931 $('body').css( 4932 'padding-top', 4933 $('#floating_menubar').outerHeight(true) 4934 ); 4935 $('#topmenu').menuResizer('resize'); 4936 }, 4); 4937 } 4938}); 4939 4940/** 4941 * Scrolls the page to the top if clicking the serverinfo bar 4942 */ 4943$(function () { 4944 $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) { 4945 event.preventDefault(); 4946 $('html, body').animate({scrollTop: 0}, 'fast'); 4947 }); 4948}); 4949 4950/** 4951 * Watches checkboxes in a form to set the checkall box accordingly 4952 */ 4953var checkboxes_sel = "input.checkall:checkbox:enabled"; 4954$(checkboxes_sel).live("change", function () { 4955 var $form = $(this.form); 4956 // total number of checkboxes in current form 4957 var total_boxes = $form.find(checkboxes_sel).length; 4958 // number of checkboxes checked in current form 4959 var checked_boxes = $form.find(checkboxes_sel + ":checked").length; 4960 var $checkall = $form.find("input#checkall"); 4961 if (total_boxes == checked_boxes) { 4962 $checkall.prop({checked: true, indeterminate: false}); 4963 } 4964 else if (checked_boxes > 0) { 4965 $checkall.prop({checked: true, indeterminate: true}); 4966 } 4967 else {
4968 $checkall.prop({checked: false, indeterminate: false}); 4969 } 4970}); 4971$("input#checkall").live("change", function() { 4972 var is_checked = $(this).is(":checked"); 4973 $(this.form).find(checkboxes_sel).prop("checked", is_checked) 4974 .parents("tr").toggleClass("marked", is_checked); 4975}); 4976 4977/** 4978 * Toggles row colors of a set of 'tr' elements starting from a given element 4979 * 4980 * @param $start Starting element 4981 */ 4982function toggleRowColors($start) 4983{ 4984 for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) { 4985 if ($curr_row.hasClass('odd')) { 4986 $curr_row.removeClass('odd').addClass('even'); 4987 } else if ($curr_row.hasClass('even')) { 4988 $curr_row.removeClass('even').addClass('odd'); 4989 } 4990 } 4991} 4992 4993/** 4994 * Formats a byte number to human-readable form 4995 * 4996 * @param bytes the bytes to format 4997 * @param optional subdecimals the number of digits after the point 4998 * @param optional pointchar the char to use as decimal point 4999 */ 5000function formatBytes(bytes, subdecimals, pointchar) { 5001 if (!subdecimals) { 5002 var subdecimals = 0; 5003 } 5004 if (!pointchar) { 5005 var pointchar = '.'; 5006 } 5007 var units = ['B', 'KiB', 'MiB', 'GiB']; 5008 for (var i = 0; bytes > 1024 && i < units.length; i++) { 5009 bytes /= 1024; 5010 } 5011 var factor = Math.pow(10, subdecimals); 5012 bytes = Math.round(bytes * factor) / factor; 5013 bytes = bytes.toString().split('.').join(pointchar); 5014 return bytes + ' ' + units[i]; 5015} 5016 5017AJAX.registerOnload('functions.js', function () { 5018 /** 5019 * Opens pma more themes link in themes browser, in new window instead of popup 5020 * This way, we don't break HTML validity 5021 */ 5022 $("a._blank").prop("target", "_blank"); 5023 /** 5024 * Reveal the login form to users with JS enabled 5025 * and focus the appropriate input field 5026 */ 5027 var $loginform = $('#loginform'); 5028 if ($loginform.length) { 5029 $loginform.find('.js-show').show(); 5030 if ($('#input_username').val()) { 5031 $('#input_password').focus(); 5032 } else { 5033 $('#input_username').focus(); 5034 } 5035 } 5036}); 5037 5038/** 5039 * When user gets an ajax session expiry message, we show a login link 5040 */ 5041$('a.login-link').live('click', function(e) { 5042 e.preventDefault(); 5043 window.location.reload(true); 5044}); 5045 5046/** 5047 * Formats timestamp for display 5048 */ 5049function PMA_formatDateTime(date, seconds) { 5050 var result = $.datepicker.formatDate('yy-mm-dd', date); 5051 var timefmt = 'HH:mm'; 5052 if (seconds) { 5053 timefmt = 'HH:mm:ss'; 5054 } 5055 return result + ' ' + $.datepicker.formatTime( 5056 timefmt, { 5057 hour: date.getHours(), 5058 minute: date.getMinutes(), 5059 second: date.getSeconds() 5060 } 5061 ); 5062} 5063; 5064 5065/* vim: set expandtab sw=4 ts=4 sts=4: */ 5066/** 5067 * function used in or for navigation panel 5068 * 5069 * @package phpMyAdmin-Navigation 5070 */ 5071 5072/** 5073 * Executed on page load 5074 */ 5075$(function() { 5076 if (! $('#pma_navigation').length) { 5077 // Don't bother running any code if the navigation is not even on the page 5078 return; 5079 } 5080 5081 // Do not let the page reload on submitting the fast filter 5082 $(document).on('submit', '.fast_filter', function(event) { 5083 event.preventDefault(); 5084 }); 5085 5086 // Fire up the resize handlers 5087 new ResizeHandler(); 5088 5089 /** 5090 * opens/closes (hides/shows) tree elements 5091 * loads data via ajax 5092 */ 5093 $('#pma_navigation_tree a.expander').live('click', function(event) { 5094 event.preventDefault(); 5095 event.stopImmediatePropagation(); 5096 var $icon = $(this).find('img'); 5097 if ($icon.is('.ic_b_plus')) { 5098 expandTreeNode($(this)); 5099 } else { 5100 collapseTreeNode($(this)); 5101 } 5102 }); 5103 5104 /** 5105 * Register event handler for click on the reload 5106 * navigation icon at the top of the panel 5107 */ 5108 $('#pma_navigation_reload').live('click', function (event) { 5109 event.preventDefault(); 5110 $('#pma_navigation .throbber') 5111 .first() 5112 .css('visibility', 'visible'); 5113 PMA_reloadNavigation(); 5114 }); 5115 5116 /** 5117 * Bind all "fast filter" events 5118 */ 5119 $('#pma_navigation_tree li.fast_filter span') 5120 .live('click', PMA_fastFilter.events.clear); 5121 $('#pma_navigation_tree li.fast_filter input.searchClause') 5122 .live('focus', PMA_fastFilter.events.focus) 5123 .live('blur', PMA_fastFilter.events.blur) 5124 .live('keyup', PMA_fastFilter.events.keyup); 5125 5126 /** 5127 * Ajax handler for pagination 5128 */ 5129 $('#pma_navigation_tree div.pageselector a.ajax').live('click', function (event) { 5130 event.preventDefault(); 5131 PMA_navigationTreePagination($(this)); 5132 }); 5133 5134 /** 5135 * Node highlighting 5136 */ 5137 $('#pma_navigation_tree.highlight li:not(.fast_filter)').live( 5138 'mouseover', 5139 function () { 5140 if ($('li:visible', this).length == 0) { 5141 $(this).addClass('activePointer'); 5142 } 5143 } 5144 ); 5145 $('#pma_navigation_tree.highlight li:not(.fast_filter)').live( 5146 'mouseout', 5147 function () { 5148 $(this).removeClass('activePointer'); 5149 } 5150 ); 5151 5152 /** 5153 * Jump to recent table 5154 */ 5155 $('#recentTable').live('change', function() { 5156 if (this.value != '') { 5157 var arr = jQuery.parseJSON(this.value); 5158 var $form = $(this).closest('form'); 5159 $form.find('input[name=db]').val(arr['db']); 5160 $form.find('input[name=table]').val(arr['table']); 5161 $form.submit(); 5162 } 5163 }); 5164 5165 /** Create a Routine, Trigger or Event */ 5166 $('li.new_procedure a.ajax, li.new_function a.ajax').live('click', function (event) { 5167 event.preventDefault(); 5168 var dialog = new RTE.object('routine'); 5169 dialog.editorDialog(1, $(this)) 5170 }); 5171 $('li.new_trigger a.ajax').live('click', function (event) { 5172 event.preventDefault(); 5173 var dialog = new RTE.object('trigger'); 5174 dialog.editorDialog(1, $(this)) 5175 }); 5176 $('li.new_event a.ajax').live('click', function (event) { 5177 event.preventDefault(); 5178 var dialog = new RTE.object('event'); 5179 dialog.editorDialog(1, $(this)) 5180 }); 5181 5182 /** Edit Routines, Triggers and Events */ 5183 $('li.procedure > a.ajax, li.function > a.ajax').live('click', function (event) { 5184 event.preventDefault(); 5185 var dialog = new RTE.object('routine'); 5186 dialog.editorDialog(0, $(this)) 5187 }); 5188 $('li.trigger > a.ajax').live('click', function (event) { 5189 event.preventDefault(); 5190 var dialog = new RTE.object('trigger'); 5191 dialog.editorDialog(0, $(this)) 5192 }); 5193 $('li.event > a.ajax').live('click', function (event) { 5194 event.preventDefault(); 5195 var dialog = new RTE.object('event'); 5196 dialog.editorDialog(0, $(this)) 5197 }); 5198 5199 /** Export Routines, Triggers and Events */ 5200 $('li.procedure a.ajax img, li.function a.ajax img, li.trigger a.ajax img, li.event a.ajax img').live('click', function (event) { 5201 event.preventDefault(); 5202 var dialog = new RTE.object(); 5203 dialog.exportDialog($(this).parent()) 5204 }); 5205 5206 /** New index */ 5207 $('li.new_index a.ajax').live('click', function (event) { 5208 event.preventDefault(); 5209 var url = $(this).attr('href').substr( 5210 $(this).attr('href').indexOf('?') + 1 5211 ) + '&ajax_request=true'; 5212 var title = PMA_messages['strAddIndex']; 5213 indexEditorDialog(url, title); 5214 }); 5215 5216 /** Edit index */ 5217 $('li.index a.ajax').live('click', function (event) { 5218 event.preventDefault(); 5219 var url = $(this).attr('href').substr( 5220 $(this).attr('href').indexOf('?') + 1 5221 ) + '&ajax_request=true'; 5222 var title = PMA_messages['strEditIndex']; 5223 indexEditorDialog(url, title); 5224 }); 5225 5226 /** New view */ 5227 $('li.new_view a.ajax').live('click', function (event) { 5228 event.preventDefault(); 5229 PMA_createViewDialog($(this)); 5230 }); 5231 5232 PMA_showCurrentNavigation(); 5233}); 5234 5235/**
5236 * Expands a node in navigation tree. 5237 * 5238 * @param $expandElem expander 5239 * @param callback callback function 5240 * 5241 * @returns void 5242 */ 5243function expandTreeNode($expandElem, callback) 5244{ 5245 var $children = $expandElem.closest('li').children('div.list_container'); 5246 var $icon = $expandElem.find('img'); 5247 if ($expandElem.hasClass('loaded')) { 5248 if ($icon.is('.ic_b_plus')) { 5249 $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); 5250 $children.show('fast'); 5251 } 5252 if (callback && typeof callback == 'function') { 5253 callback.call(); 5254 } 5255 } else { 5256 var $throbber = $('#pma_navigation .throbber') 5257 .first() 5258 .clone() 5259 .css('visibility', 'visible') 5260 .click(false); 5261 $icon.hide(); 5262 $throbber.insertBefore($icon); 5263 5264 loadChildNodes($expandElem, function(data) { 5265 if (data.success === true) { 5266 var $destination = $expandElem.closest('li'); 5267 $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); 5268 $destination 5269 .children('div.list_container') 5270 .show('fast'); 5271 if ($destination.find('ul > li').length == 1) { 5272 $destination.find('ul > li') 5273 .find('a.expander.container') 5274 .click(); 5275 } 5276 if (callback && typeof callback == 'function') { 5277 callback.call(); 5278 } 5279 } else { 5280 PMA_ajaxShowMessage(data.error, false); 5281 } 5282 $icon.show(); 5283 $throbber.remove(); 5284 }); 5285 } 5286 $expandElem.blur(); 5287} 5288 5289/** 5290 * Auto-scrolls the newly chosen database 5291 * 5292 * @param object $element The element to set to view 5293 * @param object $container The container srollable element 5294 * 5295 */ 5296function scrollToView($element, $container) { 5297 var elementOffset = $element.offset(), 5298 containerOffset = $container.offset(); 5299 if(elementOffset != undefined && containerOffset != undefined) { 5300 var pushToOffset = elementOffset.top - containerOffset.top + $container.scrollTop(); 5301 $('#pma_navigation_tree_content').stop().animate({ 5302 scrollTop: pushToOffset 5303 }); 5304 } 5305} 5306 5307/** 5308 * Collapses a node in navigation tree. 5309 * 5310 * @param $expandElem expander 5311 * 5312 * @returns void 5313 */ 5314function collapseTreeNode($expandElem) { 5315 var $children = $expandElem.closest('li').children('div.list_container'); 5316 var $icon = $expandElem.find('img'); 5317 if ($expandElem.hasClass('loaded')) { 5318 if ($icon.is('.ic_b_minus')) { 5319 $icon.removeClass('ic_b_minus').addClass('ic_b_plus'); 5320 $children.hide('fast'); 5321 } 5322 } 5323 $expandElem.blur(); 5324} 5325 5326/** 5327 * Loads child items of a node and executes a given callback 5328 * 5329 * @param $expandElem expander 5330 * @param callback callback function 5331 * 5332 * @returns void 5333 */ 5334function loadChildNodes($expandElem, callback) { 5335 var $destination = $expandElem.closest('li'); 5336 5337 var searchClause = PMA_fastFilter.getSearchClause(); 5338 var searchClause2 = PMA_fastFilter.getSearchClause2($expandElem); 5339 5340 var params = { 5341 aPath: $expandElem.find('span.aPath').text(), 5342 vPath: $expandElem.find('span.vPath').text(), 5343 pos: $expandElem.find('span.pos').text(), 5344 pos2_name: $expandElem.find('span.pos2_name').text(), 5345 pos2_value: $expandElem.find('span.pos2_value').text(), 5346 searchClause: searchClause, 5347 searchClause2: searchClause2 5348 }; 5349 5350 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 5351 $.get(url, params, function (data) { 5352 if (data.success === true) { 5353 $expandElem.addClass('loaded'); 5354 $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there 5355 $destination.append(data.message); 5356 if (callback && typeof callback == 'function') { 5357 callback(data); 5358 } 5359 } 5360 }); 5361} 5362 5363/** 5364 * Expand the navigation and highlight the current database or table/view 5365 * 5366 * @returns void 5367 */ 5368function PMA_showCurrentNavigation() 5369{ 5370 var db = PMA_commonParams.get('db'); 5371 var table = PMA_commonParams.get('table'); 5372 $('#pma_navigation_tree') 5373 .find('li.selected') 5374 .removeClass('selected'); 5375 if (db && table) { // if we are at the table/view level 5376 // open the database in the tree 5377 var $dbItem = highlightLoadedItem( 5378 $('#pma_navigation_tree > div'), db, 'database', false, false 5379 ); 5380 if ($dbItem) { 5381 // open the table in the tree and select it 5382 var $expander = $dbItem.children('div:first').children('a.expander'); 5383 // if not loaded or loaded but collapsed 5384 if (! $expander.hasClass('loaded') 5385 || $expander.find('img').is('.ic_b_plus') 5386 ) { 5387 expandTreeNode($expander, function() { 5388 loadAndHighlightTableOrView($dbItem, table); 5389 }); 5390 } else { 5391 loadAndHighlightTableOrView($dbItem, table); 5392 } 5393 } 5394 } else if (db) { // if we are at the database level 5395 // open in the tree and select the database 5396 highlightLoadedItem( 5397 $('#pma_navigation_tree > div'), db, 'database', true, true 5398 ); 5399 } 5400 5401 function highlightLoadedItem($container, name, clazz, doSelect, doOpen) { 5402 var ret = false;
5403 $container.children('ul').children('li').each(function() { 5404 var $li = $(this); 5405 // this is a navigation group, recurse 5406 if ($li.is('.navGroup')) { 5407 var $container = $li.children('div.list_container'); 5408 var $childRet = highlightLoadedItem( 5409 $container, name, clazz, doSelect, doOpen 5410 ); 5411 if ($childRet) { 5412 ret = $childRet; 5413 return false; 5414 } 5415 } else { // this is a real navigation item 5416 // name and class matches 5417 if ($li.is('.' + clazz) && $li.children('a').text() == name) { 5418 if (doSelect) { 5419 $li.addClass('selected'); 5420 if (! doOpen) { // if the node will be opened no point scrolling now 5421 scrollToView($li, $('#pma_navigation_tree_content')); 5422 } 5423 } 5424 if (doOpen) { 5425 var $expander = $li.find('div:first').children('a.expander'); 5426 if ($expander.length > 0) { 5427 expandTreeNode($expander, function() { 5428 scrollToView($li, $('#pma_navigation_tree_content')); 5429 }); 5430 } 5431 } 5432 // taverse up and expand and parent navigation groups 5433 $li.parents('.navGroup').each(function() { 5434 $cont = $(this).children('div.list_container'); 5435 if (! $cont.is(':visible')) { 5436 $(this) 5437 .children('div:first') 5438 .children('a.expander') 5439 .click(); 5440 } 5441 }); 5442 ret = $li; 5443 return false; 5444 } 5445 } 5446 }); 5447 return ret; 5448 } 5449 5450 function loadAndHighlightTableOrView($dbItem, table) { 5451 var $container = $dbItem.children('div.list_container'); 5452 var $tableContainer = $container 5453 .children('ul') 5454 .children('li.tableContainer'); 5455 var $viewContainer = $container 5456 .children('ul') 5457 .children('li.viewContainer'); 5458 5459 if ($tableContainer.length > 0) { 5460 var $expander = $tableContainer 5461 .children('div:first') 5462 .children('a.expander'); 5463 5464 if (! $expander.hasClass('loaded') ) { 5465 loadChildNodes($expander, function(data) { 5466 highlightTableOrView($tableContainer, $viewContainer, table); 5467 }); 5468 } else { 5469 highlightTableOrView($tableContainer, $viewContainer, table); 5470 } 5471 } else if ($viewContainer.length > 0) { 5472 highlightView($viewContainer, table); 5473 } else { 5474 // no containers, highlight the item 5475 highlightLoadedItem($container, table, 'table', true, false); 5476 } 5477 } 5478 5479 function highlightTableOrView($tableContainer, $viewContainer, table) 5480 { 5481 if (isItemInContainer($tableContainer, table, 'table')) { 5482 var $expander = $tableContainer 5483 .children('div:first') 5484 .children('a.expander'); 5485 if ($expander.find('img').is('.ic_b_plus')) { 5486 expandTreeNode($expander); 5487 } 5488 highlightLoadedItem( 5489 $tableContainer.children('div.list_container'), 5490 table, 'table', true, false 5491 ); 5492 } else if ($viewContainer.length > 0) { 5493 highlightView($viewContainer, table); 5494 } 5495 } 5496 5497 function isItemInContainer($container, name, clazz) 5498 { 5499 $items = $container.find('li.' + clazz); 5500 var found = false; 5501 $items.each(function() { 5502 if ($(this).children('a').text() == name) { 5503 found = true; 5504 return false; 5505 } 5506 }); 5507 return found; 5508 } 5509 5510 function highlightView($viewContainer, view) { 5511 var $expander = $viewContainer 5512 .children('div:first') 5513 .children('a.expander'); 5514 if (! $expander.hasClass('loaded') 5515 || $expander.find('img').is('.ic_b_plus') 5516 ) { 5517 expandTreeNode($expander, function() { 5518 highlightLoadedItem( 5519 $viewContainer.children('div.list_container'), 5520 view, 'view', true, false 5521 ); 5522 }); 5523 } else { 5524 highlightLoadedItem( 5525 $viewContainer.children('div.list_container'), 5526 view, 'view', true, false 5527 ); 5528 } 5529 } 5530} 5531 5532/** 5533 * Reloads the whole navigation tree while preserving its state 5534 * 5535 * @param function the callback function 5536 * @return void 5537 */ 5538function PMA_reloadNavigation(callback) { 5539 var params = { 5540 reload: true, 5541 pos: $('#pma_navigation_tree').find('a.expander:first > span.pos').text() 5542 }; 5543 // Traverse the navigation tree backwards to generate all the actual 5544 // and virtual paths, as well as the positions in the pagination at 5545 // various levels, if necessary. 5546 var count = 0; 5547 $('#pma_navigation_tree').find('a.expander:visible').each(function () { 5548 if ($(this).find('img').is('.ic_b_minus') 5549 && $(this).closest('li').find('div.list_container .ic_b_minus').length == 0 5550 ) { 5551 params['n' + count + '_aPath'] = $(this).find('span.aPath').text(); 5552 params['n' + count + '_vPath'] = $(this).find('span.vPath').text(); 5553 5554 var pos2_name = $(this).find('span.pos2_name').text(); 5555 if (! pos2_name) { 5556 pos2_name = $(this) 5557 .parent() 5558 .parent() 5559 .find('span.pos2_name:last') 5560 .text(); 5561 } 5562 var pos2_value = $(this).find('span.pos2_value').text(); 5563 if (! pos2_value) { 5564 pos2_value = $(this) 5565 .parent() 5566 .parent() 5567 .find('span.pos2_value:last') 5568 .text(); 5569 } 5570 5571 params['n' + count + '_pos2_name'] = pos2_name; 5572 params['n' + count + '_pos2_value'] = pos2_value; 5573 5574 params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text(); 5575 params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text(); 5576 count++; 5577 } 5578 }); 5579 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 5580 $.post(url, params, function (data) { 5581 // Hide throbber if it's visible 5582 $('#pma_navigation .throbber') 5583 .first() 5584 .css('visibility', 'hidden'); 5585 if (data.success) { 5586 $('#pma_navigation_tree').html(data.message).children('div').show(); 5587 PMA_showCurrentNavigation(); 5588 // Fire the callback, if any 5589 if (typeof callback === 'function') { 5590 callback.call(); 5591 } 5592 } else { 5593 PMA_ajaxShowMessage(data.error); 5594 } 5595 }); 5596} 5597 5598/** 5599 * Handles any requests to change the page in a branch of a tree 5600 * 5601 * This can be called from link click or select change event handlers 5602 * 5603 * @param object $this A jQuery object that points to the element that 5604 * initiated the action of changing the page 5605 * 5606 * @return void 5607 */ 5608function PMA_navigationTreePagination($this) 5609{
5610 var $msgbox = PMA_ajaxShowMessage(); 5611 var isDbSelector = $this.closest('div.pageselector').is('.dbselector'); 5612 if ($this[0].tagName == 'A') { 5613 var url = $this.attr('href'); 5614 var params = 'ajax_request=true'; 5615 } else { // tagName == 'SELECT' 5616 var url = 'navigation.php'; 5617 var params = $this.closest("form").serialize() + '&ajax_request=true'; 5618 } 5619 var searchClause = PMA_fastFilter.getSearchClause(); 5620 if (searchClause) { 5621 params += '&searchClause=' + encodeURIComponent(searchClause); 5622 } 5623 if (isDbSelector) { 5624 params += '&full=true'; 5625 } else { 5626 var searchClause2 = PMA_fastFilter.getSearchClause2($this); 5627 if (searchClause2) { 5628 params += '&searchClause2=' + encodeURIComponent(searchClause2); 5629 } 5630 } 5631 $.post(url, params, function (data) { 5632 PMA_ajaxRemoveMessage($msgbox); 5633 if (data.success) { 5634 if (isDbSelector) { 5635 var val = PMA_fastFilter.getSearchClause(); 5636 $('#pma_navigation_tree') 5637 .html(data.message) 5638 .children('div') 5639 .show(); 5640 if (val) { 5641 $('#pma_navigation_tree') 5642 .find('li.fast_filter input.searchClause') 5643 .val(val); 5644 } 5645 } else { 5646 var $parent = $this.closest('div.list_container').parent(); 5647 var val = PMA_fastFilter.getSearchClause2($this); 5648 $this.closest('div.list_container').html( 5649 $(data.message).children().show() 5650 ); 5651 if (val) { 5652 $parent.find('li.fast_filter input.searchClause').val(val); 5653 } 5654 $parent.find('span.pos2_value:first').text( 5655 $parent.find('span.pos2_value:last').text() 5656 ); 5657 $parent.find('span.pos3_value:first').text( 5658 $parent.find('span.pos3_value:last').text() 5659 ); 5660 } 5661 } else { 5662 PMA_ajaxShowMessage(data.error); 5663 } 5664 }); 5665} 5666 5667/** 5668 * @var ResizeHandler Custom object that manages the resizing of the navigation 5669 * 5670 * XXX: Must only be ever instanciated once 5671 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler' 5672 */ 5673var ResizeHandler = function () { 5674 /** 5675 * Whether the user has initiated a resize operation 5676 */ 5677 this.active = false; 5678 /** 5679 * @var int panel_width Used by the collapser to know where to go 5680 * back to when uncollapsing the panel 5681 */ 5682 this.panel_width = 0; 5683 /** 5684 * @var string left Used to provide support for RTL languages 5685 */ 5686 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right'; 5687 /** 5688 * Adjusts the width of the navigation panel to the specified value 5689 * 5690 * @param int pos Navigation width in pixels 5691 * 5692 * @return void 5693 */ 5694 this.setWidth = function (pos) { 5695 var $resizer = $('#pma_navigation_resizer'); 5696 var resizer_width = $resizer.width(); 5697 var $collapser = $('#pma_navigation_collapser'); 5698 $('#pma_navigation').width(pos); 5699 $('body').css('margin-' + this.left, pos + 'px'); 5700 $("#floating_menubar") 5701 .css('margin-' + this.left, (pos + resizer_width) + 'px'); 5702 $resizer.css(this.left, pos + 'px'); 5703 if (pos === 0) { 5704 $collapser 5705 .css(this.left, pos + resizer_width) 5706 .html(this.getSymbol(pos)) 5707 .prop('title', PMA_messages['strShowPanel']); 5708 } else { 5709 $collapser 5710 .css(this.left, pos) 5711 .html(this.getSymbol(pos)) 5712 .prop('title', PMA_messages['strHidePanel']); 5713 } 5714 setTimeout(function (){ 5715 $(window).trigger('resize'); 5716 }, 4); 5717 }; 5718 /** 5719 * Returns the horizontal position of the mouse, 5720 * relative to the outer side of the navigation panel 5721 * 5722 * @param int pos Navigation width in pixels 5723 * 5724 * @return void 5725 */ 5726 this.getPos = function (event) { 5727 var pos = event.pageX; 5728 var windowWidth = $(window).width(); 5729 if (this.left != 'left') { 5730 pos = windowWidth - event.pageX; 5731 } 5732 if (pos < 0) { 5733 pos = 0; 5734 } else if (pos + 100 >= windowWidth) { 5735 pos = windowWidth - 100; 5736 } else { 5737 this.panel_width = 0; 5738 } 5739 return pos; 5740 }; 5741 /** 5742 * Returns the HTML code for the arrow symbol used in the collapser 5743 * 5744 * @param int width The width of the panel 5745 * 5746 * @return string 5747 */
5748 this.getSymbol = function (width) { 5749 if (this.left == 'left') { 5750 if (width == 0) { 5751 return '→'; 5752 } else { 5753 return '←'; 5754 } 5755 } else { 5756 if (width == 0) { 5757 return '←'; 5758 } else { 5759 return '→'; 5760 } 5761 } 5762 }; 5763 /** 5764 * Event handler for initiating a resize of the panel 5765 * 5766 * @param object e Event data (contains a reference to resizeHandler) 5767 * 5768 * @return void 5769 */ 5770 this.mousedown = function (event) { 5771 event.preventDefault(); 5772 event.data.resize_handler.active = true; 5773 $('body').css('cursor', 'col-resize'); 5774 }; 5775 /** 5776 * Event handler for terminating a resize of the panel 5777 * 5778 * @param object e Event data (contains a reference to resizeHandler) 5779 * 5780 * @return void 5781 */ 5782 this.mouseup = function (event) { 5783 if (event.data.resize_handler.active) { 5784 event.data.resize_handler.active = false; 5785 $('body').css('cursor', ''); 5786 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event)); 5787 $('#topmenu').menuResizer('resize'); 5788 } 5789 }; 5790 /** 5791 * Event handler for updating the panel during a resize operation 5792 * 5793 * @param object e Event data (contains a reference to resizeHandler) 5794 * 5795 * @return void 5796 */ 5797 this.mousemove = function (event) { 5798 if (event.data && event.data.resize_handler && event.data.resize_handler.active) { 5799 event.preventDefault(); 5800 var pos = event.data.resize_handler.getPos(event); 5801 event.data.resize_handler.setWidth(pos); 5802 } 5803 }; 5804 /** 5805 * Event handler for collapsing the panel 5806 * 5807 * @param object e Event data (contains a reference to resizeHandler) 5808 * 5809 * @return void 5810 */ 5811 this.collapse = function (event) { 5812 event.preventDefault(); 5813 event.data.active = false; 5814 var panel_width = event.data.resize_handler.panel_width; 5815 var width = $('#pma_navigation').width(); 5816 if (width === 0 && panel_width === 0) { 5817 panel_width = 240; 5818 } 5819 event.data.resize_handler.setWidth(panel_width); 5820 event.data.resize_handler.panel_width = width; 5821 }; 5822 /** 5823 * Even thandler for resizing the navigation tree height on window resize 5824 * 5825 * @return void 5826 */ 5827 this.treeResize = function (event) { 5828 var $nav = $("#pma_navigation"), 5829 $nav_tree = $("#pma_navigation_tree"), 5830 $nav_header = $("#pma_navigation_header"), 5831 $nav_tree_content = $("#pma_navigation_tree_content"); 5832 $nav_tree.height($nav.height() - $nav_header.height()); 5833 $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top); 5834 }; 5835 /* Initialisation section begins here */ 5836 if ($.cookie('pma_navi_width')) { 5837 // If we have a cookie, set the width of the panel to its value 5838 var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0); 5839 this.setWidth(pos); 5840 $('#topmenu').menuResizer('resize'); 5841 } 5842 // Register the events for the resizer and the collapser 5843 $('#pma_navigation_resizer') 5844 .live('mousedown', {'resize_handler':this}, this.mousedown); 5845 $(document) 5846 .bind('mouseup', {'resize_handler':this}, this.mouseup) 5847 .bind('mousemove', {'resize_handler':this}, $.throttle(this.mousemove, 4)); 5848 var $collapser = $('#pma_navigation_collapser'); 5849 $collapser.live('click', {'resize_handler':this}, this.collapse); 5850 // Add the correct arrow symbol to the collapser 5851 $collapser.html(this.getSymbol($('#pma_navigation').width())); 5852 // Fix navigation tree height 5853 $(window).on('resize', this.treeResize); 5854 // need to call this now and then, browser might decide 5855 // to show/hide horizontal scrollbars depending on page content width 5856 setInterval(this.treeResize, 2000); 5857}; // End of ResizeHandler 5858 5859/** 5860 * @var object PMA_fastFilter Handles the functionality that allows filtering 5861 * of the items in a branch of the navigation tree 5862 */ 5863var PMA_fastFilter = { 5864 /** 5865 * Construct for the asynchronous fast filter functionality 5866 * 5867 * @param object $this A jQuery object pointing to the list container 5868 * which is the nearest parent of the fast filter 5869 * @param string searchClause The query string for the filter 5870 * 5871 * @return new PMA_fastFilter.filter object 5872 */ 5873 filter: function ($this, searchClause) { 5874 /** 5875 * @var object $this A jQuery object pointing to the list container 5876 * which is the nearest parent of the fast filter 5877 */
5878 this.$this = $this; 5879 /** 5880 * @var bool searchClause The query string for the filter 5881 */ 5882 this.searchClause = searchClause; 5883 /** 5884 * @var object $clone A clone of the original contents 5885 * of the navigation branch before 5886 * the fast filter was applied 5887 */ 5888 this.$clone = $this.clone(); 5889 /** 5890 * @var bool swapped Whether the user clicked on the "N other results" link 5891 */ 5892 this.swapped = false; 5893 /** 5894 * @var object xhr A reference to the ajax request that is currently running 5895 */ 5896 this.xhr = null; 5897 /** 5898 * @var int timeout Used to delay the request for asynchronous search 5899 */ 5900 this.timeout = null; 5901 5902 var $filterInput = $this.find('li.fast_filter input.searchClause'); 5903 if ( $filterInput.length != 0 5904 && $filterInput.val() != '' 5905 && $filterInput.val() != $filterInput[0].defaultValue 5906 ) { 5907 this.request(); 5908 } 5909 }, 5910 /** 5911 * Gets the query string from the database fast filter form 5912 * 5913 * @return string 5914 */ 5915 getSearchClause: function () { 5916 var retval = ''; 5917 var $input = $('#pma_navigation_tree') 5918 .find('li.fast_filter.db_fast_filter input.searchClause'); 5919 if ($input.length && $input.val() != $input[0].defaultValue) { 5920 retval = $input.val(); 5921 } 5922 return retval; 5923 }, 5924 /** 5925 * Gets the query string from a second level item's fast filter form 5926 * The retrieval is done by trasversing the navigation tree backwards 5927 * 5928 * @return string 5929 */ 5930 getSearchClause2: function ($this) { 5931 var $filterContainer = $this.closest('div.list_container'); 5932 var $filterInput = $([]); 5933 while (1) { 5934 if ($filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause').length != 0) { 5935 $filterInput = $filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause'); 5936 break; 5937 } else if (! $filterContainer.is('div.list_container')) { 5938 break; 5939 } 5940 $filterContainer = $filterContainer 5941 .parent() 5942 .closest('div.list_container'); 5943 } 5944 var searchClause2 = ''; 5945 if ($filterInput.length != 0 5946 && $filterInput.first().val() != $filterInput[0].defaultValue 5947 ) { 5948 searchClause2 = $filterInput.val(); 5949 } 5950 return searchClause2; 5951 }, 5952 /** 5953 * @var hash events A list of functions that are bound to DOM events 5954 * at the top of this file 5955 */ 5956 events: { 5957 focus: function (event) { 5958 var $obj = $(this).closest('div.list_container'); 5959 if (! $obj.data('fastFilter')) { 5960 $obj.data( 5961 'fastFilter', 5962 new PMA_fastFilter.filter($obj, $(this).val()) 5963 ); 5964 } 5965 if ($(this).val() == this.defaultValue) { 5966 $(this).val(''); 5967 } else { 5968 $(this).select(); 5969 } 5970 }, 5971 blur: function (event) { 5972 if ($(this).val() == '') { 5973 $(this).val(this.defaultValue); 5974 } 5975 var $obj = $(this).closest('div.list_container'); 5976 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) { 5977 $obj.data('fastFilter').restore(); 5978 } 5979 }, 5980 keyup: function (event) { 5981 var $obj = $(this).closest('div.list_container'); 5982 var str = ''; 5983 if ($(this).val() != this.defaultValue && $(this).val() != '') { 5984 $obj.find('div.pageselector').hide(); 5985 str = $(this).val().toLowerCase(); 5986 } 5987 $obj.find('li > a').not('.container').each(function () { 5988 if ($(this).text().toLowerCase().indexOf(str) != -1) { 5989 $(this).parent().show().removeClass('hidden'); 5990 } else { 5991 $(this).parent().hide().addClass('hidden'); 5992 } 5993 }); 5994 var container_filter = function ($curr, str) { 5995 $curr.children('ul').children('li.navGroup').each(function() { 5996 var $group = $(this); 5997 $group.children('div.list_container').each(function() { 5998 container_filter($(this)); // recursive 5999 }); 6000 $group.show().removeClass('hidden'); 6001 if ($group.children('div.list_container').children('ul') 6002 .children('li').not('.hidden').length === 0) { 6003 $group.hide().addClass('hidden'); 6004 } 6005 }); 6006 }; 6007 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) { 6008 container_filter($('#pma_navigation_tree_content'), str); 6009 } else { 6010 container_filter($obj, str); 6011 } 6012 if ($(this).val() != this.defaultValue && $(this).val() != '') { 6013 if (! $obj.data('fastFilter')) { 6014 $obj.data( 6015 'fastFilter', 6016 new PMA_fastFilter.filter($obj, $(this).val()) 6017 ); 6018 } else { 6019 $obj.data('fastFilter').update($(this).val()); 6020 } 6021 } else if ($obj.data('fastFilter')) { 6022 $obj.data('fastFilter').restore(true); 6023 } 6024 }, 6025 clear: function (event) { 6026 event.stopPropagation(); 6027 // Clear the input and apply the fast filter with empty input 6028 var filter = $(this).closest('div.list_container').data('fastFilter'); 6029 if (filter) {
6030 filter.restore(); 6031 } 6032 var value = $(this).prev()[0].defaultValue; 6033 $(this).prev().val(value).trigger('keyup'); 6034 } 6035 } 6036}; 6037/** 6038 * Handles a change in the search clause 6039 * 6040 * @param string searchClause The query string for the filter 6041 * 6042 * @return void 6043 */ 6044PMA_fastFilter.filter.prototype.update = function (searchClause) 6045{ 6046 if (this.searchClause != searchClause) { 6047 this.searchClause = searchClause; 6048 this.$this.find('.moreResults').remove(); 6049 this.request(); 6050 } 6051}; 6052/** 6053 * After a delay of 250mS, initiates a request to retrieve search results 6054 * Multiple calls to this function will always abort the previous request 6055 * 6056 * @return void 6057 */ 6058PMA_fastFilter.filter.prototype.request = function () 6059{ 6060 var self = this; 6061 clearTimeout(self.timeout); 6062 if (self.$this.find('li.fast_filter').find('img.throbber').length == 0) { 6063 self.$this.find('li.fast_filter').append( 6064 $('<div class="throbber"></div>').append( 6065 $('#pma_navigation_content') 6066 .find('img.throbber') 6067 .clone() 6068 .css('visibility', 'visible') 6069 ) 6070 ); 6071 } 6072 self.timeout = setTimeout(function () { 6073 if (self.xhr) { 6074 self.xhr.abort(); 6075 } 6076 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 6077 var results = self.$this.find('li:not(.hidden):not(.fast_filter):not(.navGroup)').not('[class^=new]').length; 6078 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize() + "&results=" + results; 6079 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length == 0) { 6080 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause'); 6081 if ($input.length && $input.val() != $input[0].defaultValue) { 6082 params += '&searchClause=' + encodeURIComponent($input.val()); 6083 } 6084 } 6085 self.xhr = $.ajax({ 6086 url: url, 6087 type: 'post', 6088 dataType: 'json', 6089 data: params, 6090 complete: function (jqXHR) { 6091 var data = $.parseJSON(jqXHR.responseText); 6092 self.$this.find('li.fast_filter').find('div.throbber').remove(); 6093 if (data && data.results) { 6094 var $listItem = $('<li />', {'class':'moreResults'}) 6095 .appendTo(self.$this.find('li.fast_filter')); 6096 var $link = $('<a />', {href:'#'}) 6097 .text(data.results) 6098 .appendTo($listItem) 6099 .click(function (event) { 6100 event.preventDefault(); 6101 self.swap.apply(self, [data.message]); 6102 }); 6103 } 6104 } 6105 }); 6106 }, 250); 6107}; 6108/** 6109 * Replaces the contents of the navigation branch with the search results 6110 * 6111 * @param string list The search results 6112 * 6113 * @return void 6114 */ 6115PMA_fastFilter.filter.prototype.swap = function (list) 6116{ 6117 this.swapped = true; 6118 this.$this 6119 .html($(list).html()) 6120 .children() 6121 .show() 6122 .end() 6123 .find('li.fast_filter input.searchClause') 6124 .val(this.searchClause); 6125 this.$this.data('fastFilter', this); 6126}; 6127/** 6128 * Restores the navigation to the original state after the fast filter is cleared 6129 * 6130 * @param bool focus Whether to also focus the input box of the fast filter 6131 * 6132 * @return void 6133 */ 6134PMA_fastFilter.filter.prototype.restore = function (focus) 6135{ 6136 if (this.swapped) { 6137 this.swapped = false; 6138 this.$this.html(this.$clone.html()).children().show(); 6139 this.$this.data('fastFilter', this); 6140 if (focus) { 6141 this.$this.find('li.fast_filter input.searchClause').focus(); 6142 } 6143 } 6144 this.searchClause = ''; 6145 this.$this.find('.moreResults').remove(); 6146 this.$this.find('div.pageselector').show(); 6147 this.$this.find('div.throbber').remove(); 6148}; 6149; 6150 6151/* vim: set expandtab sw=4 ts=4 sts=4: */ 6152/** 6153 * @fileoverview function used for index manipulation pages 6154 * @name Table Structure 6155 * 6156 * @requires jQuery 6157 * @requires jQueryUI 6158 * @required js/functions.js 6159 */ 6160 6161/** 6162 * Hides/shows the inputs and submits appropriately depending 6163 * on whether the index type chosen is 'SPATIAL' or not. 6164 */ 6165function checkIndexType() 6166{ 6167 /** 6168 * @var Object Dropdown to select the index type. 6169 */ 6170 $select_index_type = $('#select_index_type'); 6171 /** 6172 * @var Object Table header for the size column. 6173 */ 6174 $size_header = $('#index_columns thead tr th:nth-child(2)'); 6175 /** 6176 * @var Object Inputs to specify the columns for the index. 6177 */ 6178 $column_inputs = $('select[name="index[columns][names][]"]'); 6179 /** 6180 * @var Object Inputs to specify sizes for columns of the index. 6181 */ 6182 $size_inputs = $('input[name="index[columns][sub_parts][]"]'); 6183 /** 6184 * @var Object Footer containg the controllers to add more columns 6185 */
6186 $add_more = $('#index_frm .tblFooters'); 6187 6188 if ($select_index_type.val() == 'SPATIAL') { 6189 // Disable and hide the size column 6190 $size_header.hide(); 6191 $size_inputs.each(function(){ 6192 $(this) 6193 .prop('disabled', true) 6194 .parent('td').hide(); 6195 }); 6196 6197 // Disable and hide the columns of the index other than the first one 6198 var initial = true; 6199 $column_inputs.each(function() { 6200 $column_input = $(this); 6201 if (! initial) { 6202 $column_input 6203 .prop('disabled', true) 6204 .parent('td').hide(); 6205 } else { 6206 initial = false; 6207 } 6208 }); 6209 6210 // Hide controllers to add more columns 6211 $add_more.hide(); 6212 } else { 6213 // Enable and show the size column 6214 $size_header.show(); 6215 $size_inputs.each(function() { 6216 $(this) 6217 .prop('disabled', false) 6218 .parent('td').show(); 6219 }); 6220 6221 // Enable and show the columns of the index 6222 $column_inputs.each(function() { 6223 $(this) 6224 .prop('disabled', false) 6225 .parent('td').show(); 6226 }); 6227 6228 // Show controllers to add more columns 6229 $add_more.show(); 6230 } 6231} 6232 6233/** 6234 * Unbind all event handlers before tearing down a page 6235 */ 6236AJAX.registerTeardown('indexes.js', function() { 6237 $('#select_index_type').die('change'); 6238 $('a.drop_primary_key_index_anchor.ajax').die('click'); 6239 $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").die('click'); 6240 $('#index_frm input[type=submit]').die('click'); 6241}); 6242 6243/** 6244 * @description <p>Ajax scripts for table index page</p> 6245 * 6246 * Actions ajaxified here: 6247 * <ul> 6248 * <li>Showing/hiding inputs depending on the index type chosen</li> 6249 * <li>create/edit/drop indexes</li> 6250 * </ul> 6251 */ 6252AJAX.registerOnload('indexes.js', function() { 6253 checkIndexType(); 6254 checkIndexName("index_frm"); 6255 $('#select_index_type').live('change', function(event){ 6256 event.preventDefault(); 6257 checkIndexType(); 6258 checkIndexName("index_frm"); 6259 }); 6260 6261 /** 6262 * Ajax Event handler for 'Drop Index' 6263 */ 6264 $('a.drop_primary_key_index_anchor.ajax').live('click', function(event) { 6265 event.preventDefault(); 6266 var $anchor = $(this); 6267 /** 6268 * @var $curr_row Object containing reference to the current field's row 6269 */ 6270 var $curr_row = $anchor.parents('tr'); 6271 /** @var Number of columns in the key */ 6272 var rows = $anchor.parents('td').attr('rowspan') || 1; 6273 /** @var Rows that should be hidden */ 6274 var $rows_to_hide = $curr_row;
6275 for (var i = 1, $last_row = $curr_row.next(); i < rows; i++, $last_row = $last_row.next()) { 6276 $rows_to_hide = $rows_to_hide.add($last_row); 6277 } 6278 6279 var question = escapeHtml( 6280 $curr_row.children('td') 6281 .children('.drop_primary_key_index_msg') 6282 .val() 6283 ); 6284 6285 $anchor.PMA_confirm(question, $anchor.attr('href'), function(url) { 6286 var $msg = PMA_ajaxShowMessage(PMA_messages['strDroppingPrimaryKeyIndex'], false); 6287 $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function(data) { 6288 if (data.success == true) { 6289 PMA_ajaxRemoveMessage($msg); 6290 var $table_ref = $rows_to_hide.closest('table'); 6291 if ($rows_to_hide.length == $table_ref.find('tbody > tr').length) { 6292 // We are about to remove all rows from the table 6293 $table_ref.hide('medium', function() { 6294 $('div.no_indexes_defined').show('medium'); 6295 $rows_to_hide.remove(); 6296 });
6297 $table_ref.siblings('div.notice').hide('medium'); 6298 } else { 6299 // We are removing some of the rows only 6300 toggleRowColors($rows_to_hide.last().next()); 6301 $rows_to_hide.hide("medium", function () { 6302 $(this).remove(); 6303 }); 6304 } 6305 if ($('#result_query').length) { 6306 $('#result_query').remove(); 6307 } 6308 if (data.sql_query) { 6309 $('<div id="result_query"></div>') 6310 .html(data.sql_query) 6311 .prependTo('#page_content'); 6312 } 6313 PMA_commonActions.refreshMain(false, function() { 6314 $("a.ajax[href^=#indexes]").click(); 6315 }); 6316 PMA_reloadNavigation(); 6317 } else { 6318 PMA_ajaxShowMessage(PMA_messages['strErrorProcessingRequest'] + " : " + data.error, false); 6319 } 6320 }); // end $.get() 6321 }); // end $.PMA_confirm() 6322 }); //end Drop Primary Key/Index 6323 6324 /** 6325 *Ajax event handler for index edit 6326 **/ 6327 $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").live('click', function(event) { 6328 event.preventDefault(); 6329 if ($(this).find("a").length == 0) { 6330 // Add index 6331 var valid = checkFormElementInRange( 6332 $(this).closest('form')[0], 6333 'added_fields', 6334 'Column count has to be larger than zero.' 6335 ); 6336 if (! valid) { 6337 return; 6338 } 6339 var url = $(this).closest('form').serialize(); 6340 var title = PMA_messages['strAddIndex']; 6341 } else { 6342 // Edit index 6343 var url = $(this).find("a").attr("href"); 6344 if (url.substring(0, 16) == "tbl_indexes.php?") { 6345 url = url.substring(16, url.length); 6346 } 6347 var title = PMA_messages['strEditIndex']; 6348 } 6349 url += "&ajax_request=true"; 6350 indexEditorDialog(url, title, function() { 6351 // refresh the page using ajax 6352 PMA_commonActions.refreshMain(false, function() { 6353 $("a.ajax[href^=#indexes]").click(); 6354 }); 6355 }); 6356 }); 6357}); 6358; 6359 6360/* vim: set expandtab sw=4 ts=4 sts=4: */ 6361/** 6362 * Functionality for communicating with the querywindow 6363 */ 6364$(function () { 6365 /** 6366 * Event handler for click on the open query window link 6367 * in the top menu of the navigation panel 6368 */ 6369 $('#pma_open_querywindow').click(function (event) { 6370 event.preventDefault(); 6371 PMA_querywindow.focus(); 6372 }); 6373}); 6374 6375/** 6376 * Holds common parameters such as server, db, table, etc 6377 * 6378 * The content for this is normally loaded from Header.class.php or 6379 * Response.class.php and executed by ajax.js 6380 */ 6381var PMA_commonParams = (function () { 6382 /** 6383 * @var hash params An associative array of key value pairs 6384 * @access private 6385 */ 6386 var params = {}; 6387 // The returned object is the public part of the module 6388 return { 6389 /** 6390 * Saves all the key value pair that 6391 * are provided in the input array 6392 * 6393 * @param hash obj The input array 6394 * 6395 * @return void 6396 */ 6397 setAll: function (obj) { 6398 var reload = false; 6399 var updateNavigation = false; 6400 for (var i in obj) { 6401 if (params[i] !== undefined && params[i] !== obj[i]) { 6402 reload = true; 6403 if (i == 'db' || i == 'table') { 6404 updateNavigation = true; 6405 } 6406 } 6407 params[i] = obj[i]; 6408 } 6409 if (updateNavigation) { 6410 PMA_showCurrentNavigation(); 6411 } 6412 if (reload) { 6413 PMA_querywindow.refresh(); 6414 } 6415 }, 6416 /** 6417 * Retrieves a value given its key 6418 * Returns empty string for undefined values 6419 * 6420 * @param string name The key 6421 * 6422 * @return string 6423 */ 6424 get: function (name) { 6425 return params[name] || ''; 6426 }, 6427 /** 6428 * Saves a single key value pair 6429 * 6430 * @param string name The key 6431 * @param string value The value 6432 * 6433 * @return self For chainability 6434 */ 6435 set: function (name, value) { 6436 var updateNavigation = false;
6437 if (params[name] !== undefined && params[name] !== value) { 6438 PMA_querywindow.refresh(); 6439 if (name == 'db' || name == 'table') { 6440 updateNavigation = true; 6441 } 6442 } 6443 params[name] = value; 6444 if (updateNavigation) { 6445 PMA_showCurrentNavigation(); 6446 } 6447 return this; 6448 }, 6449 /** 6450 * Returns the url query string using the saved parameters 6451 * 6452 * @return string 6453 */ 6454 getUrlQuery: function () { 6455 return $.sprintf( 6456 '?%s&server=%s&db=%s&table=%s', 6457 this.get('common_query'), 6458 encodeURIComponent(this.get('server')), 6459 encodeURIComponent(this.get('db')), 6460 encodeURIComponent(this.get('table')) 6461 ); 6462 } 6463 }; 6464})(); 6465 6466/** 6467 * Holds common parameters such as server, db, table, etc 6468 * 6469 * The content for this is normally loaded from Header.class.php or 6470 * Response.class.php and executed by ajax.js 6471 */ 6472var PMA_commonActions = { 6473 /** 6474 * Saves the database name when it's changed 6475 * and reloads the query window, if necessary 6476 * 6477 * @param string new_db The name of the new database 6478 * 6479 * @return void 6480 */ 6481 setDb: function (new_db) { 6482 if (new_db != PMA_commonParams.get('db')) { 6483 PMA_commonParams.setAll({'db': new_db, 'table': ''}); 6484 } 6485 }, 6486 /** 6487 * Opens a database in the main part of the page 6488 * 6489 * @param string new_db The name of the new database 6490 * 6491 * @return void 6492 */ 6493 openDb: function (new_db) { 6494 PMA_commonParams 6495 .set('db', new_db) 6496 .set('table', ''); 6497 PMA_querywindow.refresh(); 6498 this.refreshMain( 6499 PMA_commonParams.get('opendb_url') 6500 ); 6501 }, 6502 /** 6503 * Refreshes the main frame 6504 * 6505 * @param mixed url Undefined to refresh to the same page 6506 * String to go to a different page, e.g: 'index.php' 6507 * 6508 * @return void 6509 */ 6510 refreshMain: function (url, callback) { 6511 if (! url) { 6512 url = $('#selflink a').attr('href'); 6513 url = url.substring(0, url.indexOf('?')); 6514 } 6515 url += PMA_commonParams.getUrlQuery(); 6516 $('<a />', {href: url}) 6517 .appendTo('body') 6518 .click() 6519 .remove(); 6520 AJAX._callback = callback; 6521 } 6522}; 6523 6524/** 6525 * Common functions used for communicating with the querywindow 6526 */ 6527var PMA_querywindow = (function ($, window) { 6528 /** 6529 * @var Object querywindow Reference to the window 6530 * object of the querywindow 6531 * @access private 6532 */ 6533 var querywindow = {}; 6534 /** 6535 * @var string queryToLoad Stores the SQL query that is to be displayed 6536 * in the querywindow when it is ready 6537 * @access private 6538 */ 6539 var queryToLoad = ''; 6540 // The returned object is the public part of the module 6541 return { 6542 /** 6543 * Opens the query window 6544 * 6545 * @param mixed url Undefined to open the default page 6546 * String to go to a different 6547 * 6548 * @return void 6549 */ 6550 open: function (url, sql_query) { 6551 if (! url) { 6552 url = 'querywindow.php' + PMA_commonParams.getUrlQuery(); 6553 } 6554 if (sql_query) { 6555 url += '&sql_query=' + encodeURIComponent(sql_query); 6556 } 6557 6558 if (! querywindow.closed && querywindow.location) { 6559 var href = querywindow.location.href; 6560 if (href != url 6561 && href != PMA_commonParams.get('pma_absolute_uri') + url 6562 ) { 6563 if (PMA_commonParams.get('safari_browser')) { 6564 querywindow.location.href = targeturl; 6565 } else { 6566 querywindow.location.replace(targeturl); 6567 } 6568 querywindow.focus(); 6569 } 6570 } else { 6571 querywindow = window.open( 6572 url + '&init=1', 6573 '', 6574 'toolbar=0,location=0,directories=0,status=1,' 6575 + 'menubar=0,scrollbars=yes,resizable=yes,' 6576 + 'width=' + PMA_commonParams.get('querywindow_width') + ',' 6577 + 'height=' + PMA_commonParams.get('querywindow_height') 6578 ); 6579 } 6580 if (! querywindow.opener) { 6581 querywindow.opener = window.window; 6582 } 6583 if (window.focus) { 6584 querywindow.focus(); 6585 } 6586 }, 6587 /** 6588 * Opens, if necessary, focuses the query window 6589 * and displays an SQL query. 6590 * 6591 * @param string sql_query The SQL query to display in 6592 * the query window 6593 * 6594 * @return void 6595 */ 6596 focus: function (sql_query) { 6597 if (! querywindow || querywindow.closed || ! querywindow.location) { 6598 // we need first to open the window and cannot pass the query with it 6599 // as we dont know if the query exceeds max url length 6600 queryToLoad = sql_query; 6601 this.open(false, sql_query); 6602 } else { 6603 //var querywindow = querywindow; 6604 var hiddenqueryform = querywindow 6605 .document 6606 .getElementById('hiddenqueryform'); 6607 if (hiddenqueryform.querydisplay_tab != 'sql' ) { 6608 hiddenqueryform.querydisplay_tab.value = "sql"; 6609 hiddenqueryform.sql_query.value = sql_query; 6610 $(hiddenqueryform).addClass('disableAjax'); 6611 hiddenqueryform.submit(); 6612 querywindow.focus(); 6613 } else { 6614 querywindow.focus(); 6615 } 6616 } 6617 }, 6618 /** 6619 * Refreshes the query window given a url 6620 * 6621 * @param string url Where to go to 6622 * 6623 * @return void 6624 */ 6625 refresh: function (url) { 6626 if (! querywindow.closed && querywindow.location) { 6627 var $form = $(querywindow.document).find('#sqlqueryform'); 6628 if ($form.find('#checkbox_lock:checked').length == 0) { 6629 PMA_querywindow.open(url); 6630 } 6631 } 6632 }, 6633 /** 6634 * Reloads the query window given the details 6635 * of a db, a table and an sql_query 6636 * 6637 * @param string db The name of the database 6638 * @param string table The name of the table 6639 * @param string sql_query The SQL query to be displayed 6640 * 6641 * @return void 6642 */ 6643 reload: function (db, table, sql_query) { 6644 if (! querywindow.closed && querywindow.location) { 6645 var $form = $(querywindow.document).find('#sqlqueryform'); 6646 if ($form.find('#checkbox_lock:checked').length == 0) { 6647 var $hiddenform = $(querywindow.document) 6648 .find('#hiddenqueryform'); 6649 $hiddenform.find('input[name=db]').val(db); 6650 $hiddenform.find('input[name=table]').val(table); 6651 if (sql_query) { 6652 $hiddenform.find('input[name=sql_query]').val(sql_query); 6653 } 6654 $hiddenform.addClass('disableAjax').submit(); 6655 } 6656 } 6657 } 6658 }; 6659})(jQuery, window); 6660; 6661 6662// CodeMirror version 2.38 6663// 6664// All functions that need access to the editor's state live inside
vendor: 4,285 bytes, lines 6665-6732
6665// the CodeMirror function. Below that, at the bottom of the file, 6666// some utilities are defined. 6667 6668// CodeMirror is the only global var we claim 6669window.CodeMirror = (function() { 6670 "use strict"; 6671 // This is the function that produces an editor instance. Its 6672 // closure is used to store the editor state. 6673 function CodeMirror(place, givenOptions) { 6674 // Determine effective options based on given values and defaults. 6675 var options = {}, defaults = CodeMirror.defaults; 6676 for (var opt in defaults) 6677 if (defaults.hasOwnProperty(opt)) 6678 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; 6679 6680 var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em"); 6681 input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); 6682 // Wraps and hides input textarea 6683 var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); 6684 // The empty scrollbar content, used solely for managing the scrollbar thumb. 6685 var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner"); 6686 // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. 6687 var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar"); 6688 // DIVs containing the selection and the actual code 6689 var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1"); 6690 // Blinky cursor, and element used to ensure cursor fits at the end of a line 6691 var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden"); 6692 // Used to measure text size 6693 var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"); 6694 var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0"); 6695 var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter"); 6696 // Moved around its parent to cover visible view 6697 var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative"); 6698 // Set to the height of the text, causes scrolling 6699 var sizer = elt("div", [mover], null, "position: relative"); 6700 // Provides scrolling 6701 var scroller = elt("div", [sizer], "CodeMirror-scroll"); 6702 scroller.setAttribute("tabIndex", "-1"); 6703 // The element in which the editor lives. 6704 var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "")); 6705 if (place.appendChild) place.appendChild(wrapper); else place(wrapper); 6706 6707 themeChanged(); keyMapChanged(); 6708 // Needed to hide big blue blinking cursor on Mobile Safari 6709 if (ios) input.style.width = "0px"; 6710 if (!webkit) scroller.draggable = true; 6711 lineSpace.style.outline = "none"; 6712 if (options.tabindex != null) input.tabIndex = options.tabindex; 6713 if (options.autofocus) focusInput(); 6714 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; 6715 // Needed to handle Tab key in KHTML 6716 if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; 6717 6718 // Check for OS X >= 10.7. This has transparent scrollbars, so the 6719 // overlaying of one scrollbar with another won't work. This is a 6720 // temporary hack to simply turn off the overlay scrollbar. See 6721 // issue #727. 6722 if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; } 6723 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 6724 else if (ie_lt8) scrollbar.style.minWidth = "18px"; 6725 6726 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. 6727 var poll = new Delayed(), highlight = new Delayed(), blinker; 6728 6729 // mode holds a mode API object. doc is the tree of Line objects, 6730 // frontier is the point up to which the content has been parsed, 6731 // and history the undo history (instance of History constructor). 6732 var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused;
6733 loadMode(); 6734 // The selection. These are always maintained to point at valid 6735 // positions. Inverted is used to remember that the user is 6736 // selecting bottom-to-top. 6737 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; 6738 // Selection-related flags. shiftSelecting obviously tracks 6739 // whether the user is holding shift. 6740 var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText, 6741 overwrite = false, suppressEdits = false, pasteIncoming = false; 6742 // Variables used by startOperation/endOperation to track what 6743 // happened during the operation. 6744 var updateInput, userSelChange, changes, textChanged, selectionChanged, 6745 gutterDirty, callbacks; 6746 // Current visible range (may be bigger than the view window). 6747 var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; 6748 // bracketHighlighted is used to remember that a bracket has been 6749 // marked. 6750 var bracketHighlighted; 6751 // Tracks the maximum line length so that the horizontal scrollbar 6752 // can be kept static when scrolling. 6753 var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; 6754 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll 6755 var goalColumn = null; 6756 6757 // Initialize the content. 6758 operation(function(){setValue(options.value || ""); updateInput = false;})(); 6759 var history = new History(); 6760 6761 // Register our event handlers. 6762 connect(scroller, "mousedown", operation(onMouseDown)); 6763 connect(scroller, "dblclick", operation(onDoubleClick)); 6764 connect(lineSpace, "selectstart", e_preventDefault); 6765 // Gecko browsers fire contextmenu *after* opening the menu, at 6766 // which point we can't mess with it anymore. Context menu is 6767 // handled in onMouseDown for Gecko. 6768 if (!gecko) connect(scroller, "contextmenu", onContextMenu); 6769 connect(scroller, "scroll", onScrollMain); 6770 connect(scrollbar, "scroll", onScrollBar); 6771 connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);}); 6772 var resizeHandler = connect(window, "resize", function() { 6773 if (wrapper.parentNode) updateDisplay(true); 6774 else resizeHandler(); 6775 }, true); 6776 connect(input, "keyup", operation(onKeyUp)); 6777 connect(input, "input", fastPoll); 6778 connect(input, "keydown", operation(onKeyDown)); 6779 connect(input, "keypress", operation(onKeyPress)); 6780 connect(input, "focus", onFocus); 6781 connect(input, "blur", onBlur); 6782 6783 function drag_(e) { 6784 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; 6785 e_stop(e); 6786 } 6787 if (options.dragDrop) { 6788 connect(scroller, "dragstart", onDragStart); 6789 connect(scroller, "dragenter", drag_); 6790 connect(scroller, "dragover", drag_); 6791 connect(scroller, "drop", operation(onDrop)); 6792 } 6793 connect(scroller, "paste", function(){focusInput(); fastPoll();}); 6794 connect(input, "paste", function(){pasteIncoming = true; fastPoll();}); 6795 connect(input, "cut", operation(function(){ 6796 if (!options.readOnly) replaceSelection(""); 6797 })); 6798 6799 // Needed to handle Tab key in KHTML 6800 if (khtml) connect(sizer, "mouseup", function() { 6801 if (document.activeElement == input) input.blur(); 6802 focusInput(); 6803 }); 6804 6805 // IE throws unspecified error in certain cases, when 6806 // trying to access activeElement before onload 6807 var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } 6808 if (hasFocus || options.autofocus) setTimeout(onFocus, 20); 6809 else onBlur(); 6810 6811 function isLine(l) {return l >= 0 && l < doc.size;} 6812 // The instance object that we'll return. Mostly calls out to 6813 // local functions in the CodeMirror function. Some do some extra 6814 // range checking and/or clipping. operation is used to wrap the 6815 // call so that changes it makes are tracked, and the display is 6816 // updated afterwards. 6817 var instance = wrapper.CodeMirror = { 6818 getValue: getValue, 6819 setValue: operation(setValue), 6820 getSelection: getSelection, 6821 replaceSelection: operation(replaceSelection), 6822 focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, 6823 setOption: function(option, value) { 6824 var oldVal = options[option]; 6825 options[option] = value; 6826 if (option == "mode" || option == "indentUnit") loadMode(); 6827 else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} 6828 else if (option == "readOnly" && !value) {resetInput(true);} 6829 else if (option == "theme") themeChanged(); 6830 else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); 6831 else if (option == "tabSize") updateDisplay(true);
6832 else if (option == "keyMap") keyMapChanged(); 6833 else if (option == "tabindex") input.tabIndex = value; 6834 else if (option == "showCursorWhenSelecting") updateSelection(); 6835 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || 6836 option == "theme" || option == "lineNumberFormatter") { 6837 gutterChanged(); 6838 updateDisplay(true); 6839 } 6840 }, 6841 getOption: function(option) {return options[option];}, 6842 getMode: function() {return mode;}, 6843 undo: operation(undo), 6844 redo: operation(redo), 6845 indentLine: operation(function(n, dir) { 6846 if (typeof dir != "string") { 6847 if (dir == null) dir = options.smartIndent ? "smart" : "prev"; 6848 else dir = dir ? "add" : "subtract"; 6849 } 6850 if (isLine(n)) indentLine(n, dir); 6851 }), 6852 indentSelection: operation(indentSelected), 6853 historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, 6854 clearHistory: function() {history = new History();}, 6855 setHistory: function(histData) { 6856 history = new History(); 6857 history.done = histData.done; 6858 history.undone = histData.undone; 6859 }, 6860 getHistory: function() { 6861 function cp(arr) { 6862 for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { 6863 nw.push(nwelt = []); 6864 for (var j = 0, elt = arr[i]; j < elt.length; ++j) { 6865 var old = [], cur = elt[j]; 6866 nwelt.push({start: cur.start, added: cur.added, old: old}); 6867 for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); 6868 } 6869 } 6870 return nw; 6871 } 6872 return {done: cp(history.done), undone: cp(history.undone)}; 6873 }, 6874 matchBrackets: operation(function(){matchBrackets(true);}), 6875 getTokenAt: operation(function(pos) { 6876 pos = clipPos(pos); 6877 return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch); 6878 }), 6879 getStateAfter: function(line) { 6880 line = clipLine(line == null ? doc.size - 1: line); 6881 return getStateBefore(line + 1); 6882 }, 6883 cursorCoords: function(start, mode) { 6884 if (start == null) start = sel.inverted; 6885 return this.charCoords(start ? sel.from : sel.to, mode); 6886 }, 6887 charCoords: function(pos, mode) { 6888 pos = clipPos(pos); 6889 if (mode == "local") return localCoords(pos, false); 6890 if (mode == "div") return localCoords(pos, true); 6891 return pageCoords(pos); 6892 }, 6893 coordsChar: function(coords) { 6894 var off = eltOffset(lineSpace); 6895 return coordsChar(coords.x - off.left, coords.y - off.top); 6896 }, 6897 defaultTextHeight: function() { return textHeight(); }, 6898 markText: operation(markText), 6899 setBookmark: setBookmark, 6900 findMarksAt: findMarksAt, 6901 setMarker: operation(addGutterMarker), 6902 clearMarker: operation(removeGutterMarker), 6903 setLineClass: operation(setLineClass), 6904 hideLine: operation(function(h) {return setLineHidden(h, true);}), 6905 showLine: operation(function(h) {return setLineHidden(h, false);}), 6906 onDeleteLine: function(line, f) { 6907 if (typeof line == "number") { 6908 if (!isLine(line)) return null; 6909 line = getLine(line); 6910 } 6911 (line.handlers || (line.handlers = [])).push(f); 6912 return line; 6913 }, 6914 lineInfo: lineInfo, 6915 getViewport: function() { return {from: showingFrom, to: showingTo};}, 6916 addWidget: function(pos, node, scroll, vert, horiz) { 6917 pos = localCoords(clipPos(pos)); 6918 var top = pos.yBot, left = pos.x; 6919 node.style.position = "absolute"; 6920 sizer.appendChild(node); 6921 if (vert == "over") top = pos.y; 6922 else if (vert == "near") { 6923 var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), 6924 hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft(); 6925 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) 6926 top = pos.y - node.offsetHeight; 6927 if (left + node.offsetWidth > hspace) 6928 left = hspace - node.offsetWidth; 6929 } 6930 node.style.top = (top + paddingTop()) + "px"; 6931 node.style.left = node.style.right = ""; 6932 if (horiz == "right") { 6933 left = sizer.clientWidth - node.offsetWidth; 6934 node.style.right = "0px"; 6935 } else { 6936 if (horiz == "left") left = 0; 6937 else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2; 6938 node.style.left = (left + paddingLeft()) + "px"; 6939 } 6940 if (scroll) 6941 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); 6942 }, 6943 6944 lineCount: function() {return doc.size;}, 6945 clipPos: clipPos, 6946 getCursor: function(start) { 6947 if (start == null || start == "head") start = sel.inverted; 6948 if (start == "anchor") start = !sel.inverted; 6949 if (start == "end") start = false;
6950 return copyPos(start ? sel.from : sel.to); 6951 }, 6952 somethingSelected: function() {return !posEq(sel.from, sel.to);}, 6953 setCursor: operation(function(line, ch, user) { 6954 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); 6955 else setCursor(line, ch, user); 6956 }), 6957 setSelection: operation(function(from, to, user) { 6958 (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); 6959 }), 6960 getLine: function(line) {if (isLine(line)) return getLine(line).text;}, 6961 getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, 6962 setLine: operation(function(line, text) { 6963 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); 6964 }), 6965 removeLine: operation(function(line) { 6966 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); 6967 }), 6968 replaceRange: operation(replaceRange), 6969 getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);}, 6970 6971 triggerOnKeyDown: operation(onKeyDown), 6972 execCommand: function(cmd) {return commands[cmd](instance);}, 6973 // Stuff used by commands, probably not much use to outside code. 6974 moveH: operation(moveH), 6975 deleteH: operation(deleteH), 6976 moveV: operation(moveV), 6977 toggleOverwrite: function() { 6978 if(overwrite){ 6979 overwrite = false; 6980 cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); 6981 } else { 6982 overwrite = true; 6983 cursor.className += " CodeMirror-overwrite"; 6984 } 6985 }, 6986 6987 posFromIndex: function(off) { 6988 var lineNo = 0, ch; 6989 doc.iter(0, doc.size, function(line) { 6990 var sz = line.text.length + 1; 6991 if (sz > off) { ch = off; return true; } 6992 off -= sz; 6993 ++lineNo; 6994 }); 6995 return clipPos({line: lineNo, ch: ch}); 6996 }, 6997 indexFromPos: function (coords) { 6998 if (coords.line < 0 || coords.ch < 0) return 0; 6999 var index = coords.ch; 7000 doc.iter(0, coords.line, function (line) { 7001 index += line.text.length + 1; 7002 }); 7003 return index; 7004 }, 7005 scrollTo: function(x, y) { 7006 if (x != null) scroller.scrollLeft = x; 7007 if (y != null) scrollbar.scrollTop = scroller.scrollTop = y; 7008 updateDisplay([]); 7009 }, 7010 getScrollInfo: function() { 7011 return {x: scroller.scrollLeft, y: scrollbar.scrollTop, 7012 height: scrollbar.scrollHeight, width: scroller.scrollWidth}; 7013 }, 7014 scrollIntoView: function(pos) { 7015 var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to); 7016 scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); 7017 }, 7018 7019 setSize: function(width, height) { 7020 function interpret(val) { 7021 val = String(val); 7022 return /^\d+$/.test(val) ? val + "px" : val; 7023 } 7024 if (width != null) wrapper.style.width = interpret(width); 7025 if (height != null) scroller.style.height = interpret(height); 7026 instance.refresh(); 7027 }, 7028 7029 operation: function(f){return operation(f)();}, 7030 compoundChange: function(f){return compoundChange(f);}, 7031 refresh: function(){ 7032 updateDisplay(true, null, lastScrollTop); 7033 if (scrollbar.scrollHeight > lastScrollTop) 7034 scrollbar.scrollTop = lastScrollTop; 7035 }, 7036 getInputField: function(){return input;}, 7037 getWrapperElement: function(){return wrapper;}, 7038 getScrollerElement: function(){return scroller;}, 7039 getGutterElement: function(){return gutter;} 7040 }; 7041 7042 function getLine(n) { return getLineAt(doc, n); } 7043 function updateLineHeight(line, height) { 7044 gutterDirty = true; 7045 var diff = height - line.height; 7046 for (var n = line; n; n = n.parent) n.height += diff; 7047 } 7048 7049 function lineContent(line, wrapAt) { 7050 if (!line.styles) 7051 line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); 7052 return line.getContent(options.tabSize, wrapAt, options.lineWrapping); 7053 } 7054 7055 function setValue(code) { 7056 var top = {line: 0, ch: 0}; 7057 updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, 7058 splitLines(code), top, top); 7059 updateInput = true; 7060 } 7061 function getValue(lineSep) { 7062 var text = []; 7063 doc.iter(0, doc.size, function(line) { text.push(line.text); });
7064 return text.join(lineSep || "\n"); 7065 } 7066 7067 function onScrollBar(e) { 7068 if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) { 7069 lastScrollTop = scroller.scrollTop = scrollbar.scrollTop; 7070 updateDisplay([]); 7071 } 7072 } 7073 7074 function onScrollMain(e) { 7075 if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px") 7076 gutter.style.left = scroller.scrollLeft + "px"; 7077 if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) { 7078 lastScrollTop = scroller.scrollTop; 7079 if (scrollbar.scrollTop != lastScrollTop) 7080 scrollbar.scrollTop = lastScrollTop; 7081 updateDisplay([]); 7082 } 7083 if (options.onScroll) options.onScroll(instance); 7084 } 7085 7086 function onMouseDown(e) { 7087 setShift(e_prop(e, "shiftKey")); 7088 // Check whether this is a click in a widget 7089 for (var n = e_target(e); n != wrapper; n = n.parentNode) 7090 if (n.parentNode == sizer && n != mover) return; 7091 7092 // See if this is a click in the gutter 7093 for (var n = e_target(e); n != wrapper; n = n.parentNode) 7094 if (n.parentNode == gutterText) { 7095 if (options.onGutterClick) 7096 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); 7097 return e_preventDefault(e); 7098 } 7099 7100 var start = posFromMouse(e); 7101 7102 switch (e_button(e)) { 7103 case 3: 7104 if (gecko) onContextMenu(e); 7105 return; 7106 case 2: 7107 if (start) setCursor(start.line, start.ch, true); 7108 setTimeout(focusInput, 20); 7109 e_preventDefault(e); 7110 return; 7111 } 7112 // For button 1, if it was clicked inside the editor 7113 // (posFromMouse returning non-null), we have to adjust the 7114 // selection. 7115 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} 7116 7117 if (!focused) onFocus(); 7118 7119 var now = +new Date, type = "single"; 7120 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { 7121 type = "triple"; 7122 e_preventDefault(e); 7123 setTimeout(focusInput, 20); 7124 selectLine(start.line); 7125 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { 7126 type = "double"; 7127 lastDoubleClick = {time: now, pos: start}; 7128 e_preventDefault(e); 7129 var word = findWordAt(start); 7130 setSelectionUser(word.from, word.to); 7131 } else { lastClick = {time: now, pos: start}; } 7132 7133 function dragEnd(e2) { 7134 if (webkit) scroller.draggable = false; 7135 draggingText = false; 7136 up(); drop(); 7137 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { 7138 e_preventDefault(e2); 7139 setCursor(start.line, start.ch, true); 7140 focusInput(); 7141 } 7142 } 7143 var last = start, going; 7144 if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && 7145 !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { 7146 // Let the drag handler handle this. 7147 if (webkit) scroller.draggable = true; 7148 var up = connect(document, "mouseup", operation(dragEnd), true); 7149 var drop = connect(scroller, "drop", operation(dragEnd), true); 7150 draggingText = true; 7151 // IE's approach to draggable 7152 if (scroller.dragDrop) scroller.dragDrop(); 7153 return; 7154 } 7155 e_preventDefault(e); 7156 if (type == "single") setCursor(start.line, start.ch, true); 7157 7158 var startstart = sel.from, startend = sel.to; 7159 7160 function doSelect(cur) { 7161 if (type == "single") { 7162 setSelectionUser(clipPos(start), cur); 7163 return; 7164 } 7165 startstart = clipPos(startstart); 7166 startend = clipPos(startend); 7167 if (type == "double") { 7168 var word = findWordAt(cur); 7169 if (posLess(cur, startstart)) setSelectionUser(word.from, startend); 7170 else setSelectionUser(startstart, word.to); 7171 } else if (type == "triple") { 7172 if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0})); 7173 else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0})); 7174 } 7175 } 7176 7177 function extend(e) { 7178 var cur = posFromMouse(e, true); 7179 if (cur && !posEq(cur, last)) { 7180 if (!focused) onFocus(); 7181 last = cur; 7182 doSelect(cur); 7183 updateInput = false;
7184 var visible = visibleLines(); 7185 if (cur.line >= visible.to || cur.line < visible.from) 7186 going = setTimeout(operation(function(){extend(e);}), 150); 7187 } 7188 } 7189 7190 function done(e) { 7191 clearTimeout(going); 7192 var cur = posFromMouse(e); 7193 if (cur) doSelect(cur); 7194 e_preventDefault(e); 7195 focusInput(); 7196 updateInput = true; 7197 move(); up(); 7198 } 7199 var move = connect(document, "mousemove", operation(function(e) { 7200 clearTimeout(going); 7201 e_preventDefault(e); 7202 if (!ie && !e_button(e)) done(e); 7203 else extend(e); 7204 }), true); 7205 var up = connect(document, "mouseup", operation(done), true); 7206 } 7207 function onDoubleClick(e) { 7208 for (var n = e_target(e); n != wrapper; n = n.parentNode) 7209 if (n.parentNode == gutterText) return e_preventDefault(e); 7210 e_preventDefault(e); 7211 } 7212 function onDrop(e) { 7213 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; 7214 e_preventDefault(e); 7215 var pos = posFromMouse(e, true), files = e.dataTransfer.files; 7216 if (!pos || options.readOnly) return; 7217 if (files && files.length && window.FileReader && window.File) { 7218 var n = files.length, text = Array(n), read = 0; 7219 var loadFile = function(file, i) { 7220 var reader = new FileReader; 7221 reader.onload = function() { 7222 text[i] = reader.result; 7223 if (++read == n) { 7224 pos = clipPos(pos); 7225 operation(function() { 7226 var end = replaceRange(text.join(""), pos, pos); 7227 setSelectionUser(pos, end); 7228 })(); 7229 } 7230 }; 7231 reader.readAsText(file); 7232 }; 7233 for (var i = 0; i < n; ++i) loadFile(files[i], i); 7234 } else { 7235 // Don't do a replace if the drop happened inside of the selected text. 7236 if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; 7237 try { 7238 var text = e.dataTransfer.getData("Text"); 7239 if (text) { 7240 compoundChange(function() { 7241 var curFrom = sel.from, curTo = sel.to; 7242 setSelectionUser(pos, pos); 7243 if (draggingText) replaceRange("", curFrom, curTo); 7244 replaceSelection(text); 7245 focusInput(); 7246 }); 7247 } 7248 } 7249 catch(e){} 7250 } 7251 } 7252 function onDragStart(e) { 7253 var txt = getSelection(); 7254 e.dataTransfer.setData("Text", txt); 7255 7256 // Use dummy image instead of default browsers image. 7257 if (e.dataTransfer.setDragImage) 7258 e.dataTransfer.setDragImage(elt('img'), 0, 0); 7259 } 7260 7261 function doHandleBinding(bound, dropShift) { 7262 if (typeof bound == "string") { 7263 bound = commands[bound]; 7264 if (!bound) return false; 7265 } 7266 var prevShift = shiftSelecting; 7267 try { 7268 if (options.readOnly) suppressEdits = true; 7269 if (dropShift) shiftSelecting = null; 7270 bound(instance); 7271 } catch(e) { 7272 if (e != Pass) throw e; 7273 return false; 7274 } finally { 7275 shiftSelecting = prevShift; 7276 suppressEdits = false; 7277 } 7278 return true; 7279 } 7280 var maybeTransition; 7281 function handleKeyBinding(e) { 7282 // Handle auto keymap transitions 7283 var startMap = getKeyMap(options.keyMap), next = startMap.auto; 7284 clearTimeout(maybeTransition); 7285 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { 7286 if (getKeyMap(options.keyMap) == startMap) { 7287 options.keyMap = (next.call ? next.call(null, instance) : next); 7288 } 7289 }, 50); 7290 7291 var name = keyNames[e_prop(e, "keyCode")], handled = false; 7292 if (name == null || e.altGraphKey) return false; 7293 if (e_prop(e, "altKey")) name = "Alt-" + name; 7294 if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; 7295 if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; 7296 7297 var stopped = false; 7298 function stop() { stopped = true; } 7299 7300 if (e_prop(e, "shiftKey")) { 7301 handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, 7302 function(b) {return doHandleBinding(b, true);}, stop) 7303 || lookupKey(name, options.extraKeys, options.keyMap, function(b) { 7304 if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); 7305 }, stop); 7306 } else { 7307 handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop
7307); 7308 } 7309 if (stopped) handled = false; 7310 if (handled) { 7311 e_preventDefault(e); 7312 restartBlink(); 7313 if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } 7314 } 7315 return handled; 7316 } 7317 function handleCharBinding(e, ch) { 7318 var handled = lookupKey("'" + ch + "'", options.extraKeys, 7319 options.keyMap, function(b) { return doHandleBinding(b, true); }); 7320 if (handled) { 7321 e_preventDefault(e); 7322 restartBlink(); 7323 } 7324 return handled; 7325 } 7326 7327 var lastStoppedKey = null; 7328 function onKeyDown(e) { 7329 if (!focused) onFocus(); 7330 if (ie && e.keyCode == 27) { e.returnValue = false; } 7331 if (pollingFast) { if (readInput()) pollingFast = false; } 7332 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 7333 var code = e_prop(e, "keyCode"); 7334 // IE does strange things with escape. 7335 setShift(code == 16 || e_prop(e, "shiftKey")); 7336 // First give onKeyEvent option a chance to handle this. 7337 var handled = handleKeyBinding(e); 7338 if (opera) { 7339 lastStoppedKey = handled ? code : null; 7340 // Opera has no cut event... we try to at least catch the key combo 7341 if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) 7342 replaceSelection(""); 7343 } 7344 } 7345 function onKeyPress(e) { 7346 if (pollingFast) readInput(); 7347 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 7348 var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); 7349 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} 7350 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; 7351 var ch = String.fromCharCode(charCode == null ? keyCode : charCode); 7352 if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { 7353 if (mode.electricChars.indexOf(ch) > -1) 7354 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); 7355 } 7356 if (handleCharBinding(e, ch)) return; 7357 fastPoll(); 7358 } 7359 function onKeyUp(e) { 7360 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 7361 if (e_prop(e, "keyCode") == 16) shiftSelecting = null; 7362 } 7363 7364 function onFocus() { 7365 if (options.readOnly == "nocursor") return; 7366 if (!focused) { 7367 if (options.onFocus) options.onFocus(instance); 7368 focused = true; 7369 if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) 7370 scroller.className += " CodeMirror-focused"; 7371 } 7372 slowPoll(); 7373 restartBlink(); 7374 } 7375 function onBlur() { 7376 if (focused) { 7377 if (options.onBlur) options.onBlur(instance); 7378 focused = false; 7379 if (bracketHighlighted) 7380 operation(function(){ 7381 if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } 7382 })(); 7383 scroller.className = scroller.className.replace(" CodeMirror-focused", ""); 7384 } 7385 clearInterval(blinker); 7386 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); 7387 } 7388 7389 // Replace the range from from to to by the strings in newText. 7390 // Afterwards, set the selection to selFrom, selTo. 7391 function updateLines(from, to, newText, selFrom, selTo) { 7392 if (suppressEdits) return; 7393 var old = []; 7394 doc.iter(from.line, to.line + 1, function(line) { 7395 old.push(newHL(line.text, line.markedSpans)); 7396 }); 7397 if (history) { 7398 history.addChange(from.line, newText.length, old); 7399 while (history.done.length > options.undoDepth) history.done.shift(); 7400 } 7401 var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); 7402 updateLinesNoUndo(from, to, lines, selFrom, selTo); 7403 } 7404 function unredoHelper(from, to) { 7405 if (!from.length) return; 7406 var set = from.pop(), out = []; 7407 for (var i = set.length - 1; i >= 0; i -= 1) { 7408 var change = set[i]; 7409 var replaced = [], end = change.start + change.added; 7410 doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); 7411 out.push({start: change.start, added: change.old.length, old: replaced}); 7412 var pos = {line: change.start + change.old.length - 1, 7413 ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; 7414 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, 7415 change.old, pos, pos); 7416 } 7417 updateInput = true; 7418 to.push(out); 7419 } 7420 function undo() {unredoHelper(history.done, history.undone);} 7421 function redo() {unredoHelper(history.undone, history.done);} 7422 7423 function updateLinesNoUndo(from, to, lines, selFrom, selTo) { 7424 if (suppressEdits) return; 7425 var recomputeMaxLength = false, maxLineLength = maxLine.text.length; 7426 if (!options.lineWrapping) 7427 doc.iter(from.line, to.line + 1, function(line) { 7428 if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} 7429 }); 7430 if (from.line != to.line || lines.length > 1) gutterDirty = true; 7431 7432 var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); 7433 var lastHL = lst(lines); 7434
7435 // First adjust the line structure 7436 if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { 7437 // This is a whole-line replace. Treated specially to make 7438 // sure line objects move the way they are supposed to. 7439 var added = [], prevLine = null; 7440 for (var i = 0, e = lines.length - 1; i < e; ++i) 7441 added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); 7442 lastLine.update(lastLine.text, hlSpans(lastHL)); 7443 if (nlines) doc.remove(from.line, nlines, callbacks); 7444 if (added.length) doc.insert(from.line, added); 7445 } else if (firstLine == lastLine) { 7446 if (lines.length == 1) { 7447 firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); 7448 } else { 7449 for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) 7450 added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); 7451 added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); 7452 firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); 7453 doc.insert(from.line + 1, added); 7454 } 7455 } else if (lines.length == 1) { 7456 firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); 7457 doc.remove(from.line + 1, nlines, callbacks); 7458 } else { 7459 var added = []; 7460 firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); 7461 lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); 7462 for (var i = 1, e = lines.length - 1; i < e; ++i) 7463 added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); 7464 if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); 7465 doc.insert(from.line + 1, added); 7466 } 7467 if (options.lineWrapping) { 7468 var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); 7469 doc.iter(from.line, from.line + lines.length, function(line) { 7470 if (line.hidden) return; 7471 var guess = Math.ceil(line.text.length / perLine) || 1; 7472 if (guess != line.height) updateLineHeight(line, guess); 7473 }); 7474 } else { 7475 doc.iter(from.line, from.line + lines.length, function(line) { 7476 var l = line.text; 7477 if (!line.hidden && l.length > maxLineLength) { 7478 maxLine = line; maxLineLength = l.length; maxLineChanged = true; 7479 recomputeMaxLength = false; 7480 } 7481 }); 7482 if (recomputeMaxLength) updateMaxLine = true; 7483 } 7484 7485 // Adjust frontier, schedule worker 7486 frontier = Math.min(frontier, from.line); 7487 startWorker(400); 7488 7489 var lendiff = lines.length - nlines - 1; 7490 // Remember that these lines changed, for updating the display 7491 changes.push({from: from.line, to: to.line + 1, diff: lendiff}); 7492 if (options.onChange) { 7493 // Normalize lines to contain only strings, since that's what 7494 // the change event handler expects 7495 for (var i = 0; i < lines.length; ++i) 7496 if (typeof lines[i] != "string") lines[i] = lines[i].text; 7497 var changeObj = {from: from, to: to, text: lines}; 7498 if (textChanged) { 7499 for (var cur = textChanged; cur.next; cur = cur.next) {} 7500 cur.next = changeObj; 7501 } else textChanged = changeObj; 7502 } 7503 7504 // Update the selection 7505 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} 7506 setSelection(clipPos(selFrom), clipPos(selTo), 7507 updateLine(sel.from.line), updateLine(sel.to.line)); 7508 } 7509 7510 function needsScrollbar() { 7511 var realHeight = doc.height * textHeight() + 2 * paddingTop(); 7512 return realHeight * .99 > scroller.offsetHeight ? realHeight : false; 7513 } 7514 7515 function updateVerticalScroll(scrollTop) { 7516 var scrollHeight = needsScrollbar(); 7517 scrollbar.style.display = scrollHeight ? "block" : "none"; 7518 if (scrollHeight) { 7519 scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px"; 7520 scrollbar.style.height = scroller.clientHeight + "px"; 7521 if (scrollTop != null) { 7522 scrollbar.scrollTop = scroller.scrollTop = scrollTop; 7523 // 'Nudge' the scrollbar to work around a Webkit bug where, 7524 // in some situations, we'd end up with a scrollbar that 7525 // reported its scrollTop (and looked) as expected, but 7526 // *behaved* as if it was still in a previous state (i.e. 7527 // couldn't scroll up, even though it appeared to be at the 7528 // bottom). 7529 if (webkit) setTimeout(function() { 7530 if (scrollbar.scrollTop != scrollTop) return; 7531 scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1); 7532 scrollbar.scrollTop = scrollTop; 7533 }, 0); 7534 } 7535 } else { 7536 sizer.style.minHeight = ""; 7537 } 7538 // Position the mover div to align with the current virtual scroll position 7539 mover.style.top = displayOffset * textHeight() + "px"; 7540 } 7541 7542 function computeMaxLength() { 7543 maxLine = getLine(0); maxLineChanged = true; 7544 var maxLineLength = maxLine.text.length; 7545 doc.iter(1, doc.size, function(line) { 7546 var l = line.text; 7547 if (!line.hidden && l.length > maxLineLength) { 7548 maxLineLength = l.length; maxLine = line; 7549 } 7550 }); 7551 updateMaxLine = false;
7552 } 7553 7554 function replaceRange(code, from, to) { 7555 from = clipPos(from); 7556 if (!to) to = from; else to = clipPos(to); 7557 code = splitLines(code); 7558 function adjustPos(pos) { 7559 if (posLess(pos, from)) return pos; 7560 if (!posLess(to, pos)) return end; 7561 var line = pos.line + code.length - (to.line - from.line) - 1; 7562 var ch = pos.ch; 7563 if (pos.line == to.line) 7564 ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); 7565 return {line: line, ch: ch}; 7566 } 7567 var end; 7568 replaceRange1(code, from, to, function(end1) { 7569 end = end1; 7570 return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; 7571 }); 7572 return end; 7573 } 7574 function replaceSelection(code, collapse) { 7575 replaceRange1(splitLines(code), sel.from, sel.to, function(end) { 7576 if (collapse == "end") return {from: end, to: end}; 7577 else if (collapse == "start") return {from: sel.from, to: sel.from}; 7578 else return {from: sel.from, to: end}; 7579 }); 7580 } 7581 function replaceRange1(code, from, to, computeSel) { 7582 var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; 7583 var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); 7584 updateLines(from, to, code, newSel.from, newSel.to); 7585 } 7586 7587 function getRange(from, to, lineSep) { 7588 var l1 = from.line, l2 = to.line; 7589 if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); 7590 var code = [getLine(l1).text.slice(from.ch)]; 7591 doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); 7592 code.push(getLine(l2).text.slice(0, to.ch)); 7593 return code.join(lineSep || "\n"); 7594 } 7595 function getSelection(lineSep) { 7596 return getRange(sel.from, sel.to, lineSep); 7597 } 7598 7599 function slowPoll() { 7600 if (pollingFast) return; 7601 poll.set(options.pollInterval, function() { 7602 readInput(); 7603 if (focused) slowPoll(); 7604 }); 7605 } 7606 function fastPoll() { 7607 var missed = false; 7608 pollingFast = true; 7609 function p() { 7610 var changed = readInput(); 7611 if (!changed && !missed) {missed = true; poll.set(60, p);} 7612 else {pollingFast = false; slowPoll();} 7613 } 7614 poll.set(20, p); 7615 } 7616 7617 // Previnput is a hack to work with IME. If we reset the textarea 7618 // on every change, that breaks IME. So we look for changes 7619 // compared to the previous content instead. (Modern browsers have 7620 // events that indicate IME taking place, but these are not widely 7621 // supported or compatible enough yet to rely on.) 7622 var prevInput = ""; 7623 function readInput() { 7624 if (!focused || hasSelection(input) || options.readOnly) return false; 7625 var text = input.value; 7626 if (text == prevInput) return false; 7627 if (!nestedOperation) startOperation(); 7628 shiftSelecting = null; 7629 var same = 0, l = Math.min(prevInput.length, text.length); 7630 while (same < l && prevInput[same] == text[same]) ++same; 7631 if (same < prevInput.length) 7632 sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; 7633 else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming) 7634 sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; 7635 replaceSelection(text.slice(same), "end"); 7636 if (text.length > 1000) { input.value = prevInput = ""; } 7637 else prevInput = text; 7638 if (!nestedOperation) endOperation(); 7639 pasteIncoming = false; 7640 return true; 7641 } 7642 function resetInput(user) { 7643 if (!posEq(sel.from, sel.to)) { 7644 prevInput = ""; 7645 input.value = getSelection(); 7646 if (focused) selectInput(input); 7647 } else if (user) prevInput = input.value = ""; 7648 } 7649 7650 function focusInput() { 7651 if (options.readOnly != "nocursor" && (ie_lt9 || document.activeElement != input)) 7652 input.focus(); 7653 } 7654 7655 function scrollCursorIntoView() { 7656 var coords = calculateCursorCoords(); 7657 scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); 7658 if (!focused) return; 7659 var box = sizer.getBoundingClientRect(), doScroll = null; 7660 if (coords.y + box.top < 0) doScroll = true; 7661 else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
7662 if (doScroll != null) { 7663 var hidden = cursor.style.display == "none"; 7664 if (hidden) { 7665 cursor.style.display = ""; 7666 cursor.style.left = coords.x + "px"; 7667 cursor.style.top = (coords.y - displayOffset) + "px"; 7668 } 7669 cursor.scrollIntoView(doScroll); 7670 if (hidden) cursor.style.display = "none"; 7671 } 7672 } 7673 function calculateCursorCoords() { 7674 var cursor = localCoords(sel.inverted ? sel.from : sel.to); 7675 var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; 7676 return {x: x, y: cursor.y, yBot: cursor.yBot}; 7677 } 7678 function scrollIntoView(x1, y1, x2, y2) { 7679 var scrollPos = calculateScrollPos(x1, y1, x2, y2); 7680 if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;} 7681 if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;} 7682 } 7683 function calculateScrollPos(x1, y1, x2, y2) { 7684 var pl = paddingLeft(), pt = paddingTop(); 7685 y1 += pt; y2 += pt; x1 += pl; x2 += pl; 7686 var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; 7687 var docBottom = needsScrollbar() || Infinity; 7688 var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; 7689 if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); 7690 else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; 7691 7692 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; 7693 var gutterw = options.fixedGutter ? gutter.clientWidth : 0; 7694 var atLeft = x1 < gutterw + pl + 10; 7695 if (x1 < screenleft + gutterw || atLeft) { 7696 if (atLeft) x1 = 0; 7697 result.scrollLeft = Math.max(0, x1 - 10 - gutterw); 7698 } else if (x2 > screenw + screenleft - 3) { 7699 result.scrollLeft = x2 + 10 - screenw; 7700 } 7701 return result; 7702 } 7703 7704 function visibleLines(scrollTop) { 7705 var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); 7706 var fromHeight = Math.max(0, Math.floor(top / lh)); 7707 var toHeight = Math.ceil((top + scroller.clientHeight) / lh); 7708 return {from: lineAtHeight(doc, fromHeight), 7709 to: lineAtHeight(doc, toHeight)}; 7710 } 7711 // Uses a set of changes plus the current scroll position to 7712 // determine which DOM updates have to be made, and makes the 7713 // updates. 7714 function updateDisplay(changes, suppressCallback, scrollTop) { 7715 if (!scroller.clientWidth) { 7716 showingFrom = showingTo = displayOffset = 0; 7717 return; 7718 } 7719 // Compute the new visible window 7720 // If scrollTop is specified, use that to determine which lines 7721 // to render instead of the current scrollbar position. 7722 var visible = visibleLines(scrollTop); 7723 // Bail out if the visible area is already rendered and nothing changed. 7724 if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { 7725 updateVerticalScroll(scrollTop); 7726 return; 7727 } 7728 var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); 7729 if (showingFrom < from && from - showingFrom < 20) from = showingFrom; 7730 if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); 7731 7732 // Create a range of theoretically intact lines, and punch holes 7733 // in that using the change info. 7734 var intact = changes === true ? [] : 7735 computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); 7736 // Clip off the parts that won't be visible 7737 var intactLines = 0; 7738 for (var i = 0; i < intact.length; ++i) { 7739 var range = intact[i]; 7740 if (range.from < from) {range.domStart += (from - range.from); range.from = from;} 7741 if (range.to > to) range.to = to; 7742 if (range.from >= range.to) intact.splice(i--, 1); 7743 else intactLines += range.to - range.from; 7744 } 7745 if (intactLines == to - from && from == showingFrom && to == showingTo) { 7746 updateVerticalScroll(scrollTop); 7747 return; 7748 } 7749 intact.sort(function(a, b) {return a.domStart - b.domStart;}); 7750 7751 var th = textHeight(), gutterDisplay = gutter.style.display; 7752 lineDiv.style.display = "none"; 7753 patchDisplay(from, to, intact); 7754 lineDiv.style.display = gutter.style.display = ""; 7755 7756 var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; 7757 // This is just a bogus formula that detects when the editor is 7758 // resized or the font size changes. 7759 if (different) lastSizeC = scroller.clientHeight + th; 7760 if (from != showingFrom || to != showingTo && options.onViewportChange) 7761 setTimeout(function(){ 7762 if (options.onViewportChange) options.onViewportChange(instance, from, to); 7763 }); 7764 showingFrom = from; showingTo = to; 7765 displayOffset = heightAtLine(doc, from); 7766 startWorker(100); 7767 7768 // Since this is all rather error prone, it is honoured with the 7769 // only assertion in the whole file. 7770 if (lineDiv.childNodes.length != showingTo - showingFrom) 7771 throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + 7772 " nodes=" + lineDiv.childNodes.length); 7773 7774 function checkHeights() { 7775 var curNode = lineDiv.firstChild, heightChanged = false;
7776 doc.iter(showingFrom, showingTo, function(line) { 7777 // Work around bizarro IE7 bug where, sometimes, our curNode 7778 // is magically replaced with a new node in the DOM, leaving 7779 // us with a reference to an orphan (nextSibling-less) node. 7780 if (!curNode) return; 7781 if (!line.hidden) { 7782 var height = Math.round(curNode.offsetHeight / th) || 1; 7783 if (line.height != height) { 7784 updateLineHeight(line, height); 7785 gutterDirty = heightChanged = true; 7786 } 7787 } 7788 curNode = curNode.nextSibling; 7789 }); 7790 return heightChanged; 7791 } 7792 7793 if (options.lineWrapping) checkHeights(); 7794 7795 gutter.style.display = gutterDisplay; 7796 if (different || gutterDirty) { 7797 // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. 7798 updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); 7799 } 7800 updateVerticalScroll(scrollTop); 7801 updateSelection(); 7802 if (!suppressCallback && options.onUpdate) options.onUpdate(instance); 7803 return true; 7804 } 7805 7806 function computeIntact(intact, changes) { 7807 for (var i = 0, l = changes.length || 0; i < l; ++i) { 7808 var change = changes[i], intact2 = [], diff = change.diff || 0; 7809 for (var j = 0, l2 = intact.length; j < l2; ++j) { 7810 var range = intact[j]; 7811 if (change.to <= range.from && change.diff) 7812 intact2.push({from: range.from + diff, to: range.to + diff, 7813 domStart: range.domStart}); 7814 else if (change.to <= range.from || change.from >= range.to) 7815 intact2.push(range); 7816 else { 7817 if (change.from > range.from) 7818 intact2.push({from: range.from, to: change.from, domStart: range.domStart}); 7819 if (change.to < range.to) 7820 intact2.push({from: change.to + diff, to: range.to + diff, 7821 domStart: range.domStart + (change.to - range.from)}); 7822 } 7823 } 7824 intact = intact2; 7825 } 7826 return intact; 7827 } 7828 7829 function patchDisplay(from, to, intact) { 7830 function killNode(node) { 7831 var tmp = node.nextSibling; 7832 node.parentNode.removeChild(node); 7833 return tmp; 7834 } 7835 // The first pass removes the DOM nodes that aren't intact. 7836 if (!intact.length) removeChildren(lineDiv); 7837 else { 7838 var domPos = 0, curNode = lineDiv.firstChild, n; 7839 for (var i = 0; i < intact.length; ++i) { 7840 var cur = intact[i]; 7841 while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} 7842 for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} 7843 } 7844 while (curNode) curNode = killNode(curNode); 7845 } 7846 // This pass fills in the lines that actually changed. 7847 var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; 7848 doc.iter(from, to, function(line) { 7849 if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); 7850 if (!nextIntact || nextIntact.from > j) { 7851 if (line.hidden) var lineElement = elt("pre"); 7852 else { 7853 var lineElement = lineContent(line); 7854 if (line.className) lineElement.className = line.className; 7855 // Kludge to make sure the styled element lies behind the selection (by z-index) 7856 if (line.bgClassName) { 7857 var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"); 7858 lineElement = elt("div", [pre, lineElement], null, "position: relative"); 7859 } 7860 } 7861 lineDiv.insertBefore(lineElement, curNode); 7862 } else { 7863 curNode = curNode.nextSibling; 7864 } 7865 ++j; 7866 }); 7867 } 7868 7869 function updateGutter() { 7870 if (!options.gutter && !options.lineNumbers) return; 7871 var hText = mover.offsetHeight, hEditor = scroller.clientHeight; 7872 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; 7873 var fragment = document.createDocumentFragment(), i = showingFrom, normalNode; 7874 doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { 7875 if (line.hidden) { 7876 fragment.appendChild(elt("pre")); 7877 } else { 7878 var marker = line.gutterMarker; 7879 var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null; 7880 if (marker && marker.text) 7881 text = marker.text.replace("%N%", text != null ? text : ""); 7882 else if (text == null) 7883 text = "\u00a0"; 7884 var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); 7885 markerElement.innerHTML = text; 7886 for (var j = 1;
7886 j < line.height; ++j) { 7887 markerElement.appendChild(elt("br")); 7888 markerElement.appendChild(document.createTextNode("\u00a0")); 7889 } 7890 if (!marker) normalNode = i; 7891 } 7892 ++i; 7893 }); 7894 gutter.style.display = "none"; 7895 removeChildrenAndAdd(gutterText, fragment); 7896 // Make sure scrolling doesn't cause number gutter size to pop 7897 if (normalNode != null && options.lineNumbers) { 7898 var node = gutterText.childNodes[normalNode - showingFrom]; 7899 var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; 7900 while (val.length + pad.length < minwidth) pad += "\u00a0"; 7901 if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); 7902 } 7903 gutter.style.display = ""; 7904 var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; 7905 lineSpace.style.marginLeft = gutter.offsetWidth + "px"; 7906 gutterDirty = false; 7907 return resized; 7908 } 7909 function updateSelection() { 7910 var collapsed = posEq(sel.from, sel.to); 7911 var fromPos = localCoords(sel.from, true); 7912 var toPos = collapsed ? fromPos : localCoords(sel.to, true); 7913 var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); 7914 var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); 7915 inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; 7916 inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; 7917 if (collapsed || options.showCursorWhenSelecting) { 7918 cursor.style.top = headPos.y + "px"; 7919 cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; 7920 cursor.style.display = ""; 7921 } else { 7922 cursor.style.display = "none"; 7923 } 7924 if (!collapsed) { 7925 var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); 7926 var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; 7927 var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; 7928 var add = function(left, top, right, height) { 7929 var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" 7930 : "right: " + (right - 1) + "px"; 7931 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + 7932 "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); 7933 }; 7934 if (sel.from.ch && fromPos.y >= 0) { 7935 var right = sameLine ? clientWidth - toPos.x : 0; 7936 add(fromPos.x, fromPos.y, right, th); 7937 } 7938 var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); 7939 var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; 7940 if (middleHeight > 0.2 * th) 7941 add(0, middleStart, 0, middleHeight); 7942 if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) 7943 add(0, toPos.y, clientWidth - toPos.x, th); 7944 removeChildrenAndAdd(selectionDiv, fragment); 7945 selectionDiv.style.display = ""; 7946 } else { 7947 selectionDiv.style.display = "none"; 7948 } 7949 } 7950 7951 function setShift(val) { 7952 if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); 7953 else shiftSelecting = null; 7954 } 7955 function setSelectionUser(from, to) { 7956 var sh = shiftSelecting && clipPos(shiftSelecting); 7957 if (sh) { 7958 if (posLess(sh, from)) from = sh; 7959 else if (posLess(to, sh)) to = sh; 7960 } 7961 setSelection(from, to); 7962 userSelChange = true; 7963 } 7964 // Update the selection. Last two args are only used by 7965 // updateLines, since they have to be expressed in the line 7966 // numbers before the update. 7967 function setSelection(from, to, oldFrom, oldTo) { 7968 goalColumn = null; 7969 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} 7970 if (posEq(sel.from, from) && posEq(sel.to, to)) return; 7971 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} 7972 7973 // Skip over hidden lines. 7974 if (from.line != oldFrom) { 7975 var from1 = skipHidden(from, oldFrom, sel.from.ch); 7976 // If there is no non-hidden line left, force visibility on current line 7977 if (!from1) setLineHidden(from.line, false); 7978 else from = from1; 7979 } 7980 if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); 7981 7982 if (posEq(from, to)) sel.inverted = false;
7983 else if (posEq(from, sel.to)) sel.inverted = false; 7984 else if (posEq(to, sel.from)) sel.inverted = true; 7985 7986 if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { 7987 var head = sel.inverted ? from : to; 7988 if (head.line != sel.from.line && sel.from.line < doc.size) { 7989 var oldLine = getLine(sel.from.line); 7990 if (/^\s+$/.test(oldLine.text)) 7991 setTimeout(operation(function() { 7992 if (oldLine.parent && /^\s+$/.test(oldLine.text)) { 7993 var no = lineNo(oldLine); 7994 replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); 7995 } 7996 }, 10)); 7997 } 7998 } 7999 8000 sel.from = from; sel.to = to; 8001 selectionChanged = true; 8002 } 8003 function skipHidden(pos, oldLine, oldCh) { 8004 function getNonHidden(dir) { 8005 var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; 8006 while (lNo != end) { 8007 var line = getLine(lNo); 8008 if (!line.hidden) { 8009 var ch = pos.ch; 8010 if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; 8011 return {line: lNo, ch: ch}; 8012 } 8013 lNo += dir; 8014 } 8015 } 8016 var line = getLine(pos.line); 8017 var toEnd = pos.ch == line.text.length && pos.ch != oldCh; 8018 if (!line.hidden) return pos; 8019 if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); 8020 else return getNonHidden(-1) || getNonHidden(1); 8021 } 8022 function setCursor(line, ch, user) { 8023 var pos = clipPos({line: line, ch: ch || 0}); 8024 (user ? setSelectionUser : setSelection)(pos, pos); 8025 } 8026 8027 function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} 8028 function clipPos(pos) { 8029 if (pos.line < 0) return {line: 0, ch: 0}; 8030 if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; 8031 var ch = pos.ch, linelen = getLine(pos.line).text.length; 8032 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; 8033 else if (ch < 0) return {line: pos.line, ch: 0}; 8034 else return pos; 8035 } 8036 8037 function findPosH(dir, unit) { 8038 var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; 8039 var lineObj = getLine(line); 8040 function findNextLine() { 8041 for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { 8042 var lo = getLine(l); 8043 if (!lo.hidden) { line = l; lineObj = lo; return true; } 8044 } 8045 } 8046 function moveOnce(boundToLine) { 8047 if (ch == (dir < 0 ? 0 : lineObj.text.length)) { 8048 if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; 8049 else return false; 8050 } else ch += dir; 8051 return true; 8052 } 8053 if (unit == "char") moveOnce(); 8054 else if (unit == "column") moveOnce(true); 8055 else if (unit == "word") { 8056 var sawWord = false; 8057 for (;;) { 8058 if (dir < 0) if (!moveOnce()) break; 8059 if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; 8060 else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} 8061 if (dir > 0) if (!moveOnce()) break; 8062 } 8063 } 8064 return {line: line, ch: ch}; 8065 } 8066 function moveH(dir, unit) { 8067 var pos = dir < 0 ? sel.from : sel.to; 8068 if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); 8069 setCursor(pos.line, pos.ch, true); 8070 } 8071 function deleteH(dir, unit) { 8072 if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); 8073 else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); 8074 else replaceRange("", sel.from, findPosH(dir, unit)); 8075 userSelChange = true; 8076 } 8077 function moveV(dir, unit) { 8078 var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); 8079 if (goalColumn != null) pos.x = goalColumn; 8080 if (unit == "page") { 8081 var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); 8082 var target = coordsChar(pos.x, pos.y + screen * dir); 8083 } else if (unit == "line") { 8084 var th = textHeight(); 8085 var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); 8086 } 8087 if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; 8088 setCursor(target.line, target.ch, true); 8089 goalColumn = pos.x; 8090 } 8091 8092 function findWordAt(pos) { 8093 var line = getLine(pos.line).text; 8094 var start = pos.ch, end = pos.ch; 8095 if (line) { 8096 if (pos.after === false || end == line.length) --start; else ++end; 8097 var startChar = line.charAt(start); 8098 var check = isWordChar(startChar) ? isWordChar : 8099 /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : 8100 function(ch) {return !/\s/.test(ch) && isWordChar(ch);}; 8101 while (start > 0 && check(line.charAt(start - 1))) --start; 8102 while (end < line.length && check(line.charAt(end))) ++end; 8103 } 8104 return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; 8105 } 8106 function selectLine(line) { 8107 setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); 8108 } 8109 function indentSelected(mode) { 8110 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); 8111 var e = sel.to.line - (sel.to.ch ? 0 : 1); 8112 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); 8113 } 8114 8115 function indentLine(n, how) { 8116 if (!how) how = "add"; 8117 if (how == "smart") { 8118 if (!mode.indent) how = "prev"; 8119 else var state = getStateBefore(n); 8120 } 8121 8122 var line = getLine(n), curSpace = line.indentation(options.tabSize), 8123 curSpaceString = line.text.match(/^\s*/)[0], indentation; 8124 if (how == "smart") { 8125 indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); 8126 if (indentation == Pass) how = "prev"; 8127 } 8128 if (how == "prev") { 8129 if (n) indentation = getLine(n-1).indentation(options.tabSize); 8130 else indentation = 0; 8131 } 8132 else if (how == "add") indentation = curSpace + options.indentUnit; 8133 else if (how == "subtract") indentation = curSpace - options.indentUnit; 8134 indentation = Math.max(0, indentation); 8135 var diff = indentation - curSpace; 8136
8137 var indentString = "", pos = 0; 8138 if (options.indentWithTabs) 8139 for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} 8140 if (pos < indentation) indentString += spaceStr(indentation - pos); 8141 8142 if (indentString != curSpaceString) 8143 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); 8144 line.stateAfter = null; 8145 } 8146 8147 function loadMode() { 8148 mode = CodeMirror.getMode(options, options.mode); 8149 doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); 8150 frontier = 0; 8151 startWorker(100); 8152 } 8153 function gutterChanged() { 8154 var visible = options.gutter || options.lineNumbers; 8155 gutter.style.display = visible ? "" : "none"; 8156 if (visible) gutterDirty = true; 8157 else lineDiv.parentNode.style.marginLeft = 0; 8158 } 8159 function wrappingChanged(from, to) { 8160 if (options.lineWrapping) { 8161 wrapper.className += " CodeMirror-wrap"; 8162 var perLine = scroller.clientWidth / charWidth() - 3; 8163 doc.iter(0, doc.size, function(line) { 8164 if (line.hidden) return; 8165 var guess = Math.ceil(line.text.length / perLine) || 1; 8166 if (guess != 1) updateLineHeight(line, guess); 8167 }); 8168 lineSpace.style.minWidth = widthForcer.style.left = ""; 8169 } else { 8170 wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); 8171 computeMaxLength(); 8172 doc.iter(0, doc.size, function(line) { 8173 if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); 8174 }); 8175 } 8176 changes.push({from: 0, to: doc.size}); 8177 } 8178 function themeChanged() { 8179 scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + 8180 options.theme.replace(/(^|\s)\s*/g, " cm-s-"); 8181 } 8182 function keyMapChanged() { 8183 var style = keyMap[options.keyMap].style; 8184 wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + 8185 (style ? " cm-keymap-" + style : ""); 8186 } 8187 8188 function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } 8189 TextMarker.prototype.clear = operation(function() { 8190 var min, max; 8191 for (var i = 0; i < this.lines.length; ++i) { 8192 var line = this.lines[i]; 8193 var span = getMarkedSpanFor(line.markedSpans, this); 8194 if (span.from != null) min = lineNo(line); 8195 if (span.to != null) max = lineNo(line); 8196 line.markedSpans = removeMarkedSpan(line.markedSpans, span); 8197 } 8198 if (min != null) changes.push({from: min, to: max + 1}); 8199 this.lines.length = 0; 8200 this.explicitlyCleared = true; 8201 }); 8202 TextMarker.prototype.find = function() { 8203 var from, to; 8204 for (var i = 0; i < this.lines.length; ++i) { 8205 var line = this.lines[i]; 8206 var span = getMarkedSpanFor(line.markedSpans, this); 8207 if (span.from != null || span.to != null) { 8208 var found = lineNo(line); 8209 if (span.from != null) from = {line: found, ch: span.from}; 8210 if (span.to != null) to = {line: found, ch: span.to}; 8211 } 8212 } 8213 if (this.type == "bookmark") return from; 8214 return from && {from: from, to: to}; 8215 }; 8216 8217 function markText(from, to, className, options) { 8218 from = clipPos(from); to = clipPos(to); 8219 var marker = new TextMarker("range", className); 8220 if (options) for (var opt in options) if (options.hasOwnProperty(opt)) 8221 marker[opt] = options[opt]; 8222 var curLine = from.line; 8223 doc.iter(curLine, to.line + 1, function(line) { 8224 var span = {from: curLine == from.line ? from.ch : null, 8225 to: curLine == to.line ? to.ch : null, 8226 marker: marker}; 8227 line.markedSpans = (line.markedSpans || []).concat([span]); 8228 marker.lines.push(line); 8229 ++curLine; 8230 }); 8231 changes.push({from: from.line, to: to.line + 1}); 8232 return marker; 8233 } 8234 8235 function setBookmark(pos) { 8236 pos = clipPos(pos); 8237 var marker = new TextMarker("bookmark"), line = getLine(pos.line); 8238 history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); 8239 var span = {from: pos.ch, to: pos.ch, marker: marker}; 8240 line.markedSpans = (line.markedSpans || []).concat([span]); 8241 marker.lines.push(line); 8242 return marker; 8243 } 8244 8245 function findMarksAt(pos) { 8246 pos = clipPos(pos); 8247 var markers = [], spans = getLine(pos.line).markedSpans; 8248 if (spans) for (var i = 0; i < spans.length; ++i) { 8249 var span = spans[i]; 8250 if ((span.from == null || span.from <= pos.ch) && 8251 (span.to == null || span.to >= pos.ch)) 8252 markers.push(span.marker); 8253 } 8254 return markers; 8255 } 8256 8257 function addGutterMarker(line, text, className) { 8258 if (typeof line == "number") line = getLine(clipLine(line)); 8259 line.gutterMarker = {text: text, style: className}; 8260 gutterDirty = true; 8261 return line; 8262 } 8263 function removeGutterMarker(line) { 8264 if (typeof line == "number") line = getLine(clipLine(line)); 8265 line.gutterMarker = null; 8266 gutterDirty = true; 8267 } 8268 8269 function changeLine(handle, op) { 8270 var no = handle, line = handle; 8271 if (typeof handle == "number") line = getLine(clipLine(handle)); 8272 else no = lineNo(handle); 8273 if (no == null) return null; 8274 if (op(line, no)) changes.push({from: no, to: no + 1}); 8275 else return null; 8276 return line; 8277 } 8278 function setLineClass(handle, className, bgClassName) { 8279 return changeLine(handle, function(line) { 8280 if (line.className != className || line.bgClassName != bgClassName) { 8281 line.className = className; 8282 line.bgClassName = bgClassName; 8283 return true; 8284 } 8285 }); 8286 } 8287 function setLineHidden(handle, hidden) { 8288 return changeLine(handle, function(line, no) { 8289 if (line.hidden != hidden) { 8290 line.hidden = hidden; 8291 if (!options.lineWrapping) { 8292 if (hidden && line.text.length == maxLine.text.length) { 8293 updateMaxLine = true; 8294 } else if (!hidden && line.text.length > maxLine.text.length) { 8295 maxLine = line; updateMaxLine = false;
8296 } 8297 } 8298 updateLineHeight(line, hidden ? 0 : 1); 8299 var fline = sel.from.line, tline = sel.to.line; 8300 if (hidden && (fline == no || tline == no)) { 8301 var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; 8302 var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; 8303 // Can't hide the last visible line, we'd have no place to put the cursor 8304 if (!to) return; 8305 setSelection(from, to); 8306 } 8307 return (gutterDirty = true); 8308 } 8309 }); 8310 } 8311 8312 function lineInfo(line) { 8313 if (typeof line == "number") { 8314 if (!isLine(line)) return null; 8315 var n = line; 8316 line = getLine(line); 8317 if (!line) return null; 8318 } else { 8319 var n = lineNo(line); 8320 if (n == null) return null; 8321 } 8322 var marker = line.gutterMarker; 8323 return {line: n, handle: line, text: line.text, markerText: marker && marker.text, 8324 markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; 8325 } 8326 8327 function measureLine(line, ch) { 8328 if (ch == 0) return {top: 0, left: 0}; 8329 var pre = lineContent(line, ch); 8330 removeChildrenAndAdd(measure, pre); 8331 var anchor = pre.anchor; 8332 var top = anchor.offsetTop, left = anchor.offsetLeft; 8333 // Older IEs report zero offsets for spans directly after a wrap 8334 if (ie && top == 0 && left == 0) { 8335 var backup = elt("span", "x"); 8336 anchor.parentNode.insertBefore(backup, anchor.nextSibling); 8337 top = backup.offsetTop; 8338 } 8339 return {top: top, left: left}; 8340 } 8341 function localCoords(pos, inLineWrap) { 8342 var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); 8343 if (pos.ch == 0) x = 0; 8344 else { 8345 var sp = measureLine(getLine(pos.line), pos.ch); 8346 x = sp.left; 8347 if (options.lineWrapping) y += Math.max(0, sp.top); 8348 } 8349 return {x: x, y: y, yBot: y + lh}; 8350 } 8351 // Coords must be lineSpace-local 8352 function coordsChar(x, y) { 8353 var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); 8354 if (heightPos < 0) return {line: 0, ch: 0}; 8355 var lineNo = lineAtHeight(doc, heightPos); 8356 if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; 8357 var lineObj = getLine(lineNo), text = lineObj.text; 8358 var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; 8359 if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; 8360 var wrongLine = false; 8361 function getX(len) { 8362 var sp = measureLine(lineObj, len); 8363 if (tw) { 8364 var off = Math.round(sp.top / th); 8365 wrongLine = off != innerOff; 8366 return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); 8367 } 8368 return sp.left; 8369 } 8370 var from = 0, fromX = 0, to = text.length, toX; 8371 // Guess a suitable upper bound for our search. 8372 var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); 8373 for (;;) { 8374 var estX = getX(estimated); 8375 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); 8376 else {toX = estX; to = estimated; break;} 8377 } 8378 if (x > toX) return {line: lineNo, ch: to}; 8379 // Try to guess a suitable lower bound as well. 8380 estimated = Math.floor(to * 0.8); estX = getX(estimated); 8381 if (estX < x) {from = estimated; fromX = estX;} 8382 // Do a binary search between these bounds. 8383 for (;;) { 8384 if (to - from <= 1) { 8385 var after = x - fromX < toX - x; 8386 return {line: lineNo, ch: after ? from : to, after: after}; 8387 } 8388 var middle = Math.ceil((from + to) / 2), middleX = getX(middle); 8389 if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } 8390 else {from = middle; fromX = middleX;} 8391 } 8392 } 8393 function pageCoords(pos) { 8394 var local = localCoords(pos, true), off = eltOffset(lineSpace); 8395 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; 8396 } 8397 8398 var cachedHeight, cachedHeightFor, measurePre; 8399 function textHeight() { 8400 if (measurePre == null) { 8401 measurePre = elt("pre"); 8402 for (var i = 0; i < 49; ++i) { 8403 measurePre.appendChild(document.createTextNode("x")); 8404 measurePre.appendChild(elt("br")); 8405 } 8406 measurePre.appendChild(document.createTextNode("x")); 8407 } 8408 var offsetHeight = lineDiv.clientHeight;
8409 if (offsetHeight == cachedHeightFor) return cachedHeight; 8410 cachedHeightFor = offsetHeight; 8411 removeChildrenAndAdd(measure, measurePre.cloneNode(true)); 8412 cachedHeight = measure.firstChild.offsetHeight / 50 || 1; 8413 removeChildren(measure); 8414 return cachedHeight; 8415 } 8416 var cachedWidth, cachedWidthFor = 0; 8417 function charWidth() { 8418 if (scroller.clientWidth == cachedWidthFor) return cachedWidth; 8419 cachedWidthFor = scroller.clientWidth; 8420 var anchor = elt("span", "x"); 8421 var pre = elt("pre", [anchor]); 8422 removeChildrenAndAdd(measure, pre); 8423 return (cachedWidth = anchor.offsetWidth || 10); 8424 } 8425 function paddingTop() {return lineSpace.offsetTop;} 8426 function paddingLeft() {return lineSpace.offsetLeft;} 8427 8428 function posFromMouse(e, liberal) { 8429 var offW = eltOffset(scroller, true), x, y; 8430 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 8431 try { x = e.clientX; y = e.clientY; } catch (e) { return null; } 8432 // This is a mess of a heuristic to try and determine whether a 8433 // scroll-bar was clicked or not, and to return null if one was 8434 // (and !liberal). 8435 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) 8436 return null; 8437 var offL = eltOffset(lineSpace, true); 8438 return coordsChar(x - offL.left, y - offL.top); 8439 } 8440 var detectingSelectAll; 8441 function onContextMenu(e) { 8442 var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; 8443 if (!pos || opera) return; // Opera is difficult. 8444 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) 8445 operation(setCursor)(pos.line, pos.ch); 8446 8447 var oldCSS = input.style.cssText; 8448 inputDiv.style.position = "absolute"; 8449 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + 8450 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + 8451 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; 8452 focusInput(); 8453 resetInput(true); 8454 // Adds "Select all" to context menu in FF 8455 if (posEq(sel.from, sel.to)) input.value = prevInput = " "; 8456 8457 function rehide() { 8458 inputDiv.style.position = "relative"; 8459 input.style.cssText = oldCSS; 8460 if (ie_lt9) scrollbar.scrollTop = scrollPos; 8461 slowPoll(); 8462 8463 // Try to detect the user choosing select-all 8464 if (input.selectionStart != null) { 8465 clearTimeout(detectingSelectAll); 8466 var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; 8467 prevInput = " "; 8468 input.selectionStart = 1; input.selectionEnd = extval.length; 8469 detectingSelectAll = setTimeout(function poll(){ 8470 if (prevInput == " " && input.selectionStart == 0) 8471 operation(commands.selectAll)(instance); 8472 else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); 8473 else resetInput(); 8474 }, 200); 8475 } 8476 } 8477 8478 if (gecko) { 8479 e_stop(e); 8480 var mouseup = connect(window, "mouseup", function() { 8481 mouseup(); 8482 setTimeout(rehide, 20); 8483 }, true); 8484 } else { 8485 setTimeout(rehide, 50); 8486 } 8487 } 8488 8489 // Cursor-blinking 8490 function restartBlink() { 8491 clearInterval(blinker); 8492 var on = true; 8493 cursor.style.visibility = ""; 8494 blinker = setInterval(function() { 8495 cursor.style.visibility = (on = !on) ? "" : "hidden"; 8496 }, options.cursorBlinkRate); 8497 } 8498 8499 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 8500 function matchBrackets(autoclear) { 8501 var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; 8502 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 8503 if (!match) return; 8504 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; 8505 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) 8506 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} 8507 8508 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; 8509 function scan(line, from, to) { 8510 if (!line.text) return; 8511 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; 8512 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { 8513 var text = st[i]; 8514 if (st[i+1] != style) {pos += d * text.length; continue;} 8515 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { 8516 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { 8517 var match = matching[cur]; 8518 if (match.charAt(1) == ">" == forward) stack.push(cur); 8519 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; 8520 else if (!stack.length) return {pos: pos, match: true}; 8521 } 8522 } 8523 } 8524 } 8525 for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { 8526 var line = getLine(i), first = i == head.line; 8527 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); 8528 if (found) break; 8529 } 8530 if (!found) found = {pos: null, match: false}; 8531 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 8532 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), 8533 two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); 8534 var clear = operation(function(){one.clear(); two && two.clear();}); 8535 if (autoclear) setTimeout(clear, 800); 8536 else bracketHighlighted = clear; 8537 } 8538 8539 // Finds the line to start with when starting a parse. Tries to 8540 // find a line with a stateAfter, so that it can start with a 8541 // valid state. If that fails, it returns the line with the 8542 // smallest indentation, which tends to need the least context to 8543 // parse correctly. 8544 function findStartLine(n) { 8545 var minindent, minline; 8546 for (var search = n, lim = n - 40; search > lim; --search) { 8547 if (search == 0) return 0; 8548 var line = getLine(search-1); 8549 if (line.stateAfter) return search; 8550 var indented = line.indentation(options.tabSize); 8551 if (minline == null || minindent > indented) { 8552 minline = search - 1; 8553 minindent = indented; 8554 } 8555 } 8556 return minline; 8557 } 8558 function getStateBefore(n) { 8559 var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; 8560 if (!state) state = startState(mode); 8561 else state = copyState(mode, state); 8562 doc.iter(pos, n, function(line) { 8563 line.process(mode, state, options.tabSize); 8564 line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; 8565 }); 8566 return state; 8567 } 8568 function highlightWorker() { 8569 if (frontier >= showingTo) return; 8570 var end = +new Date + options.workTime, state = copyState(mode, get
8570StateBefore(frontier)); 8571 var startFrontier = frontier; 8572 doc.iter(frontier, showingTo, function(line) { 8573 if (frontier >= showingFrom) { // Visible 8574 line.highlight(mode, state, options.tabSize); 8575 line.stateAfter = copyState(mode, state); 8576 } else { 8577 line.process(mode, state, options.tabSize); 8578 line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; 8579 } 8580 ++frontier; 8581 if (+new Date > end) { 8582 startWorker(options.workDelay); 8583 return true; 8584 } 8585 }); 8586 if (showingTo > startFrontier && frontier >= showingFrom) 8587 operation(function() {changes.push({from: startFrontier, to: frontier});})(); 8588 } 8589 function startWorker(time) { 8590 if (frontier < showingTo) 8591 highlight.set(time, highlightWorker); 8592 } 8593 8594 // Operations are used to wrap changes in such a way that each 8595 // change won't have to update the cursor and display (which would 8596 // be awkward, slow, and error-prone), but instead updates are 8597 // batched and then all combined and executed at once. 8598 function startOperation() { 8599 updateInput = userSelChange = textChanged = null; 8600 changes = []; selectionChanged = false; callbacks = []; 8601 } 8602 function endOperation() { 8603 if (updateMaxLine) computeMaxLength(); 8604 if (maxLineChanged && !options.lineWrapping) { 8605 var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; 8606 if (!ie_lt8) { 8607 widthForcer.style.left = left + "px"; 8608 lineSpace.style.minWidth = (left + cursorWidth) + "px"; 8609 } 8610 maxLineChanged = false; 8611 } 8612 var newScrollPos, updated; 8613 if (selectionChanged) { 8614 var coords = calculateCursorCoords(); 8615 newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); 8616 } 8617 if (changes.length || newScrollPos && newScrollPos.scrollTop != null) 8618 updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); 8619 if (!updated) { 8620 if (selectionChanged) updateSelection(); 8621 if (gutterDirty) updateGutter(); 8622 } 8623 if (newScrollPos) scrollCursorIntoView(); 8624 if (selectionChanged) restartBlink(); 8625 8626 if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) 8627 resetInput(userSelChange); 8628 8629 if (selectionChanged && options.matchBrackets) 8630 setTimeout(operation(function() { 8631 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} 8632 if (posEq(sel.from, sel.to)) matchBrackets(false); 8633 }), 20); 8634 var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks 8635 if (textChanged && options.onChange && instance) 8636 options.onChange(instance, textChanged); 8637 if (sc && options.onCursorActivity) 8638 options.onCursorActivity(instance); 8639 for (var i = 0; i < cbs.length; ++i) cbs[i](instance); 8640 if (updated && options.onUpdate) options.onUpdate(instance); 8641 } 8642 var nestedOperation = 0; 8643 function operation(f) { 8644 return function() { 8645 if (!nestedOperation++) startOperation(); 8646 try {var result = f.apply(this, arguments);} 8647 finally {if (!--nestedOperation) endOperation();} 8648 return result; 8649 }; 8650 } 8651 8652 function compoundChange(f) { 8653 history.startCompound(); 8654 try { return f(); } finally { history.endCompound(); } 8655 } 8656 8657 for (var ext in extensions) 8658 if (extensions.propertyIsEnumerable(ext) && 8659 !instance.propertyIsEnumerable(ext)) 8660 instance[ext] = extensions[ext]; 8661 for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); 8662 return instance; 8663 } // (end of function CodeMirror) 8664 8665 // The default configuration options. 8666 CodeMirror.defaults = { 8667 value: "", 8668 mode: null, 8669 theme: "default", 8670 indentUnit: 2, 8671 indentWithTabs: false, 8672 smartIndent: true, 8673 tabSize: 4, 8674 keyMap: "default", 8675 extraKeys: null, 8676 electricChars: true, 8677 autoClearEmptyLines: false, 8678 onKeyEvent: null, 8679 onDragEvent: null, 8680 lineWrapping: false, 8681 lineNumbers: false, 8682 gutter: false, 8683 fixedGutter: false, 8684 firstLineNumber: 1, 8685 showCursorWhenSelecting: false, 8686 readOnly: false, 8687 dragDrop: true, 8688 onChange: null, 8689 onCursorActivity: null, 8690 onViewportChange: null, 8691 onGutterClick: null, 8692 onUpdate: null, 8693 onFocus: null, onBlur: null, onScroll: null, 8694 matchBrackets: false, 8695 cursorBlinkRate: 530, 8696 workTime: 100, 8697 workDelay: 200, 8698 pollInterval: 100, 8699 undoDepth: 40, 8700 tabindex: null, 8701 autofocus: null, 8702 lineNumberFormatter: function(integer) { return integer; } 8703 }; 8704
8705 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); 8706 var mac = ios || /Mac/.test(navigator.platform); 8707 var win = /Win/.test(navigator.platform); 8708 8709 // Known modes, by name and by MIME 8710 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; 8711 CodeMirror.defineMode = function(name, mode) { 8712 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; 8713 if (arguments.length > 2) { 8714 mode.dependencies = []; 8715 for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); 8716 } 8717 modes[name] = mode; 8718 }; 8719 CodeMirror.defineMIME = function(mime, spec) { 8720 mimeModes[mime] = spec; 8721 }; 8722 CodeMirror.resolveMode = function(spec) { 8723 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) 8724 spec = mimeModes[spec]; 8725 else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) 8726 return CodeMirror.resolveMode("application/xml"); 8727 if (typeof spec == "string") return {name: spec}; 8728 else return spec || {name: "null"}; 8729 }; 8730 CodeMirror.getMode = function(options, spec) { 8731 var spec = CodeMirror.resolveMode(spec); 8732 var mfactory = modes[spec.name]; 8733 if (!mfactory) return CodeMirror.getMode(options, "text/plain"); 8734 var modeObj = mfactory(options, spec); 8735 if (modeExtensions.hasOwnProperty(spec.name)) { 8736 var exts = modeExtensions[spec.name]; 8737 for (var prop in exts) { 8738 if (!exts.hasOwnProperty(prop)) continue; 8739 if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; 8740 modeObj[prop] = exts[prop]; 8741 } 8742 } 8743 modeObj.name = spec.name; 8744 return modeObj; 8745 }; 8746 CodeMirror.listModes = function() { 8747 var list = []; 8748 for (var m in modes) 8749 if (modes.propertyIsEnumerable(m)) list.push(m); 8750 return list; 8751 }; 8752 CodeMirror.listMIMEs = function() { 8753 var list = []; 8754 for (var m in mimeModes) 8755 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); 8756 return list; 8757 }; 8758 8759 var extensions = CodeMirror.extensions = {}; 8760 CodeMirror.defineExtension = function(name, func) { 8761 extensions[name] = func; 8762 }; 8763 8764 var initHooks = []; 8765 CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; 8766 8767 var modeExtensions = CodeMirror.modeExtensions = {}; 8768 CodeMirror.extendMode = function(mode, properties) { 8769 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); 8770 for (var prop in properties) if (properties.hasOwnProperty(prop)) 8771 exts[prop] = properties[prop]; 8772 }; 8773 8774 var commands = CodeMirror.commands = { 8775 selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, 8776 killLine: function(cm) { 8777 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); 8778 if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); 8779 else cm.replaceRange("", from, sel ? to : {line: from.line}); 8780 }, 8781 deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, 8782 undo: function(cm) {cm.undo();}, 8783 redo: function(cm) {cm.redo();}, 8784 goDocStart: function(cm) {cm.setCursor(0, 0, true);}, 8785 goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, 8786 goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, 8787 goLineStartSmart: function(cm) { 8788 var cur = cm.getCursor(); 8789 var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); 8790 cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); 8791 }, 8792 goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, 8793 goLineUp: function(cm) {cm.moveV(-1, "line");}, 8794 goLineDown: function(cm) {cm.moveV(1, "line");}, 8795 goPageUp: function(cm) {cm.moveV(-1, "page");}, 8796 goPageDown: function(cm) {cm.moveV(1, "page");}, 8797 goCharLeft: function(cm) {cm.moveH(-1, "char");}, 8798 goCharRight: function(cm) {cm.moveH(1, "char");}, 8799 goColumnLeft: function(cm) {cm.moveH(-1, "column");}, 8800 goColumnRight: function(cm) {cm.moveH(1, "column");}, 8801 goWordLeft: function(cm) {cm.moveH(-1, "word");}, 8802 goWordRight: function(cm) {cm.moveH(1, "word");}, 8803 delCharLeft: function(cm) {cm.deleteH(-1, "char");}, 8804 delCharRight: function(cm) {cm.deleteH(1, "char");}, 8805 delWordLeft: function(cm) {cm.deleteH(-1, "word");}, 8806 delWordRight: function(cm) {cm.deleteH(1, "word");}, 8807 indentAuto: function(cm) {cm.indentSelection("smart");}, 8808 indentMore: function(cm) {cm.indentSelection("add");}, 8809 indentLess: function(cm) {cm.indentSelection("subtract");}, 8810 insertTab: function(cm) {cm.replaceSelection("\t", "end");}, 8811 defaultTab: function(cm) { 8812 if (cm.somethingSelected()) cm.indentSelection("add"); 8813 else cm.replaceSelection("\t", "end"); 8814 }, 8815 transposeChars: function(cm) { 8816 var cur = cm.getCursor(), line = cm.getLine(cur.line); 8817 if (cur.ch > 0 && cur.ch < line.length - 1) 8818 cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), 8819 {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); 8820 }, 8821 newlineAndIndent: function(cm) { 8822 cm.replaceSelection("\n", "end"); 8823 cm.indentLine(cm.getCursor().line); 8824 }, 8825 toggleOverwrite: function(cm) {cm.toggleOverwrite();} 8826 }; 8827 8828 var keyMap = CodeMirror.keyMap = {}; 8829 keyMap.basic = { 8830 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", 8831 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", 8832 "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", 8833 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" 8834 }; 8835 // Note that the save and find-related commands aren't defined by 8836 // default. Unknown commands are simply ignored. 8837 keyMap.pcDefault = { 8838 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", 8839 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", 8840 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", 8841 "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", 8842 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", 8843 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", 8844 fallthrough: "basic" 8845 }; 8846 keyMap.macDefault = { 8847 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", 8848 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt
8848-Left": "goWordLeft", 8849 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", 8850 "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", 8851 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", 8852 "Cmd-[": "indentLess", "Cmd-]": "indentMore", 8853 fallthrough: ["basic", "emacsy"] 8854 }; 8855 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; 8856 keyMap.emacsy = { 8857 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", 8858 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 8859 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", 8860 "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" 8861 }; 8862 8863 function getKeyMap(val) { 8864 if (typeof val == "string") return keyMap[val]; 8865 else return val; 8866 } 8867 function lookupKey(name, extraMap, map, handle, stop) { 8868 function lookup(map) { 8869 map = getKeyMap(map); 8870 var found = map[name]; 8871 if (found === false) { 8872 if (stop) stop(); 8873 return true; 8874 } 8875 if (found != null && handle(found)) return true; 8876 if (map.nofallthrough) { 8877 if (stop) stop(); 8878 return true; 8879 } 8880 var fallthrough = map.fallthrough; 8881 if (fallthrough == null) return false; 8882 if (Object.prototype.toString.call(fallthrough) != "[object Array]") 8883 return lookup(fallthrough); 8884 for (var i = 0, e = fallthrough.length; i < e; ++i) { 8885 if (lookup(fallthrough[i])) return true; 8886 } 8887 return false; 8888 } 8889 if (extraMap && lookup(extraMap)) return true; 8890 return lookup(map); 8891 } 8892 function isModifierKey(event) { 8893 var name = keyNames[e_prop(event, "keyCode")]; 8894 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; 8895 } 8896 CodeMirror.isModifierKey = isModifierKey; 8897 8898 CodeMirror.fromTextArea = function(textarea, options) { 8899 if (!options) options = {}; 8900 options.value = textarea.value; 8901 if (!options.tabindex && textarea.tabindex) 8902 options.tabindex = textarea.tabindex; 8903 // Set autofocus to true if this textarea is focused, or if it has 8904 // autofocus and no other element is focused. 8905 if (options.autofocus == null) { 8906 var hasFocus = document.body; 8907 // doc.activeElement occasionally throws on IE 8908 try { hasFocus = document.activeElement; } catch(e) {} 8909 options.autofocus = hasFocus == textarea || 8910 textarea.getAttribute("autofocus") != null && hasFocus == document.body; 8911 } 8912 8913 function save() {textarea.value = instance.getValue();} 8914 if (textarea.form) { 8915 // Deplorable hack to make the submit method do the right thing. 8916 var rmSubmit = connect(textarea.form, "submit", save, true); 8917 var form = textarea.form, realSubmit = form.submit; 8918 textarea.form.submit = function wrappedSubmit() { 8919 save(); 8920 form.submit = realSubmit; 8921 form.submit(); 8922 form.submit = wrappedSubmit; 8923 }; 8924 } 8925 8926 textarea.style.display = "none"; 8927 var instance = CodeMirror(function(node) { 8928 textarea.parentNode.insertBefore(node, textarea.nextSibling); 8929 }, options); 8930 instance.save = save; 8931 instance.getTextArea = function() { return textarea; }; 8932 instance.toTextArea = function() { 8933 save(); 8934 textarea.parentNode.removeChild(instance.getWrapperElement()); 8935 textarea.style.display = ""; 8936 if (textarea.form) { 8937 rmSubmit(); 8938 if (typeof textarea.form.submit == "function") 8939 textarea.form.submit = realSubmit; 8940 } 8941 }; 8942 return instance; 8943 }; 8944 8945 var gecko = /gecko\/\d/i.test(navigator.userAgent); 8946 var ie = /MSIE \d/.test(navigator.userAgent); 8947 var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); 8948 var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); 8949 var quirksMode = ie && document.documentMode == 5; 8950 var webkit = /WebKit\//.test(navigator.userAgent); 8951 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); 8952 var chrome = /Chrome\//.test(navigator.userAgent); 8953 var opera = /Opera\//.test(navigator.userAgent); 8954 var safari = /Apple Computer/.test(navigator.vendor); 8955 var khtml = /KHTML\//.test(navigator.userAgent); 8956 var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); 8957 8958 var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); 8959 if (opera_version) opera_version = Number(opera_version[1]); 8960 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X 8961 var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); 8962 8963 // Utility functions for working with state. Exported because modes 8964 // sometimes need to do this. 8965 function copyState(mode, state) { 8966 if (state === true) return state; 8967 if (mode.copyState) return mode.copyState(state); 8968 var nstate = {}; 8969 for (var n in state) { 8970 var val = state[n]; 8971 if (val instanceof Array) val = val.concat([]); 8972 nstate[n] = val; 8973 } 8974 return nstate; 8975 } 8976 CodeMirror.copyState = copyState; 8977 function startState(mode, a1, a2) { 8978 return mode.startState ? mode.startState(a1, a2) : true; 8979 } 8980 CodeMirror.startState = startState; 8981 CodeMirror.innerMode = function(mode, state) { 8982 while (mode.innerMode) { 8983 var info = mode.innerMode(state); 8984 state = info.state; 8985 mode = info.mode; 8986 } 8987 return info || {mode: mode, state: state}; 8988 }; 8989 8990 // The character stream used by a mode's parser. 8991 function StringStream(string, tabSize) { 8992 this.pos = this.start = 0; 8993 this.string = string; 8994 this.tabSize = tabSize || 8; 8995 } 8996 StringStream.prototype = { 8997 eol: function() {return this.pos >= this.string.length;}, 8998 sol: function() {return this.pos == 0;}, 8999 peek: function() {return this.string.charAt(this.pos) || undefined;}, 9000 next: function() { 9001 if (this.pos < this.string.length) 9002 return this.string.charAt(this.pos++); 9003 }, 9004 eat: function(match) { 9005 var ch = this.string.charAt(this.pos); 9006 if (typeof match == "string") var ok = ch == match; 9007 else var ok = ch && (match.test ? match.test(ch) : match(ch)); 9008 if (ok) {++this.pos; return ch;} 9009 }, 9010 eatWhile: function(match) { 9011 var start = this.pos; 9012 while (this.eat(match)){} 9013 return this.pos > start; 9014 }, 9015 eatSpace: function() { 9016 var start = this.pos; 9017 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; 9018 return this.pos > start; 9019 }, 9020 skipToEnd: function() {this.pos = this.string.length;}, 9021 skipTo: function(ch) { 9022 var found = this.string.indexOf(ch, this.pos); 9023 if (found > -1) {this.pos = found; return true;} 9024 }, 9025 backUp: function(n) {this.pos -= n;}, 9026 column: function() {return countColumn(this.string, this.start, this.tabSize);}, 9027 indentation: function() {return countColumn(this.string, null, this.tabSize);}, 9028 match: function(pattern, consume, caseInsensitive) { 9029 if (typeof pattern == "string") { 9030 var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; 9031 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { 9032 if (consume !== false) this.pos += pattern.length; 9033 return true; 9034 } 9035 } else { 9036 var match = this.string.slice(this.pos).match(pattern); 9037 if (match && match.index > 0) return null; 9038 if (match && consume !== false) this.pos += match[0].length; 9039 return match; 9040 } 9041 }, 9042 current: function(){return this.string.slice(this.start, this.pos);} 9043 }; 9044 CodeMirror.StringStream = StringStream; 9045 9046 function MarkedSpan(from, to, marker) { 9047 this.from = from; this.to = to; this.marker = marker; 9048 } 9049 9050 function getMarkedSpanFor(spans, marker) { 9051 if (spans) for (var i = 0; i < spans.length; ++i) { 9052 var span = spans[i]; 9053 if (span.marker == marker) return span; 9054 } 9055 } 9056 9057 function removeMarkedSpan(spans, span) { 9058 var r; 9059 for (var i = 0; i < spans.length; ++i) 9060 if (spans[i] != span) (r || (r = [])).push(spans[i]); 9061 return r; 9062 } 9063 9064 function markedSpansBefore(old, startCh, endCh) { 9065 if (old) for (var i = 0, nw; i < old.length; ++i) { 9066 var span = old[i], marker = span.marker; 9067 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); 9068 if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { 9069 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); 9070 (nw || (nw = [])).push({from: span.from, 9071 to: endsAfter ? null : span.to, 9072 marker: marker}); 9073 } 9074 } 9075 return nw; 9076 } 9077 9078 function markedSpansAfter(old, endCh) { 9079 if (old) for (var i = 0, nw; i < old.length; ++i) { 9080 var span = old[i], marker = span.marker; 9081 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); 9082 if (endsAfter || marker.type == "bookmark" && span.from == endCh) { 9083 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); 9084 (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
9085 to: span.to == null ? null : span.to - endCh, 9086 marker: marker}); 9087 } 9088 } 9089 return nw; 9090 } 9091 9092 function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { 9093 if (!oldFirst && !oldLast) return newText; 9094 // Get the spans that 'stick out' on both sides 9095 var first = markedSpansBefore(oldFirst, startCh); 9096 var last = markedSpansAfter(oldLast, endCh); 9097 9098 // Next, merge those two ends 9099 var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); 9100 if (first) { 9101 // Fix up .to properties of first 9102 for (var i = 0; i < first.length; ++i) { 9103 var span = first[i]; 9104 if (span.to == null) { 9105 var found = getMarkedSpanFor(last, span.marker); 9106 if (!found) span.to = startCh; 9107 else if (sameLine) span.to = found.to == null ? null : found.to + offset; 9108 } 9109 } 9110 } 9111 if (last) { 9112 // Fix up .from in last (or move them into first in case of sameLine) 9113 for (var i = 0; i < last.length; ++i) { 9114 var span = last[i]; 9115 if (span.to != null) span.to += offset; 9116 if (span.from == null) { 9117 var found = getMarkedSpanFor(first, span.marker); 9118 if (!found) { 9119 span.from = offset; 9120 if (sameLine) (first || (first = [])).push(span); 9121 } 9122 } else { 9123 span.from += offset; 9124 if (sameLine) (first || (first = [])).push(span); 9125 } 9126 } 9127 } 9128 9129 var newMarkers = [newHL(newText[0], first)]; 9130 if (!sameLine) { 9131 // Fill gap with whole-line-spans 9132 var gap = newText.length - 2, gapMarkers; 9133 if (gap > 0 && first) 9134 for (var i = 0; i < first.length; ++i) 9135 if (first[i].to == null) 9136 (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); 9137 for (var i = 0; i < gap; ++i) 9138 newMarkers.push(newHL(newText[i+1], gapMarkers)); 9139 newMarkers.push(newHL(lst(newText), last)); 9140 } 9141 return newMarkers; 9142 } 9143 9144 // hl stands for history-line, a data structure that can be either a 9145 // string (line without markers) or a {text, markedSpans} object. 9146 function hlText(val) { return typeof val == "string" ? val : val.text; } 9147 function hlSpans(val) { 9148 if (typeof val == "string") return null; 9149 var spans = val.markedSpans, out = null; 9150 for (var i = 0; i < spans.length; ++i) { 9151 if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } 9152 else if (out) out.push(spans[i]); 9153 } 9154 return !out ? spans : out.length ? out : null; 9155 } 9156 function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } 9157 9158 function detachMarkedSpans(line) { 9159 var spans = line.markedSpans; 9160 if (!spans) return; 9161 for (var i = 0; i < spans.length; ++i) { 9162 var lines = spans[i].marker.lines; 9163 var ix = indexOf(lines, line); 9164 lines.splice(ix, 1); 9165 } 9166 line.markedSpans = null; 9167 } 9168 9169 function attachMarkedSpans(line, spans) { 9170 if (!spans) return; 9171 for (var i = 0; i < spans.length; ++i) 9172 var marker = spans[i].marker.lines.push(line); 9173 line.markedSpans = spans; 9174 } 9175 9176 // When measuring the position of the end of a line, different 9177 // browsers require different approaches. If an empty span is added, 9178 // many browsers report bogus offsets. Of those, some (Webkit, 9179 // recent IE) will accept a space without moving the whole span to 9180 // the next line when wrapping it, others work with a zero-width 9181 // space. 9182 var eolSpanContent = " "; 9183 if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; 9184 else if (opera) eolSpanContent = ""; 9185 9186 // Line objects. These hold state related to a line, including 9187 // highlighting info (the styles array). 9188 function Line(text, markedSpans) { 9189 this.text = text; 9190 this.height = 1; 9191 attachMarkedSpans(this, markedSpans); 9192 } 9193 Line.prototype = { 9194 update: function(text, markedSpans) { 9195 this.text = text; 9196 this.stateAfter = this.styles = null; 9197 detachMarkedSpans(this); 9198 attachMarkedSpans(this, markedSpans); 9199 }, 9200 // Run the given mode's parser over a line, update the styles 9201 // array, which contains alternating fragments of text and CSS 9202 // classes. 9203 highlight: function(mode, state, tabSize) { 9204 var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); 9205 var pos = st.length = 0; 9206 if (this.text == "" && mode.blankLine) mode.blankLine(state); 9207 while (!stream.eol()) { 9208 var style = mode.token(stream, state), substr = stream.current(); 9209 stream.start = stream.pos; 9210 if (pos && st[pos-1] == style) { 9211 st[pos-2] += substr; 9212 } else if (substr) { 9213 st[pos++] = substr; st[pos++] = style; 9214 } 9215 // Give up when line is ridiculously long 9216 if (stream.pos > 5000) { 9217 st[pos++] = this.text.slice(stream.pos); st[pos++] = null; 9218 break; 9219 } 9220 } 9221 }, 9222 process: function(mode, state, tabSize) { 9223 var stream = new StringStream(this.text, tabSize); 9224 if (this.text == "" && mode.blankLine) mode.blankLine(state); 9225 while (!stream.eol() && stream.pos <= 5000) { 9226 mode.token(stream, state); 9227 stream.start = stream.pos; 9228 } 9229 }, 9230 // Fetch the parser token for a given character. Useful for hacks 9231 // that want to inspect the mode state (say, for completion). 9232 getTokenAt: function(mode, state, tabSize, ch) { 9233 var txt = this.text, stream = new StringStream(txt, tabSize); 9234 while (stream.pos < ch && !stream.eol()) { 9235 stream.start = stream.pos; 9236 var style = mode.token(stream, state); 9237 } 9238 return {start: stream.start, 9239 end: stream.pos,
9240 string: stream.current(), 9241 className: style || null, 9242 state: state}; 9243 }, 9244 indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, 9245 // Produces an HTML fragment for the line, taking selection, 9246 // marking, and highlighting into account. 9247 getContent: function(tabSize, wrapAt, compensateForWrapping) { 9248 var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; 9249 var pre = elt("pre"); 9250 function span_(html, text, style) { 9251 if (!text) return; 9252 // Work around a bug where, in some compat modes, IE ignores leading spaces 9253 if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); 9254 first = false; 9255 if (!specials.test(text)) { 9256 col += text.length; 9257 var content = document.createTextNode(text); 9258 } else { 9259 var content = document.createDocumentFragment(), pos = 0; 9260 while (true) { 9261 specials.lastIndex = pos; 9262 var m = specials.exec(text); 9263 var skipped = m ? m.index - pos : text.length - pos; 9264 if (skipped) { 9265 content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); 9266 col += skipped; 9267 } 9268 if (!m) break; 9269 pos += skipped + 1; 9270 if (m[0] == "\t") { 9271 var tabWidth = tabSize - col % tabSize; 9272 content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); 9273 col += tabWidth; 9274 } else { 9275 var token = elt("span", "\u2022", "cm-invalidchar"); 9276 token.title = "\\u" + m[0].charCodeAt(0).toString(16); 9277 content.appendChild(token); 9278 col += 1; 9279 } 9280 } 9281 } 9282 if (style) html.appendChild(elt("span", [content], style)); 9283 else html.appendChild(content); 9284 } 9285 var span = span_; 9286 if (wrapAt != null) { 9287 var outPos = 0, anchor = pre.anchor = elt("span"); 9288 span = function(html, text, style) { 9289 var l = text.length; 9290 if (wrapAt >= outPos && wrapAt < outPos + l) { 9291 var cut = wrapAt - outPos; 9292 if (cut) { 9293 span_(html, text.slice(0, cut), style); 9294 // See comment at the definition of spanAffectsWrapping 9295 if (compensateForWrapping) { 9296 var view = text.slice(cut - 1, cut + 1); 9297 if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr")); 9298 else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d")); 9299 } 9300 } 9301 html.appendChild(anchor); 9302 span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); 9303 if (opera) span_(html, text.slice(cut + 1), style); 9304 wrapAt--; 9305 outPos += l; 9306 } else { 9307 outPos += l; 9308 span_(html, text, style); 9309 if (outPos == wrapAt && outPos == len) { 9310 setTextContent(anchor, eolSpanContent); 9311 html.appendChild(anchor); 9312 } 9313 // Stop outputting HTML when gone sufficiently far beyond measure 9314 else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; 9315 } 9316 }; 9317 } 9318 9319 var st = this.styles, allText = this.text, marked = this.markedSpans; 9320 var len = allText.length; 9321 function styleToClass(style) { 9322 if (!style) return null; 9323 return "cm-" + style.replace(/ +/g, " cm-"); 9324 } 9325 if (!allText && wrapAt == null) { 9326 span(pre, " "); 9327 } else if (!marked || !marked.length) { 9328 for (var i = 0, ch = 0; ch < len; i+=2) { 9329 var str = st[i], style = st[i+1], l = str.length; 9330 if (ch + l > len) str = str.slice(0, len - ch); 9331 ch += l; 9332 span(pre, str, styleToClass(style)); 9333 } 9334 } else { 9335 marked.sort(function(a, b) { return a.from - b.from; }); 9336 var pos = 0, i = 0, text = "", style, sg = 0; 9337 var nextChange = marked[0].from || 0, marks = [], markpos = 0; 9338 var advanceMarks = function() { 9339 var m; 9340 while (markpos < marked.length && 9341 ((m = marked[markpos]).from == pos || m.from == null)) { 9342 if (m.marker.type == "range") marks.push(m); 9343 ++markpos; 9344 } 9345 nextChange = markpos < marked.length ? marked[markpos].from : Infinity; 9346 for (var i = 0; i < marks.length; ++i) { 9347 var to = marks[i].to; 9348 if (to == null) to = Infinity; 9349 if (to == pos) marks.splice(i--, 1); 9350 else nextChange = Math.min(to, nextChange); 9351 } 9352 }; 9353 var m = 0; 9354 while (pos < len) { 9355 if (nextChange == pos) advanceMarks(); 9356 var upto = Math.min(len, nextChange); 9357 while (true) { 9358 if (text) { 9359 var end = pos + text.length; 9360 var appliedStyle = style; 9361 for (var j = 0; j < marks.length; ++j) { 9362 var mark = marks[j]; 9363 appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; 9364 if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; 9365 if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; 9366 } 9367 span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); 9368 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} 9369 pos = end; 9370 } 9371 text = st[i++]; style = styleToClass(st[i++]); 9372 } 9373 } 9374 } 9375 return pre; 9376 }, 9377 cleanUp: function() { 9378 this.parent = null; 9379 detachMarkedSpans(this); 9380 } 9381 }; 9382 9383 // Data structure that holds the sequence of lines. 9384 function LeafChunk(lines) { 9385 this.lines = lines; 9386 this.parent = null; 9387 for (var i = 0, e = lines.length, height = 0; i < e; ++i) { 9388 lines[i].parent = this; 9389 height += lines[i].height; 9390 } 9391 this.height = height; 9392 } 9393 LeafChunk.prototype = { 9394 chunkSize: function() { return this.lines.length; }, 9395 remove: function(at, n, callbacks) { 9396 for (var i = at, e = at + n; i < e; ++i) { 9397 var line = this.lines[i];
9398 this.height -= line.height; 9399 line.cleanUp(); 9400 if (line.handlers) 9401 for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); 9402 } 9403 this.lines.splice(at, n); 9404 }, 9405 collapse: function(lines) { 9406 lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); 9407 }, 9408 insertHeight: function(at, lines, height) { 9409 this.height += height; 9410 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); 9411 for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; 9412 }, 9413 iterN: function(at, n, op) { 9414 for (var e = at + n; at < e; ++at) 9415 if (op(this.lines[at])) return true; 9416 } 9417 }; 9418 function BranchChunk(children) { 9419 this.children = children; 9420 var size = 0, height = 0; 9421 for (var i = 0, e = children.length; i < e; ++i) { 9422 var ch = children[i]; 9423 size += ch.chunkSize(); height += ch.height; 9424 ch.parent = this; 9425 } 9426 this.size = size; 9427 this.height = height; 9428 this.parent = null; 9429 } 9430 BranchChunk.prototype = { 9431 chunkSize: function() { return this.size; }, 9432 remove: function(at, n, callbacks) { 9433 this.size -= n; 9434 for (var i = 0; i < this.children.length; ++i) { 9435 var child = this.children[i], sz = child.chunkSize(); 9436 if (at < sz) { 9437 var rm = Math.min(n, sz - at), oldHeight = child.height; 9438 child.remove(at, rm, callbacks); 9439 this.height -= oldHeight - child.height; 9440 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } 9441 if ((n -= rm) == 0) break; 9442 at = 0; 9443 } else at -= sz; 9444 } 9445 if (this.size - n < 25) { 9446 var lines = []; 9447 this.collapse(lines); 9448 this.children = [new LeafChunk(lines)]; 9449 this.children[0].parent = this; 9450 } 9451 }, 9452 collapse: function(lines) { 9453 for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); 9454 }, 9455 insert: function(at, lines) { 9456 var height = 0; 9457 for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; 9458 this.insertHeight(at, lines, height); 9459 }, 9460 insertHeight: function(at, lines, height) { 9461 this.size += lines.length; 9462 this.height += height; 9463 for (var i = 0, e = this.children.length; i < e; ++i) { 9464 var child = this.children[i], sz = child.chunkSize(); 9465 if (at <= sz) { 9466 child.insertHeight(at, lines, height); 9467 if (child.lines && child.lines.length > 50) { 9468 while (child.lines.length > 50) { 9469 var spilled = child.lines.splice(child.lines.length - 25, 25); 9470 var newleaf = new LeafChunk(spilled); 9471 child.height -= newleaf.height; 9472 this.children.splice(i + 1, 0, newleaf); 9473 newleaf.parent = this; 9474 } 9475 this.maybeSpill(); 9476 } 9477 break; 9478 } 9479 at -= sz; 9480 } 9481 }, 9482 maybeSpill: function() { 9483 if (this.children.length <= 10) return; 9484 var me = this; 9485 do { 9486 var spilled = me.children.splice(me.children.length - 5, 5); 9487 var sibling = new BranchChunk(spilled); 9488 if (!me.parent) { // Become the parent node 9489 var copy = new BranchChunk(me.children); 9490 copy.parent = me; 9491 me.children = [copy, sibling]; 9492 me = copy; 9493 } else { 9494 me.size -= sibling.size; 9495 me.height -= sibling.height; 9496 var myIndex = indexOf(me.parent.children, me); 9497 me.parent.children.splice(myIndex + 1, 0, sibling); 9498 } 9499 sibling.parent = me.parent; 9500 } while (me.children.length > 10); 9501 me.parent.maybeSpill(); 9502 },
9503 iter: function(from, to, op) { this.iterN(from, to - from, op); }, 9504 iterN: function(at, n, op) { 9505 for (var i = 0, e = this.children.length; i < e; ++i) { 9506 var child = this.children[i], sz = child.chunkSize(); 9507 if (at < sz) { 9508 var used = Math.min(n, sz - at); 9509 if (child.iterN(at, used, op)) return true; 9510 if ((n -= used) == 0) break; 9511 at = 0; 9512 } else at -= sz; 9513 } 9514 } 9515 }; 9516 9517 function getLineAt(chunk, n) { 9518 while (!chunk.lines) { 9519 for (var i = 0;; ++i) { 9520 var child = chunk.children[i], sz = child.chunkSize(); 9521 if (n < sz) { chunk = child; break; } 9522 n -= sz; 9523 } 9524 } 9525 return chunk.lines[n]; 9526 } 9527 function lineNo(line) { 9528 if (line.parent == null) return null; 9529 var cur = line.parent, no = indexOf(cur.lines, line); 9530 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { 9531 for (var i = 0; ; ++i) { 9532 if (chunk.children[i] == cur) break; 9533 no += chunk.children[i].chunkSize(); 9534 } 9535 } 9536 return no; 9537 } 9538 function lineAtHeight(chunk, h) { 9539 var n = 0; 9540 outer: do { 9541 for (var i = 0, e = chunk.children.length; i < e; ++i) { 9542 var child = chunk.children[i], ch = child.height; 9543 if (h < ch) { chunk = child; continue outer; } 9544 h -= ch; 9545 n += child.chunkSize(); 9546 } 9547 return n; 9548 } while (!chunk.lines); 9549 for (var i = 0, e = chunk.lines.length; i < e; ++i) { 9550 var line = chunk.lines[i], lh = line.height; 9551 if (h < lh) break; 9552 h -= lh; 9553 } 9554 return n + i; 9555 } 9556 function heightAtLine(chunk, n) { 9557 var h = 0; 9558 outer: do { 9559 for (var i = 0, e = chunk.children.length; i < e; ++i) { 9560 var child = chunk.children[i], sz = child.chunkSize(); 9561 if (n < sz) { chunk = child; continue outer; } 9562 n -= sz; 9563 h += child.height; 9564 } 9565 return h; 9566 } while (!chunk.lines); 9567 for (var i = 0; i < n; ++i) h += chunk.lines[i].height; 9568 return h; 9569 } 9570 9571 // The history object 'chunks' changes that are made close together 9572 // and at almost the same time into bigger undoable units. 9573 function History() { 9574 this.time = 0; 9575 this.done = []; this.undone = []; 9576 this.compound = 0; 9577 this.closed = false; 9578 } 9579 History.prototype = { 9580 addChange: function(start, added, old) { 9581 this.undone.length = 0; 9582 var time = +new Date, cur = lst(this.done), last = cur && lst(cur); 9583 var dtime = time - this.time; 9584 9585 if (cur && !this.closed && this.compound) { 9586 cur.push({start: start, added: added, old: old}); 9587 } else if (dtime > 400 || !last || this.closed || 9588 last.start > start + old.length || last.start + last.added < start) { 9589 this.done.push([{start: start, added: added, old: old}]); 9590 this.closed = false; 9591 } else { 9592 var startBefore = Math.max(0, last.start - start), 9593 endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); 9594 for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); 9595 for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); 9596 if (startBefore) last.start = start; 9597 last.added += added - (old.length - startBefore - endAfter); 9598 } 9599 this.time = time; 9600 }, 9601 startCompound: function() { 9602 if (!this.compound++) this.closed = true; 9603 }, 9604 endCompound: function() { 9605 if (!--this.compound) this.closed = true; 9606 } 9607 }; 9608 9609 function stopMethod() {e_stop(this);} 9610 // Ensure an event has a stop method. 9611 function addStop(event) { 9612 if (!event.stop) event.stop = stopMethod; 9613 return event; 9614 } 9615 9616 function e_preventDefault(e) { 9617 if (e.preventDefault) e.preventDefault(); 9618 else e.returnValue = false; 9619 } 9620 function e_stopPropagation(e) { 9621 if (e.stopPropagation) e.stopPropagation(); 9622 else e.cancelBubble = true; 9623 } 9624 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} 9625 CodeMirror.e_stop = e_stop; 9626 CodeMirror.e_preventDefault = e_preventDefault; 9627 CodeMirror.e_stopPropagation = e_stopPropagation; 9628 9629 function e_target(e) {return e.target || e.srcElement;} 9630 function e_button(e) { 9631 var b = e.which; 9632 if (b == null) { 9633 if (e.button & 1) b = 1; 9634 else if (e.button & 2) b = 3; 9635 else if (e.button & 4) b = 2; 9636 } 9637 if (mac && e.ctrlKey && b == 1) b = 3; 9638 return b; 9639 } 9640 9641 // Allow 3rd-party code to override event properties by adding an override 9642 // object to an event object. 9643 function e_prop(e, prop) { 9644 var overridden = e.override && e.override.hasOwnProperty(prop); 9645 return overridden ? e.override[prop] : e[prop]; 9646 } 9647 9648 // Event handler registration. If disconnect is true, it'll return a 9649 // function that unregisters the handler. 9650 function connect(node, type, handler, disconnect) { 9651 if (typeof node.addEventListener == "function") { 9652 node.addEventListener(type, handler, false); 9653 if (disconnect) return function() {node.removeEventListener(type, handler, false);}; 9654 } else { 9655 var wrapHandler = function(event) {handler(event || window.event);}; 9656 node.attachEvent("on" + type, wrapHandler); 9657 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; 9658 } 9659 } 9660 CodeMirror.connect = connect; 9661 9662 function Delayed() {this.id = null;} 9663 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id
9663); this.id = setTimeout(f, ms);}}; 9664 9665 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; 9666 9667 // Detect drag-and-drop 9668 var dragAndDrop = function() { 9669 // There is *some* kind of drag-and-drop support in IE6-8, but I 9670 // couldn't get it to work yet. 9671 if (ie_lt9) return false; 9672 var div = elt('div'); 9673 return "draggable" in div || "dragDrop" in div; 9674 }(); 9675 9676 // Feature-detect whether newlines in textareas are converted to \r\n 9677 var lineSep = function () { 9678 var te = elt("textarea"); 9679 te.value = "foo\nbar"; 9680 if (te.value.indexOf("\r") > -1) return "\r\n"; 9681 return "\n"; 9682 }(); 9683 9684 // For a reason I have yet to figure out, some browsers disallow 9685 // word wrapping between certain characters *only* if a new inline 9686 // element is started between them. This makes it hard to reliably 9687 // measure the position of things, since that requires inserting an 9688 // extra span. This terribly fragile set of regexps matches the 9689 // character combinations that suffer from this phenomenon on the 9690 // various browsers. 9691 var spanAffectsWrapping = /^$/; // Won't match any two-character string 9692 if (gecko) spanAffectsWrapping = /$'/; 9693 else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; 9694 else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; 9695 9696 // Counts the column offset in a string, taking tabs into account. 9697 // Used mostly to find indentation. 9698 function countColumn(string, end, tabSize) { 9699 if (end == null) { 9700 end = string.search(/[^\s\u00a0]/); 9701 if (end == -1) end = string.length; 9702 } 9703 for (var i = 0, n = 0; i < end; ++i) { 9704 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); 9705 else ++n; 9706 } 9707 return n; 9708 } 9709 9710 function eltOffset(node, screen) { 9711 // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, 9712 // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) 9713 try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } 9714 catch(e) { box = {top: 0, left: 0}; } 9715 if (!screen) { 9716 // Get the toplevel scroll, working around browser differences. 9717 if (window.pageYOffset == null) { 9718 var t = document.documentElement || document.body.parentNode; 9719 if (t.scrollTop == null) t = document.body; 9720 box.top += t.scrollTop; box.left += t.scrollLeft; 9721 } else { 9722 box.top += window.pageYOffset; box.left += window.pageXOffset; 9723 } 9724 } 9725 return box; 9726 } 9727 9728 function eltText(node) { 9729 return node.textContent || node.innerText || node.nodeValue || ""; 9730 } 9731 9732 var spaceStrs = [""]; 9733 function spaceStr(n) { 9734 while (spaceStrs.length <= n) 9735 spaceStrs.push(lst(spaceStrs) + " "); 9736 return spaceStrs[n]; 9737 } 9738 9739 function lst(arr) { return arr[arr.length-1]; } 9740 9741 function selectInput(node) { 9742 if (ios) { // Mobile Safari apparently has a bug where select() is broken. 9743 node.selectionStart = 0; 9744 node.selectionEnd = node.value.length; 9745 } else node.select(); 9746 } 9747 9748 // Operations on {line, ch} objects. 9749 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} 9750 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} 9751 function copyPos(x) {return {line: x.line, ch: x.ch};} 9752 9753 function elt(tag, content, className, style) { 9754 var e = document.createElement(tag); 9755 if (className) e.className = className; 9756 if (style) e.style.cssText = style; 9757 if (typeof content == "string") setTextContent(e, content); 9758 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); 9759 return e; 9760 } 9761 function removeChildren(e) { 9762 e.innerHTML = ""; 9763 return e; 9764 } 9765 function removeChildrenAndAdd(parent, e) { 9766 removeChildren(parent).appendChild(e); 9767 } 9768 function setTextContent(e, str) { 9769 if (ie_lt9) { 9770 e.innerHTML = ""; 9771 e.appendChild(document.createTextNode(str)); 9772 } else e.textContent = str; 9773 } 9774 9775 // Used to position the cursor after an undo/redo by finding the 9776 // last edited character. 9777 function editEnd(from, to) { 9778 if (!to) return 0; 9779 if (!from) return to.length; 9780 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) 9781 if (from.charAt(i) != to.charAt(j)) break; 9782 return j + 1; 9783 } 9784 9785 function indexOf(collection, elt) { 9786 if (collection.indexOf) return collection.indexOf(elt); 9787 for (var i = 0, e = collection.length; i < e; ++i) 9788 if (collection[i] == elt) return i; 9789 return -1; 9790 } 9791 var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; 9792 function isWordChar(ch) { 9793 return /\w/.test(ch) || ch > "\x80" && 9794 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); 9795 } 9796 9797 // See if "".split is the broken IE version, if so, provide an 9798 // alternative way to split lines. 9799 var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { 9800 var pos = 0, result = [], l = string.length; 9801 while (pos <= l) { 9802 var nl = string.indexOf("\n", pos); 9803 if (nl == -1) nl = string.length; 9804 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); 9805 var rt = line.indexOf("\r"); 9806 if (rt != -1) { 9807 result.push(line.slice(0, rt)); 9808 pos += rt + 1; 9809 } else { 9810 result.push(line); 9811 pos = nl + 1; 9812 } 9813 } 9814 return result; 9815 } : function(string){return string.split(/\r\n?|\n/);}; 9816 CodeMirror.splitLines = splitLines; 9817 9818 var hasSelection = window.getSelection ? function(te) {
9819 try { return te.selectionStart != te.selectionEnd; } 9820 catch(e) { return false; } 9821 } : function(te) { 9822 try {var range = te.ownerDocument.selection.createRange();} 9823 catch(e) {} 9824 if (!range || range.parentElement() != te) return false; 9825 return range.compareEndPoints("StartToEnd", range) != 0; 9826 }; 9827 9828 CodeMirror.defineMode("null", function() { 9829 return {token: function(stream) {stream.skipToEnd();}}; 9830 }); 9831 CodeMirror.defineMIME("text/plain", "null"); 9832 9833 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 9834 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 9835 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 9836 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", 9837 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 9838 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", 9839 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; 9840 CodeMirror.keyNames = keyNames; 9841 (function() { 9842 // Number keys 9843 for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); 9844 // Alphabetic keys 9845 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); 9846 // Function keys 9847 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; 9848 })(); 9849 9850 CodeMirror.version = "2.38"; 9851 9852 return CodeMirror; 9853})(); 9854; 9855 9856/* 9857 * MySQL Mode for CodeMirror 2 by MySQL-Tools 9858 * @author James Thorne (partydroid) 9859 * @link http://github.com/partydroid/MySQL-Tools 9860 * @link http://mysqltools.org 9861 * @version 02/Jan/2012 9862*/ 9863CodeMirror.defineMode("mysql", function(config) { 9864 var indentUnit = config.indentUnit; 9865 var curPunc; 9866 9867 function wordRegexp(words) { 9868 return new RegExp("^(?:" + words.join("|") + ")$", "i"); 9869 } 9870 var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", 9871 "isblank", "isliteral", "union", "a"]); 9872 var keywords = wordRegexp([ 9873 ('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'), 9874 ('DESC'),('DISTINCT'),('DOUBLE'),('EACH'),('ENCLOSED'),('EXIT'),('FETCH'),('FLOAT8'),('FOREIGN'),('GRANT'),('HIGH_PRIORITY'),('HOUR_SECOND'),('IN'),('INNER'),('INSERT'),('INT2'),('INT8'), 9875 ('INTO'),('JOIN'),('KILL'),('LEFT'),('LINEAR'),('LOCALTIME'),('LONG'),('LOOP'),('MATCH'),('MEDIUMTEXT'),('MINUTE_SECOND'),('NATURAL'),('NULL'),('OPTIMIZE'),('OR'),('OUTER'),('PRIMARY'), 9876 ('RANGE'),('READ_WRITE'),('REGEXP'),('REPEAT'),('RESTRICT'),('RIGHT'),('SCHEMAS'),('SENSITIVE'),('SHOW'),('SPECIFIC'),('SQLSTATE'),('SQL_CALC_FOUND_ROWS'),('STARTING'),('TERMINATED'), 9877 ('TINYINT'),('TRAILING'),('UNDO'),('UNLOCK'),('USAGE'),('UTC_DATE'),('VALUES'),('VARCHARACTER'),('WHERE'),('WRITE'),('ZEROFILL'),('ALL'),('AND'),('ASENSITIVE'),('BIGINT'),('BOTH'),('CASCADE'), 9878 ('CHAR'),('COLLATE'),('CONSTRAINT'),('CREATE'),('CURRENT_TIME'),('CURSOR'),('DAY_HOUR'),('DAY_SECOND'),('DECLARE'),('DELETE'),('DETERMINISTIC'),('DIV'),('DUAL'),('ELSEIF'),('EXISTS'),('FALSE'), 9879 ('FLOAT4'),('FORCE'),('FULLTEXT'),('HAVING'),('HOUR_MINUTE'),('IGNORE'),('INFILE'),('INSENSITIVE'),('INT1'),('INT4'),('INTERVAL'),('ITERATE'),('KEYS'),('LEAVE'),('LIMIT'),('LOAD'),('LOCK'), 9880 ('LONGTEXT'),('MASTER_SSL_VERIFY_SERVER_CERT'),('MEDIUMINT'),('MINUTE_MICROSECOND'),('MODIFIES'),('NO_WRITE_TO_BINLOG'),('ON'),('OPTIONALLY'),('OUT'),('PRECISION'),('PURGE'),('READS'), 9881 ('REFERENCES'),('RENAME'),('REQUIRE'),('REVOKE'),('SCHEMA'),('SELECT'),('SET'),('SPATIAL'),('SQLEXCEPTION'),('SQL_BIG_RESULT'),('SSL'),('TABLE'),('TINYBLOB'),('TO'),('TRUE'),('UNIQUE'), 9882 ('UPDATE'),('USING'),('UTC_TIMESTAMP'),('VARCHAR'),('WHEN'),('WITH'),('YEAR_MONTH'),('ADD'),('ANALYZE'),('ASC'),('BETWEEN'),('BLOB'),('CALL'),('CHANGE'),('CHECK'),('CONDITION'),('CONVERT'), 9883 ('CURRENT_DATE'),('CURRENT_USER'),('DATABASES'),('DAY_MINUTE'),('DECIMAL'),('DELAYED'),('DESCRIBE'),('DISTINCTROW'),('DROP'),('ELSE'),('ESCAPED'),('EXPLAIN'),('FLOAT'),('FOR'),('FROM'), 9884 ('GROUP'),('HOUR_MICROSECOND'),('IF'),('INDEX'),('INOUT'),('INT'),('INT3'),('INTEGER'),('IS'),('KEY'),('LEADING'),('LIKE'),('LINES'),('LOCALTIMESTAMP'),('LONGBLOB'),('LOW_PRIORITY'), 9885 ('MEDIUMBLOB'),('MIDDLEINT'),('MOD'),('NOT'),('NUMERIC'),('OPTION'),('ORDER'),('OUTFILE'),('PROCEDURE'),('READ'),('REAL'),('RELEASE'),('REPLACE'),('RETURN'),('RLIKE'),('SECOND_MICROSECOND'), 9886 ('SEPARATOR'),('SMALLINT'),('SQL'),('SQLWARNING'),('SQL_SMALL_RESULT'),('STRAIGHT_JOIN'),('THEN'),('TINYTEXT'),('TRIGGER'),('UNION'),('UNSIGNED'),('USE'),('UTC_TIME'),('VARBINARY'),('VARYING'), 9887 ('WHILE'),('XOR'),('FULL'),('COLUMNS'),('MIN'),('MAX'),('STDEV'),('COUNT') 9888 ]); 9889 var operatorChars = /[*+\-<>=&|]/; 9890 9891 function tokenBase(stream, state) { 9892 var ch = stream.next(); 9893 curPunc = null; 9894 if (ch == "$" || ch == "?") { 9895 stream.match(/^[\w\d]*/); 9896 return "variable-2";
vendor: 4,513 bytes, lines 9897-10052
9897 } 9898 else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { 9899 stream.match(/^[^\s\u00a0>]*>?/); 9900 return "atom"; 9901 } 9902 else if (ch == "\"" || ch == "'") { 9903 state.tokenize = tokenLiteral(ch); 9904 return state.tokenize(stream, state); 9905 } 9906 else if (ch == "`") { 9907 state.tokenize = tokenOpLiteral(ch); 9908 return state.tokenize(stream, state); 9909 } 9910 else if (/[{}\(\),\.;\[\]]/.test(ch)) { 9911 curPunc = ch; 9912 return null; 9913 } 9914 else if (ch == "-" && stream.eat("-")) { 9915 stream.skipToEnd(); 9916 return "comment"; 9917 } 9918 else if (ch == "/" && stream.eat("*")) { 9919 state.tokenize = tokenComment; 9920 return state.tokenize(stream, state); 9921 } 9922 else if (operatorChars.test(ch)) { 9923 stream.eatWhile(operatorChars); 9924 return null; 9925 } 9926 else if (ch == ":") { 9927 stream.eatWhile(/[\w\d\._\-]/); 9928 return "atom"; 9929 } 9930 else { 9931 stream.eatWhile(/[_\w\d]/); 9932 if (stream.eat(":")) { 9933 stream.eatWhile(/[\w\d_\-]/); 9934 return "atom"; 9935 } 9936 var word = stream.current(), type; 9937 if (ops.test(word)) 9938 return null; 9939 else if (keywords.test(word)) 9940 return "keyword"; 9941 else 9942 return "variable"; 9943 } 9944 } 9945 9946 function tokenLiteral(quote) { 9947 return function(stream, state) { 9948 var escaped = false, ch; 9949 while ((ch = stream.next()) != null) { 9950 if (ch == quote && !escaped) { 9951 state.tokenize = tokenBase; 9952 break; 9953 } 9954 escaped = !escaped && ch == "\\"; 9955 } 9956 return "string"; 9957 }; 9958 } 9959 9960 function tokenOpLiteral(quote) { 9961 return function(stream, state) { 9962 var escaped = false, ch; 9963 while ((ch = stream.next()) != null) { 9964 if (ch == quote && !escaped) { 9965 state.tokenize = tokenBase; 9966 break; 9967 } 9968 escaped = !escaped && ch == "\\"; 9969 } 9970 return "variable-2"; 9971 }; 9972 } 9973 9974 function tokenComment(stream, state) { 9975 for (;;) { 9976 if (stream.skipTo("*")) { 9977 stream.next(); 9978 if (stream.eat("/")) { 9979 state.tokenize = tokenBase; 9980 break; 9981 } 9982 } else { 9983 stream.skipToEnd(); 9984 break; 9985 } 9986 } 9987 return "comment"; 9988 } 9989 9990 9991 function pushContext(state, type, col) { 9992 state.context = {prev: state.context, indent: state.indent, col: col, type: type}; 9993 } 9994 function popContext(state) { 9995 state.indent = state.context.indent; 9996 state.context = state.context.prev; 9997 } 9998 9999 return { 10000 startState: function(base) { 10001 return {tokenize: tokenBase, 10002 context: null, 10003 indent: 0, 10004 col: 0}; 10005 }, 10006 10007 token: function(stream, state) { 10008 if (stream.sol()) { 10009 if (state.context && state.context.align == null) state.context.align = false; 10010 state.indent = stream.indentation(); 10011 } 10012 if (stream.eatSpace()) return null; 10013 var style = state.tokenize(stream, state); 10014 10015 if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { 10016 state.context.align = true; 10017 } 10018 10019 if (curPunc == "(") pushContext(state, ")", stream.column()); 10020 else if (curPunc == "[") pushContext(state, "]", stream.column()); 10021 else if (curPunc == "{") pushContext(state, "}", stream.column()); 10022 else if (/[\]\}\)]/.test(curPunc)) { 10023 while (state.context && state.context.type == "pattern") popContext(state); 10024 if (state.context && curPunc == state.context.type) popContext(state); 10025 } 10026 else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); 10027 else if (/atom|string|variable/.test(style) && state.context) { 10028 if (/[\}\]]/.test(state.context.type)) 10029 pushContext(state, "pattern", stream.column()); 10030 else if (state.context.type == "pattern" && !state.context.align) { 10031 state.context.align = true; 10032 state.context.col = stream.column(); 10033 } 10034 } 10035 10036 return style; 10037 }, 10038 10039 indent: function(state, textAfter) { 10040 var firstChar = textAfter && textAfter.charAt(0); 10041 var context = state.context; 10042 if (/[\]\}]/.test(firstChar)) 10043 while (context && context.type == "pattern") context = context.prev; 10044 10045 var closing = context && firstChar == context.type; 10046 if (!context) 10047 return 0; 10048 else if (context.type == "pattern") 10049 return context.col; 10050 else if (context.align) 10051 return context.col + (closing ? 0 : 1); 10052 else
10053 return context.indent + (closing ? 0 : indentUnit); 10054 } 10055 }; 10056}); 10057 10058CodeMirror.defineMIME("text/x-mysql", "mysql"); 10059; 10060
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.