1 /* 2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 (function() 7 { 8 var isReplace; 9 10 function findEvaluator( node ) 11 { 12 return node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 && ( !isReplace || !node.isReadOnly() ); 13 } 14 15 /** 16 * Elements which break characters been considered as sequence. 17 */ 18 function nonCharactersBoundary( node ) 19 { 20 return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary( 21 CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$empty, CKEDITOR.dtd.$nonEditable ) ) ); 22 } 23 24 /** 25 * Get the cursor object which represent both current character and it's dom 26 * position thing. 27 */ 28 var cursorStep = function() 29 { 30 return { 31 textNode : this.textNode, 32 offset : this.offset, 33 character : this.textNode ? 34 this.textNode.getText().charAt( this.offset ) : null, 35 hitMatchBoundary : this._.matchBoundary 36 }; 37 }; 38 39 var pages = [ 'find', 'replace' ], 40 fieldsMapping = [ 41 [ 'txtFindFind', 'txtFindReplace' ], 42 [ 'txtFindCaseChk', 'txtReplaceCaseChk' ], 43 [ 'txtFindWordChk', 'txtReplaceWordChk' ], 44 [ 'txtFindCyclic', 'txtReplaceCyclic' ] ]; 45 46 /** 47 * Synchronize corresponding filed values between 'replace' and 'find' pages. 48 * @param {String} currentPageId The page id which receive values. 49 */ 50 function syncFieldsBetweenTabs( currentPageId ) 51 { 52 var sourceIndex, targetIndex, 53 sourceField, targetField; 54 55 sourceIndex = currentPageId === 'find' ? 1 : 0; 56 targetIndex = 1 - sourceIndex; 57 var i, l = fieldsMapping.length; 58 for ( i = 0 ; i < l ; i++ ) 59 { 60 sourceField = this.getContentElement( pages[ sourceIndex ], 61 fieldsMapping[ i ][ sourceIndex ] ); 62 targetField = this.getContentElement( pages[ targetIndex ], 63 fieldsMapping[ i ][ targetIndex ] ); 64 65 targetField.setValue( sourceField.getValue() ); 66 } 67 } 68 69 var findDialog = function( editor, startupPage ) 70 { 71 // Style object for highlights: (#5018) 72 // 1. Defined as full match style to avoid compromising ordinary text color styles. 73 // 2. Must be apply onto inner-most text to avoid conflicting with ordinary text color styles visually. 74 var highlightStyle = new CKEDITOR.style( 75 CKEDITOR.tools.extend( { attributes : { 'data-cke-highlight': 1 }, fullMatch : 1, ignoreReadonly : 1, childRule : function(){ return 0; } }, 76 editor.config.find_highlight, true ) ); 77 78 /** 79 * Iterator which walk through the specified range char by char. By 80 * default the walking will not stop at the character boundaries, until 81 * the end of the range is encountered. 82 * @param { CKEDITOR.dom.range } range 83 * @param {Boolean} matchWord Whether the walking will stop at character boundary. 84 */ 85 var characterWalker = function( range , matchWord ) 86 { 87 var self = this; 88 var walker = 89 new CKEDITOR.dom.walker( range ); 90 walker.guard = matchWord ? nonCharactersBoundary : function( node ) 91 { 92 !nonCharactersBoundary( node ) && ( self._.matchBoundary = true ); 93 }; 94 walker[ 'evaluator' ] = findEvaluator; 95 walker.breakOnFalse = 1; 96 97 if ( range.startContainer.type == CKEDITOR.NODE_TEXT ) 98 { 99 this.textNode = range.startContainer; 100 this.offset = range.startOffset - 1; 101 } 102 103 this._ = { 104 matchWord : matchWord, 105 walker : walker, 106 matchBoundary : false 107 }; 108 }; 109 110 characterWalker.prototype = { 111 next : function() 112 { 113 return this.move(); 114 }, 115 116 back : function() 117 { 118 return this.move( true ); 119 }, 120 121 move : function( rtl ) 122 { 123 var currentTextNode = this.textNode; 124 // Already at the end of document, no more character available. 125 if ( currentTextNode === null ) 126 return cursorStep.call( this ); 127 128 this._.matchBoundary = false; 129 130 // There are more characters in the text node, step forward. 131 if ( currentTextNode 132 && rtl 133 && this.offset > 0 ) 134 { 135 this.offset--; 136 return cursorStep.call( this ); 137 } 138 else if ( currentTextNode 139 && this.offset < currentTextNode.getLength() - 1 ) 140 { 141 this.offset++; 142 return cursorStep.call( this ); 143 } 144 else 145 { 146 currentTextNode = null; 147 // At the end of the text node, walking foward for the next. 148 while ( !currentTextNode ) 149 { 150 currentTextNode = 151 this._.walker[ rtl ? 'previous' : 'next' ].call( this._.walker ); 152 153 // Stop searching if we're need full word match OR 154 // already reach document end. 155 if ( this._.matchWord && !currentTextNode 156 || this._.walker._.end ) 157 break; 158 } 159 // Found a fresh text node. 160 this.textNode = currentTextNode; 161 if ( currentTextNode ) 162 this.offset = rtl ? currentTextNode.getLength() - 1 : 0; 163 else 164 this.offset = 0; 165 } 166 167 return cursorStep.call( this ); 168 } 169 170 }; 171 172 /** 173 * A range of cursors which represent a trunk of characters which try to 174 * match, it has the same length as the pattern string. 175 */ 176 var characterRange = function( characterWalker, rangeLength ) 177 { 178 this._ = { 179 walker : characterWalker, 180 cursors : [], 181 rangeLength : rangeLength, 182 highlightRange : null, 183 isMatched : 0 184 }; 185 }; 186 187 characterRange.prototype = { 188 /** 189 * Translate this range to {@link CKEDITOR.dom.range} 190 */ 191 toDomRange : function() 192 { 193 var range = new CKEDITOR.dom.range( editor.document ); 194 var cursors = this._.cursors; 195 if ( cursors.length < 1 ) 196 { 197 var textNode = this._.walker.textNode; 198 if ( textNode ) 199 range.setStartAfter( textNode ); 200 else 201 return null; 202 } 203 else 204 { 205 var first = cursors[0], 206 last = cursors[ cursors.length - 1 ]; 207 208 range.setStart( first.textNode, first.offset ); 209 range.setEnd( last.textNode, last.offset + 1 ); 210 } 211 212 return range; 213 }, 214 /** 215 * Reflect the latest changes from dom range. 216 */ 217 updateFromDomRange : function( domRange ) 218 { 219 var cursor, 220 walker = new characterWalker( domRange ); 221 this._.cursors = []; 222 do 223 { 224 cursor = walker.next(); 225 if ( cursor.character ) 226 this._.cursors.push( cursor ); 227 } 228 while ( cursor.character ); 229 this._.rangeLength = this._.cursors.length; 230 }, 231 232 setMatched : function() 233 { 234 this._.isMatched = true; 235 }, 236 237 clearMatched : function() 238 { 239 this._.isMatched = false; 240 }, 241 242 isMatched : function() 243 { 244 return this._.isMatched; 245 }, 246 247 /** 248 * Hightlight the current matched chunk of text. 249 */ 250 highlight : function() 251 { 252 // Do not apply if nothing is found. 253 if ( this._.cursors.length < 1 ) 254 return; 255 256 // Remove the previous highlight if there's one. 257 if ( this._.highlightRange ) 258 this.removeHighlight(); 259 260 // Apply the highlight. 261 var range = this.toDomRange(), 262 bookmark = range.createBookmark(); 263 highlightStyle.applyToRange( range ); 264 range.moveToBookmark( bookmark ); 265 this._.highlightRange = range; 266 267 // Scroll the editor to the highlighted area. 268 var element = range.startContainer; 269 if ( element.type != CKEDITOR.NODE_ELEMENT ) 270 element = element.getParent(); 271 element.scrollIntoView(); 272 273 // Update the character cursors. 274 this.updateFromDomRange( range ); 275 }, 276 277 /** 278 * Remove highlighted find result. 279 */ 280 removeHighlight : function() 281 { 282 if ( !this._.highlightRange ) 283 return; 284 285 var bookmark = this._.highlightRange.createBookmark(); 286 highlightStyle.removeFromRange( this._.highlightRange ); 287 this._.highlightRange.moveToBookmark( bookmark ); 288 this.updateFromDomRange( this._.highlightRange ); 289 this._.highlightRange = null; 290 }, 291 292 isReadOnly : function() 293 { 294 if ( !this._.highlightRange ) 295 return 0; 296 297 return this._.highlightRange.startContainer.isReadOnly(); 298 }, 299 300 moveBack : function() 301 { 302 var retval = this._.walker.back(), 303 cursors = this._.cursors; 304 305 if ( retval.hitMatchBoundary ) 306 this._.cursors = cursors = []; 307 308 cursors.unshift( retval ); 309 if ( cursors.length > this._.rangeLength ) 310 cursors.pop(); 311 312 return retval; 313 }, 314 315 moveNext : function() 316 { 317 var retval = this._.walker.next(), 318 cursors = this._.cursors; 319 320 // Clear the cursors queue if we've crossed a match boundary. 321 if ( retval.hitMatchBoundary ) 322 this._.cursors = cursors = []; 323 324 cursors.push( retval ); 325 if ( cursors.length > this._.rangeLength ) 326 cursors.shift(); 327 328 return retval; 329 }, 330 331 getEndCharacter : function() 332 { 333 var cursors = this._.cursors; 334 if ( cursors.length < 1 ) 335 return null; 336 337 return cursors[ cursors.length - 1 ].character; 338 }, 339 340 getNextCharacterRange : function( maxLength ) 341 { 342 var lastCursor, 343 nextRangeWalker, 344 cursors = this._.cursors; 345 346 if ( ( lastCursor = cursors[ cursors.length - 1 ] ) && lastCursor.textNode ) 347 nextRangeWalker = new characterWalker( getRangeAfterCursor( lastCursor ) ); 348 // In case it's an empty range (no cursors), figure out next range from walker (#4951). 349 else 350 nextRangeWalker = this._.walker; 351 352 return new characterRange( nextRangeWalker, maxLength ); 353 }, 354 355 getCursors : function() 356 { 357 return this._.cursors; 358 } 359 }; 360 361 362 // The remaining document range after the character cursor. 363 function getRangeAfterCursor( cursor , inclusive ) 364 { 365 var range = new CKEDITOR.dom.range(); 366 range.setStart( cursor.textNode, 367 ( inclusive ? cursor.offset : cursor.offset + 1 ) ); 368 range.setEndAt( editor.document.getBody(), 369 CKEDITOR.POSITION_BEFORE_END ); 370 return range; 371 } 372 373 // The document range before the character cursor. 374 function getRangeBeforeCursor( cursor ) 375 { 376 var range = new CKEDITOR.dom.range(); 377 range.setStartAt( editor.document.getBody(), 378 CKEDITOR.POSITION_AFTER_START ); 379 range.setEnd( cursor.textNode, cursor.offset ); 380 return range; 381 } 382 383 var KMP_NOMATCH = 0, 384 KMP_ADVANCED = 1, 385 KMP_MATCHED = 2; 386 /** 387 * Examination the occurrence of a word which implement KMP algorithm. 388 */ 389 var kmpMatcher = function( pattern, ignoreCase ) 390 { 391 var overlap = [ -1 ]; 392 if ( ignoreCase ) 393 pattern = pattern.toLowerCase(); 394 for ( var i = 0 ; i < pattern.length ; i++ ) 395 { 396 overlap.push( overlap[i] + 1 ); 397 while ( overlap[ i + 1 ] > 0 398 && pattern.charAt( i ) != pattern 399 .charAt( overlap[ i + 1 ] - 1 ) ) 400 overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1; 401 } 402 403 this._ = { 404 overlap : overlap, 405 state : 0, 406 ignoreCase : !!ignoreCase, 407 pattern : pattern 408 }; 409 }; 410 411 kmpMatcher.prototype = 412 { 413 feedCharacter : function( c ) 414 { 415 if ( this._.ignoreCase ) 416 c = c.toLowerCase(); 417 418 while ( true ) 419 { 420 if ( c == this._.pattern.charAt( this._.state ) ) 421 { 422 this._.state++; 423 if ( this._.state == this._.pattern.length ) 424 { 425 this._.state = 0; 426 return KMP_MATCHED; 427 } 428 return KMP_ADVANCED; 429 } 430 else if ( !this._.state ) 431 return KMP_NOMATCH; 432 else 433 this._.state = this._.overlap[ this._.state ]; 434 } 435 436 return null; 437 }, 438 439 reset : function() 440 { 441 this._.state = 0; 442 } 443 }; 444 445 var wordSeparatorRegex = 446 /[.,"'?!;: \u0085\u00a0\u1680\u280e\u2028\u2029\u202f\u205f\u3000]/; 447 448 var isWordSeparator = function( c ) 449 { 450 if ( !c ) 451 return true; 452 var code = c.charCodeAt( 0 ); 453 return ( code >= 9 && code <= 0xd ) 454 || ( code >= 0x2000 && code <= 0x200a ) 455 || wordSeparatorRegex.test( c ); 456 }; 457 458 var finder = { 459 searchRange : null, 460 matchRange : null, 461 find : function( pattern, matchCase, matchWord, matchCyclic, highlightMatched, cyclicRerun ) 462 { 463 if ( !this.matchRange ) 464 this.matchRange = 465 new characterRange( 466 new characterWalker( this.searchRange ), 467 pattern.length ); 468 else 469 { 470 this.matchRange.removeHighlight(); 471 this.matchRange = this.matchRange.getNextCharacterRange( pattern.length ); 472 } 473 474 var matcher = new kmpMatcher( pattern, !matchCase ), 475 matchState = KMP_NOMATCH, 476 character = '%'; 477 478 while ( character !== null ) 479 { 480 this.matchRange.moveNext(); 481 while ( ( character = this.matchRange.getEndCharacter() ) ) 482 { 483 matchState = matcher.feedCharacter( character ); 484 if ( matchState == KMP_MATCHED ) 485 break; 486 if ( this.matchRange.moveNext().hitMatchBoundary ) 487 matcher.reset(); 488 } 489 490 if ( matchState == KMP_MATCHED ) 491 { 492 if ( matchWord ) 493 { 494 var cursors = this.matchRange.getCursors(), 495 tail = cursors[ cursors.length - 1 ], 496 head = cursors[ 0 ]; 497 498 var rangeBefore = getRangeBeforeCursor( head ), 499 rangeAfter = getRangeAfterCursor( tail ); 500 501 // The word boundary checks requires to trim the text nodes. (#9036) 502 rangeBefore.trim(); 503 rangeAfter.trim(); 504 505 var headWalker = new characterWalker( rangeBefore, true ), 506 tailWalker = new characterWalker( rangeAfter, true ); 507 508 if ( ! ( isWordSeparator( headWalker.back().character ) 509 && isWordSeparator( tailWalker.next().character ) ) ) 510 continue; 511 } 512 this.matchRange.setMatched(); 513 if ( highlightMatched !== false ) 514 this.matchRange.highlight(); 515 return true; 516 } 517 } 518 519 this.matchRange.clearMatched(); 520 this.matchRange.removeHighlight(); 521 // Clear current session and restart with the default search 522 // range. 523 // Re-run the finding once for cyclic.(#3517) 524 if ( matchCyclic && !cyclicRerun ) 525 { 526 this.searchRange = getSearchRange( 1 ); 527 this.matchRange = null; 528 return arguments.callee.apply( this, 529 Array.prototype.slice.call( arguments ).concat( [ true ] ) ); 530 } 531 532 return false; 533 }, 534 535 /** 536 * Record how much replacement occurred toward one replacing. 537 */ 538 replaceCounter : 0, 539 540 replace : function( dialog, pattern, newString, matchCase, matchWord, 541 matchCyclic , isReplaceAll ) 542 { 543 isReplace = 1; 544 545 // Successiveness of current replace/find. 546 var result = 0; 547 548 // 1. Perform the replace when there's already a match here. 549 // 2. Otherwise perform the find but don't replace it immediately. 550 if ( this.matchRange && this.matchRange.isMatched() 551 && !this.matchRange._.isReplaced && !this.matchRange.isReadOnly() ) 552 { 553 // Turn off highlight for a while when saving snapshots. 554 this.matchRange.removeHighlight(); 555 var domRange = this.matchRange.toDomRange(); 556 var text = editor.document.createText( newString ); 557 if ( !isReplaceAll ) 558 { 559 // Save undo snaps before and after the replacement. 560 var selection = editor.getSelection(); 561 selection.selectRanges( [ domRange ] ); 562 editor.fire( 'saveSnapshot' ); 563 } 564 domRange.deleteContents(); 565 domRange.insertNode( text ); 566 if ( !isReplaceAll ) 567 { 568 selection.selectRanges( [ domRange ] ); 569 editor.fire( 'saveSnapshot' ); 570 } 571 this.matchRange.updateFromDomRange( domRange ); 572 if ( !isReplaceAll ) 573 this.matchRange.highlight(); 574 this.matchRange._.isReplaced = true; 575 this.replaceCounter++; 576 result = 1; 577 } 578 else 579 result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll ); 580 581 isReplace = 0; 582 583 return result; 584 } 585 }; 586 587 /** 588 * The range in which find/replace happened, receive from user 589 * selection prior. 590 */ 591 function getSearchRange( isDefault ) 592 { 593 var searchRange, 594 sel = editor.getSelection(), 595 body = editor.document.getBody(); 596 if ( sel && !isDefault ) 597 { 598 searchRange = sel.getRanges()[ 0 ].clone(); 599 searchRange.collapse( true ); 600 } 601 else 602 { 603 searchRange = new CKEDITOR.dom.range(); 604 searchRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); 605 } 606 searchRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); 607 return searchRange; 608 } 609 610 var lang = editor.lang.findAndReplace; 611 return { 612 title : lang.title, 613 resizable : CKEDITOR.DIALOG_RESIZE_NONE, 614 minWidth : 350, 615 minHeight : 170, 616 buttons : [ CKEDITOR.dialog.cancelButton ], // Cancel button only. 617 contents : [ 618 { 619 id : 'find', 620 label : lang.find, 621 title : lang.find, 622 accessKey : '', 623 elements : [ 624 { 625 type : 'hbox', 626 widths : [ '230px', '90px' ], 627 children : 628 [ 629 { 630 type : 'text', 631 id : 'txtFindFind', 632 label : lang.findWhat, 633 isChanged : false, 634 labelLayout : 'horizontal', 635 accessKey : 'F' 636 }, 637 { 638 type : 'button', 639 id : 'btnFind', 640 align : 'left', 641 style : 'width:100%', 642 label : lang.find, 643 onClick : function() 644 { 645 var dialog = this.getDialog(); 646 if ( !finder.find( dialog.getValueOf( 'find', 'txtFindFind' ), 647 dialog.getValueOf( 'find', 'txtFindCaseChk' ), 648 dialog.getValueOf( 'find', 'txtFindWordChk' ), 649 dialog.getValueOf( 'find', 'txtFindCyclic' ) ) ) 650 alert( lang 651 .notFoundMsg ); 652 } 653 } 654 ] 655 }, 656 { 657 type : 'fieldset', 658 label : CKEDITOR.tools.htmlEncode( lang.findOptions ), 659 style : 'margin-top:29px', 660 children : 661 [ 662 { 663 type : 'vbox', 664 padding : 0, 665 children : 666 [ 667 { 668 type : 'checkbox', 669 id : 'txtFindCaseChk', 670 isChanged : false, 671 label : lang.matchCase 672 }, 673 { 674 type : 'checkbox', 675 id : 'txtFindWordChk', 676 isChanged : false, 677 label : lang.matchWord 678 }, 679 { 680 type : 'checkbox', 681 id : 'txtFindCyclic', 682 isChanged : false, 683 'default' : true, 684 label : lang.matchCyclic 685 } 686 ] 687 } 688 ] 689 } 690 ] 691 }, 692 { 693 id : 'replace', 694 label : lang.replace, 695 accessKey : 'M', 696 elements : [ 697 { 698 type : 'hbox', 699 widths : [ '230px', '90px' ], 700 children : 701 [ 702 { 703 type : 'text', 704 id : 'txtFindReplace', 705 label : lang.findWhat, 706 isChanged : false, 707 labelLayout : 'horizontal', 708 accessKey : 'F' 709 }, 710 { 711 type : 'button', 712 id : 'btnFindReplace', 713 align : 'left', 714 style : 'width:100%', 715 label : lang.replace, 716 onClick : function() 717 { 718 var dialog = this.getDialog(); 719 if ( !finder.replace( dialog, 720 dialog.getValueOf( 'replace', 'txtFindReplace' ), 721 dialog.getValueOf( 'replace', 'txtReplace' ), 722 dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ), 723 dialog.getValueOf( 'replace', 'txtReplaceWordChk' ), 724 dialog.getValueOf( 'replace', 'txtReplaceCyclic' ) ) ) 725 alert( lang 726 .notFoundMsg ); 727 } 728 } 729 ] 730 }, 731 { 732 type : 'hbox', 733 widths : [ '230px', '90px' ], 734 children : 735 [ 736 { 737 type : 'text', 738 id : 'txtReplace', 739 label : lang.replaceWith, 740 isChanged : false, 741 labelLayout : 'horizontal', 742 accessKey : 'R' 743 }, 744 { 745 type : 'button', 746 id : 'btnReplaceAll', 747 align : 'left', 748 style : 'width:100%', 749 label : lang.replaceAll, 750 isChanged : false, 751 onClick : function() 752 { 753 var dialog = this.getDialog(); 754 var replaceNums; 755 756 finder.replaceCounter = 0; 757 758 // Scope to full document. 759 finder.searchRange = getSearchRange( 1 ); 760 if ( finder.matchRange ) 761 { 762 finder.matchRange.removeHighlight(); 763 finder.matchRange = null; 764 } 765 editor.fire( 'saveSnapshot' ); 766 while ( finder.replace( dialog, 767 dialog.getValueOf( 'replace', 'txtFindReplace' ), 768 dialog.getValueOf( 'replace', 'txtReplace' ), 769 dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ), 770 dialog.getValueOf( 'replace', 'txtReplaceWordChk' ), 771 false, true ) ) 772 { /*jsl:pass*/ } 773 774 if ( finder.replaceCounter ) 775 { 776 alert( lang.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) ); 777 editor.fire( 'saveSnapshot' ); 778 } 779 else 780 alert( lang.notFoundMsg ); 781 } 782 } 783 ] 784 }, 785 { 786 type : 'fieldset', 787 label : CKEDITOR.tools.htmlEncode( lang.findOptions ), 788 children : 789 [ 790 { 791 type : 'vbox', 792 padding : 0, 793 children : 794 [ 795 { 796 type : 'checkbox', 797 id : 'txtReplaceCaseChk', 798 isChanged : false, 799 label : lang.matchCase 800 }, 801 { 802 type : 'checkbox', 803 id : 'txtReplaceWordChk', 804 isChanged : false, 805 label : lang.matchWord 806 }, 807 { 808 type : 'checkbox', 809 id : 'txtReplaceCyclic', 810 isChanged : false, 811 'default' : true, 812 label : lang.matchCyclic 813 } 814 ] 815 } 816 ] 817 } 818 ] 819 } 820 ], 821 onLoad : function() 822 { 823 var dialog = this; 824 825 // Keep track of the current pattern field in use. 826 var patternField, wholeWordChkField; 827 828 // Ignore initial page select on dialog show 829 var isUserSelect = 0; 830 this.on( 'hide', function() 831 { 832 isUserSelect = 0; 833 }); 834 this.on( 'show', function() 835 { 836 isUserSelect = 1; 837 }); 838 839 this.selectPage = CKEDITOR.tools.override( this.selectPage, function( originalFunc ) 840 { 841 return function( pageId ) 842 { 843 originalFunc.call( dialog, pageId ); 844 845 var currPage = dialog._.tabs[ pageId ]; 846 var patternFieldInput, patternFieldId, wholeWordChkFieldId; 847 patternFieldId = pageId === 'find' ? 'txtFindFind' : 'txtFindReplace'; 848 wholeWordChkFieldId = pageId === 'find' ? 'txtFindWordChk' : 'txtReplaceWordChk'; 849 850 patternField = dialog.getContentElement( pageId, 851 patternFieldId ); 852 wholeWordChkField = dialog.getContentElement( pageId, 853 wholeWordChkFieldId ); 854 855 // Prepare for check pattern text filed 'keyup' event 856 if ( !currPage.initialized ) 857 { 858 patternFieldInput = CKEDITOR.document 859 .getById( patternField._.inputId ); 860 currPage.initialized = true; 861 } 862 863 // Synchronize fields on tab switch. 864 if ( isUserSelect ) 865 syncFieldsBetweenTabs.call( this, pageId ); 866 }; 867 } ); 868 869 }, 870 onShow : function() 871 { 872 // Establish initial searching start position. 873 finder.searchRange = getSearchRange(); 874 875 // Fill in the find field with selected text. 876 var selectedText = this.getParentEditor().getSelection().getSelectedText(), 877 patternFieldId = ( startupPage == 'find' ? 'txtFindFind' : 'txtFindReplace' ); 878 879 var field = this.getContentElement( startupPage, patternFieldId ); 880 field.setValue( selectedText ); 881 field.select(); 882 883 this.selectPage( startupPage ); 884 885 this[ ( startupPage == 'find' && this._.editor.readOnly? 'hide' : 'show' ) + 'Page' ]( 'replace'); 886 }, 887 onHide : function() 888 { 889 var range; 890 if ( finder.matchRange && finder.matchRange.isMatched() ) 891 { 892 finder.matchRange.removeHighlight(); 893 editor.focus(); 894 895 range = finder.matchRange.toDomRange(); 896 if ( range ) 897 editor.getSelection().selectRanges( [ range ] ); 898 } 899 900 // Clear current session before dialog close 901 delete finder.matchRange; 902 }, 903 onFocus : function() 904 { 905 if ( startupPage == 'replace' ) 906 return this.getContentElement( 'replace', 'txtFindReplace' ); 907 else 908 return this.getContentElement( 'find', 'txtFindFind' ); 909 } 910 }; 911 }; 912 913 CKEDITOR.dialog.add( 'find', function( editor ) 914 { 915 return findDialog( editor, 'find' ); 916 }); 917 918 CKEDITOR.dialog.add( 'replace', function( editor ) 919 { 920 return findDialog( editor, 'replace' ); 921 }); 922 })(); 923