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