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