find.js 23 KB

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