]>
Commit | Line | Data |
---|---|---|
7adcb81e | 1 | /**\r |
3b35bd27 | 2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.\r |
7adcb81e IB |
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 |