\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 false; } },\r
+ editor.config.find_highlight ) );\r
\r
/**\r
* Iterator which walk through the specified range char by char. By\r
{\r
var walker =\r
new CKEDITOR.dom.walker( range );\r
- walker[ matchWord ? 'guard' : 'evaluator' ] =\r
- guardDomWalkerNonEmptyTextNode;\r
+ walker.guard = matchWord ? nonCharactersBoundary : null;\r
+ walker[ 'evaluator' ] = findEvaluator;\r
walker.breakOnFalse = true;\r
\r
this._ = {\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
\r
// There are more characters in the text node, step forward.\r
- if( currentTextNode\r
+ if ( currentTextNode\r
&& rtl\r
&& this.offset > 0 )\r
{\r
this.offset--;\r
return cursorStep.call( this );\r
}\r
- else if( currentTextNode\r
+ else if ( currentTextNode\r
&& this.offset < currentTextNode.getLength() - 1 )\r
{\r
this.offset++;\r
break;\r
\r
// Marking as match character boundaries.\r
- if( !currentTextNode\r
- && checkCharactersBoundary( this._.walker.current ) )\r
+ if ( !currentTextNode\r
+ && !nonCharactersBoundary( this._.walker.current ) )\r
this._.matchBoundary = true;\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
getNextCharacterRange : function( maxLength )\r
{\r
var lastCursor,\r
+ nextRangeWalker,\r
cursors = this._.cursors;\r
- if ( !( lastCursor = cursors[ cursors.length - 1 ] ) )\r
- return null;\r
- return new characterRange(\r
- new characterWalker(\r
- getRangeAfterCursor( lastCursor ) ),\r
- maxLength );\r
+\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
+ nextRangeWalker = this._.walker;\r
+\r
+ return new characterRange( nextRangeWalker, maxLength );\r
},\r
\r
getCursors : function()\r
matchRange : null,\r
find : function( pattern, matchCase, matchWord, matchCyclic, highlightMatched, cyclicRerun )\r
{\r
- if( !this.matchRange )\r
+ if ( !this.matchRange )\r
this.matchRange =\r
new characterRange(\r
new characterWalker( this.searchRange ),\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
\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
else\r
result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll );\r
\r
+ isReplace = 0;\r
+\r
return result;\r
}\r
};\r
finder.matchRange = null;\r
}\r
editor.fire( 'saveSnapshot' );\r
- while( finder.replace( dialog,\r
+ while ( finder.replace( dialog,\r
dialog.getValueOf( 'replace', 'txtFindReplace' ),\r
dialog.getValueOf( 'replace', 'txtReplace' ),\r
dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),\r
currPage.initialized = true;\r
}\r
\r
- if( isUserSelect )\r
+ if ( isUserSelect )\r
// synchronize fields on tab switch.\r
syncFieldsBetweenTabs.call( this, pageId );\r
};\r
// Establish initial searching start position.\r
finder.searchRange = getSearchRange();\r
\r
- if ( startupPage == 'replace' )\r
- this.getContentElement( 'replace', 'txtFindReplace' ).focus();\r
- else\r
- this.getContentElement( 'find', 'txtFindFind' ).focus();\r
+ this.selectPage( startupPage );\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
delete finder.matchRange;\r
+ },\r
+ onFocus : function()\r
+ {\r
+ if ( startupPage == 'replace' )\r
+ return this.getContentElement( 'replace', 'txtFindReplace' );\r
+ else\r
+ return this.getContentElement( 'find', 'txtFindFind' );\r
}\r
};\r
};\r