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