/*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
(function()\r
{\r
- function guardDomWalkerNonEmptyTextNode( node )\r
+ var isReplace;\r
+\r
+ function findEvaluator( node )\r
{\r
- return ( node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 );\r
+ return node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 && ( !isReplace || !node.isReadOnly() );\r
}\r
\r
/**\r
* Elements which break characters been considered as sequence.\r
*/\r
- function checkCharactersBoundary ( node )\r
+ function nonCharactersBoundary( node )\r
{\r
- var dtd = CKEDITOR.dtd;\r
- return node.isBlockBoundary(\r
- CKEDITOR.tools.extend( {}, dtd.$empty, dtd.$nonEditable ) );\r
+ return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary(\r
+ CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$empty, CKEDITOR.dtd.$nonEditable ) ) );\r
}\r
\r
/**\r
\r
var findDialog = function( editor, startupPage )\r
{\r
- // Style object for highlights.\r
- var highlightStyle = new CKEDITOR.style( editor.config.find_highlight );\r
+ // Style object for highlights: (#5018)\r
+ // 1. Defined as full match style to avoid compromising ordinary text color styles.\r
+ // 2. Must be apply onto inner-most text to avoid conflicting with ordinary text color styles visually.\r
+ var highlightStyle = new CKEDITOR.style( CKEDITOR.tools.extend( { fullMatch : true, childRule : function(){ return 0; } },\r
+ editor.config.find_highlight ) );\r
\r
/**\r
* Iterator which walk through the specified range char by char. By\r
*/\r
var characterWalker = function( range , matchWord )\r
{\r
+ var self = this;\r
var walker =\r
new CKEDITOR.dom.walker( range );\r
- walker[ matchWord ? 'guard' : 'evaluator' ] =\r
- guardDomWalkerNonEmptyTextNode;\r
- walker.breakOnFalse = true;\r
+ walker.guard = matchWord ? nonCharactersBoundary : function( node )\r
+ {\r
+ !nonCharactersBoundary( node ) && ( self._.matchBoundary = true );\r
+ };\r
+ walker[ 'evaluator' ] = findEvaluator;\r
+ walker.breakOnFalse = 1;\r
+\r
+ if ( range.startContainer.type == CKEDITOR.NODE_TEXT )\r
+ {\r
+ this.textNode = range.startContainer;\r
+ this.offset = range.startOffset - 1;\r
+ }\r
\r
this._ = {\r
matchWord : matchWord,\r
{\r
var currentTextNode = this.textNode;\r
// Already at the end of document, no more character available.\r
- if ( currentTextNode === null )\r
+ if ( currentTextNode === null )\r
return cursorStep.call( this );\r
\r
this._.matchBoundary = false;\r
// Stop searching if we're need full word match OR\r
// already reach document end.\r
if ( this._.matchWord && !currentTextNode\r
- ||this._.walker._.end )\r
+ || this._.walker._.end )\r
break;\r
-\r
- // Marking as match character boundaries.\r
- if ( !currentTextNode\r
- && checkCharactersBoundary( this._.walker.current ) )\r
- this._.matchBoundary = true;\r
-\r
}\r
// Found a fresh text node.\r
this.textNode = currentTextNode;\r
cursors : [],\r
rangeLength : rangeLength,\r
highlightRange : null,\r
- isMatched : false\r
+ isMatched : 0\r
};\r
};\r
\r
*/\r
toDomRange : function()\r
{\r
+ var range = new CKEDITOR.dom.range( editor.document );\r
var cursors = this._.cursors;\r
if ( cursors.length < 1 )\r
- return null;\r
+ {\r
+ var textNode = this._.walker.textNode;\r
+ if ( textNode )\r
+ range.setStartAfter( textNode );\r
+ else\r
+ return null;\r
+ }\r
+ else\r
+ {\r
+ var first = cursors[0],\r
+ last = cursors[ cursors.length - 1 ];\r
\r
- var first = cursors[0],\r
- last = cursors[ cursors.length - 1 ],\r
- range = new CKEDITOR.dom.range( editor.document );\r
+ range.setStart( first.textNode, first.offset );\r
+ range.setEnd( last.textNode, last.offset + 1 );\r
+ }\r
\r
- range.setStart( first.textNode, first.offset );\r
- range.setEnd( last.textNode, last.offset + 1 );\r
return range;\r
},\r
/**\r
this.removeHighlight();\r
\r
// Apply the highlight.\r
- var range = this.toDomRange();\r
+ var range = this.toDomRange(),\r
+ bookmark = range.createBookmark();\r
highlightStyle.applyToRange( range );\r
+ range.moveToBookmark( bookmark );\r
this._.highlightRange = range;\r
\r
// Scroll the editor to the highlighted area.\r
if ( !this._.highlightRange )\r
return;\r
\r
+ var bookmark = this._.highlightRange.createBookmark();\r
highlightStyle.removeFromRange( this._.highlightRange );\r
+ this._.highlightRange.moveToBookmark( bookmark );\r
this.updateFromDomRange( this._.highlightRange );\r
this._.highlightRange = null;\r
},\r
\r
+ isReadOnly : function()\r
+ {\r
+ if ( !this._.highlightRange )\r
+ return 0;\r
+\r
+ return this._.highlightRange.startContainer.isReadOnly();\r
+ },\r
+\r
moveBack : function()\r
{\r
var retval = this._.walker.back(),\r
nextRangeWalker,\r
cursors = this._.cursors;\r
\r
- if ( ( lastCursor = cursors[ cursors.length - 1 ] ) )\r
+ if ( ( lastCursor = cursors[ cursors.length - 1 ] ) && lastCursor.textNode )\r
nextRangeWalker = new characterWalker( getRangeAfterCursor( lastCursor ) );\r
// In case it's an empty range (no cursors), figure out next range from walker (#4951).\r
else\r
// Re-run the finding once for cyclic.(#3517)\r
if ( matchCyclic && !cyclicRerun )\r
{\r
- this.searchRange = getSearchRange( true );\r
+ this.searchRange = getSearchRange( 1 );\r
this.matchRange = null;\r
return arguments.callee.apply( this,\r
Array.prototype.slice.call( arguments ).concat( [ true ] ) );\r
replace : function( dialog, pattern, newString, matchCase, matchWord,\r
matchCyclic , isReplaceAll )\r
{\r
+ isReplace = 1;\r
+\r
// Successiveness of current replace/find.\r
- var result = false;\r
+ var result = 0;\r
\r
// 1. Perform the replace when there's already a match here.\r
// 2. Otherwise perform the find but don't replace it immediately.\r
if ( this.matchRange && this.matchRange.isMatched()\r
- && !this.matchRange._.isReplaced )\r
+ && !this.matchRange._.isReplaced && !this.matchRange.isReadOnly() )\r
{\r
// Turn off highlight for a while when saving snapshots.\r
this.matchRange.removeHighlight();\r
this.matchRange.highlight();\r
this.matchRange._.isReplaced = true;\r
this.replaceCounter++;\r
- result = true;\r
+ result = 1;\r
}\r
else\r
result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll );\r
\r
+ isReplace = 0;\r
+\r
return result;\r
}\r
};\r
return searchRange;\r
}\r
\r
+ var lang = editor.lang.findAndReplace;\r
return {\r
- title : editor.lang.findAndReplace.title,\r
+ title : lang.title,\r
resizable : CKEDITOR.DIALOG_RESIZE_NONE,\r
minWidth : 350,\r
- minHeight : 165,\r
- buttons : [ CKEDITOR.dialog.cancelButton ], //Cancel button only.\r
+ minHeight : 170,\r
+ buttons : [ CKEDITOR.dialog.cancelButton ], // Cancel button only.\r
contents : [\r
{\r
id : 'find',\r
- label : editor.lang.findAndReplace.find,\r
- title : editor.lang.findAndReplace.find,\r
+ label : lang.find,\r
+ title : lang.find,\r
accessKey : '',\r
elements : [\r
{\r
{\r
type : 'text',\r
id : 'txtFindFind',\r
- label : editor.lang.findAndReplace.findWhat,\r
+ label : lang.findWhat,\r
isChanged : false,\r
labelLayout : 'horizontal',\r
accessKey : 'F'\r
type : 'button',\r
align : 'left',\r
style : 'width:100%',\r
- label : editor.lang.findAndReplace.find,\r
+ label : lang.find,\r
onClick : function()\r
{\r
var dialog = this.getDialog();\r
dialog.getValueOf( 'find', 'txtFindCaseChk' ),\r
dialog.getValueOf( 'find', 'txtFindWordChk' ),\r
dialog.getValueOf( 'find', 'txtFindCyclic' ) ) )\r
- alert( editor.lang.findAndReplace\r
+ alert( lang\r
.notFoundMsg );\r
}\r
}\r
id : 'txtFindCaseChk',\r
isChanged : false,\r
style : 'margin-top:28px',\r
- label : editor.lang.findAndReplace.matchCase\r
+ label : lang.matchCase\r
},\r
{\r
type : 'checkbox',\r
id : 'txtFindWordChk',\r
isChanged : false,\r
- label : editor.lang.findAndReplace.matchWord\r
+ label : lang.matchWord\r
},\r
{\r
type : 'checkbox',\r
id : 'txtFindCyclic',\r
isChanged : false,\r
'default' : true,\r
- label : editor.lang.findAndReplace.matchCyclic\r
+ label : lang.matchCyclic\r
}\r
]\r
}\r
},\r
{\r
id : 'replace',\r
- label : editor.lang.findAndReplace.replace,\r
+ label : lang.replace,\r
accessKey : 'M',\r
elements : [\r
{\r
{\r
type : 'text',\r
id : 'txtFindReplace',\r
- label : editor.lang.findAndReplace.findWhat,\r
+ label : lang.findWhat,\r
isChanged : false,\r
labelLayout : 'horizontal',\r
accessKey : 'F'\r
type : 'button',\r
align : 'left',\r
style : 'width:100%',\r
- label : editor.lang.findAndReplace.replace,\r
+ label : lang.replace,\r
onClick : function()\r
{\r
var dialog = this.getDialog();\r
dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),\r
dialog.getValueOf( 'replace', 'txtReplaceWordChk' ),\r
dialog.getValueOf( 'replace', 'txtReplaceCyclic' ) ) )\r
- alert( editor.lang.findAndReplace\r
+ alert( lang\r
.notFoundMsg );\r
}\r
}\r
{\r
type : 'text',\r
id : 'txtReplace',\r
- label : editor.lang.findAndReplace.replaceWith,\r
+ label : lang.replaceWith,\r
isChanged : false,\r
labelLayout : 'horizontal',\r
accessKey : 'R'\r
type : 'button',\r
align : 'left',\r
style : 'width:100%',\r
- label : editor.lang.findAndReplace.replaceAll,\r
+ label : lang.replaceAll,\r
isChanged : false,\r
onClick : function()\r
{\r
finder.replaceCounter = 0;\r
\r
// Scope to full document.\r
- finder.searchRange = getSearchRange( true );\r
+ finder.searchRange = getSearchRange( 1 );\r
if ( finder.matchRange )\r
{\r
finder.matchRange.removeHighlight();\r
\r
if ( finder.replaceCounter )\r
{\r
- alert( editor.lang.findAndReplace.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) );\r
+ alert( lang.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) );\r
editor.fire( 'saveSnapshot' );\r
}\r
else\r
- alert( editor.lang.findAndReplace.notFoundMsg );\r
+ alert( lang.notFoundMsg );\r
}\r
}\r
]\r
type : 'checkbox',\r
id : 'txtReplaceCaseChk',\r
isChanged : false,\r
- label : editor.lang.findAndReplace\r
+ label : lang\r
.matchCase\r
},\r
{\r
type : 'checkbox',\r
id : 'txtReplaceWordChk',\r
isChanged : false,\r
- label : editor.lang.findAndReplace\r
+ label : lang\r
.matchWord\r
},\r
{\r
id : 'txtReplaceCyclic',\r
isChanged : false,\r
'default' : true,\r
- label : editor.lang.findAndReplace\r
+ label : lang\r
.matchCyclic\r
}\r
]\r
{\r
var dialog = this;\r
\r
- //keep track of the current pattern field in use.\r
+ // Keep track of the current pattern field in use.\r
var patternField, wholeWordChkField;\r
\r
- //Ignore initial page select on dialog show\r
- var isUserSelect = false;\r
- this.on('hide', function()\r
+ // Ignore initial page select on dialog show\r
+ var isUserSelect = 0;\r
+ this.on( 'hide', function()\r
{\r
- isUserSelect = false;\r
- } );\r
- this.on('show', function()\r
+ isUserSelect = 0;\r
+ });\r
+ this.on( 'show', function()\r
{\r
- isUserSelect = true;\r
- } );\r
+ isUserSelect = 1;\r
+ });\r
\r
this.selectPage = CKEDITOR.tools.override( this.selectPage, function( originalFunc )\r
{\r
wholeWordChkField = dialog.getContentElement( pageId,\r
wholeWordChkFieldId );\r
\r
- // prepare for check pattern text filed 'keyup' event\r
+ // Prepare for check pattern text filed 'keyup' event\r
if ( !currPage.initialized )\r
{\r
patternFieldInput = CKEDITOR.document\r
currPage.initialized = true;\r
}\r
\r
+ // Synchronize fields on tab switch.\r
if ( isUserSelect )\r
- // synchronize fields on tab switch.\r
syncFieldsBetweenTabs.call( this, pageId );\r
};\r
} );\r
},\r
onHide : function()\r
{\r
+ var range;\r
if ( finder.matchRange && finder.matchRange.isMatched() )\r
{\r
finder.matchRange.removeHighlight();\r
editor.focus();\r
- editor.getSelection().selectRanges(\r
- [ finder.matchRange.toDomRange() ] );\r
+\r
+ range = finder.matchRange.toDomRange();\r
+ if ( range )\r
+ editor.getSelection().selectRanges( [ range ] );\r
}\r
\r
// Clear current session before dialog close\r