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