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