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