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