1/* 2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net 3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben 4 * 5 * == BEGIN LICENSE == 6 * 7 * Licensed under the terms of any of the following licenses at your 8 * choice: 9 * 10 * - GNU General Public License Version 2 or later (the "GPL") 11 * http://www.gnu.org/licenses/gpl.html 12 * 13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") 14 * http://www.gnu.org/licenses/lgpl.html 15 * 16 * - Mozilla Public License Version 1.1 or later (the "MPL") 17 * http://www.mozilla.org/MPL/MPL-1.1.html 18 * 19 * == END LICENSE == 20 * 21 * Creation and initialization of the "FCK" object. This is the main 22 * object that represents an editor instance. 23 * (Gecko specific implementations) 24 */ 25 26FCK.Description = "FCKeditor for Gecko Browsers" ; 27 28FCK.InitializeBehaviors = function() 29{ 30 // When calling "SetData", the editing area IFRAME gets a fixed height. So we must recalculate it. 31 if ( FCKBrowserInfo.IsGecko ) // Not for Safari/Opera. 32 Window_OnResize() ; 33 34 FCKFocusManager.AddWindow( this.EditorWindow ) ; 35 36 this.ExecOnSelectionChange = function() 37 { 38 FCK.Events.FireEvent( "OnSelectionChange" ) ; 39 } 40 41 this._ExecDrop = function( evt ) 42 { 43 if ( FCK.MouseDownFlag ) 44 { 45 FCK.MouseDownFlag = false ; 46 return ; 47 } 48 if ( FCKConfig.ForcePasteAsPlainText ) 49 { 50 if ( evt.dataTransfer ) 51 { 52 var text = evt.dataTransfer.getData( 'Text' ) ; 53 text = FCKTools.HTMLEncode( text ) ; 54 text = FCKTools.ProcessLineBreaks( window, FCKConfig, text ) ; 55 FCK.InsertHtml( text ) ; 56 } 57 else if ( FCKConfig.ShowDropDialog ) 58 FCK.PasteAsPlainText() ; 59 } 60 else if ( FCKConfig.ShowDropDialog ) 61 FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ; 62 evt.preventDefault() ; 63 evt.stopPropagation() ; 64 } 65 66 this._ExecCheckCaret = function( evt ) 67 { 68 if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG ) 69 return ; 70 71 if ( evt.type == 'keypress' ) 72 { 73 var keyCode = evt.keyCode ; 74 // ignore if positioning key is not pressed. 75 // left or up arrow keys need to be processed as well, since <a> links can be expanded in Gecko's editor 76 // when the caret moved left or up from another block element below. 77 if ( keyCode < 33 || keyCode > 40 ) 78 return ; 79 } 80 81 var blockEmptyStop = function( node ) 82 { 83 if ( node.nodeType != 1 ) 84 return false ; 85 var tag = node.tagName.toLowerCase() ; 86 return ( FCKListsLib.BlockElements[tag] || FCKListsLib.EmptyElements[tag] ) ; 87 } 88 89 var moveCursor = function() 90 { 91 var selection = FCK.EditorWindow.getSelection() ; 92 var range = selection.getRangeAt(0) ; 93 if ( ! range || ! range.collapsed ) 94 return ; 95 96 var node = range.endContainer ; 97 98 // only perform the patched behavior if we're at the end of a text node. 99 if ( node.nodeType != 3 ) 100 return ; 101 102 if ( node.nodeValue.length != range.endOffset ) 103 return ; 104 105 // only perform the patched behavior if we're in an <a> tag, or the End key is pressed. 106 var parentTag = node.parentNode.tagName.toLowerCase() ; 107 if ( ! ( parentTag == 'a' || 108 ( ! ( FCKListsLib.BlockElements[parentTag] || FCKListsLib.NonEmptyBlockElements[parentTag] ) 109 && keyCode == 35 ) ) ) 110 return ; 111 112 // our caret has moved to just after the last character of a text node under an unknown tag, how to proceed? 113 // first, see if there are other text nodes by DFS walking from this text node. 114 // - if the DFS has scanned all nodes under my parent, then go the next step. 115 // - if there is a text node after me but still under my parent, then do nothing and return. 116 var nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode, blockEmptyStop ) ; 117 if ( nextTextNode ) 118 return ; 119 120 // we're pretty sure we need to move the caret forcefully from here. 121 range = FCK.EditorDocument.createRange() ; 122 123 nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode.parentNode, blockEmptyStop ) ; 124 if ( nextTextNode ) 125 { 126 // Opera thinks the dummy empty text node we append beyond the end of <a> nodes occupies a caret 127 // position. So if the user presses the left key and we reset the caret position here, the user 128 // wouldn't be able to go back. 129 if ( FCKBrowserInfo.IsOpera && keyCode == 37 ) 130 return ; 131 132 // now we want to get out of our current parent node, adopt the next parent, and move the caret to 133 // the appropriate text node under our new parent. 134 // our new parent might be our current parent's siblings if we are lucky. 135 range.setStart( nextTextNode, 0 ) ; 136 range.setEnd( nextTextNode, 0 ) ; 137 } 138 else 139 { 140 // no suitable next siblings under our grandparent! what to do next? 141 while ( node.parentNode 142 && node.parentNode != FCK.EditorDocument.body 143 && node.parentNode != FCK.EditorDocument.documentElement 144 && node == node.parentNode.lastChild 145 && ( ! FCKListsLib.BlockElements[node.parentNode.tagName.toLowerCase()] ) ) 146 node = node.parentNode ; 147 148 149 if ( FCKListsLib.BlockElements[ parentTag ] 150 || FCKListsLib.EmptyElements[ parentTag ] 151 || node == FCK.EditorDocument.body ) 152 { 153 // if our parent is a block node, move to the end of our parent. 154 range.setStart( node, node.childNodes.length ) ; 155 range.setEnd( node, node.childNodes.length ) ; 156 } 157 else 158 { 159 // things are a little bit more interesting if our parent is not a block node 160 // due to the weired ways how Gecko's caret acts... 161 var stopNode = node.nextSibling ; 162 163 // find out the next block/empty element at our grandparent, we'll 164 // move the caret just before it. 165 while ( stopNode ) 166 { 167 if ( stopNode.nodeType != 1 ) 168 { 169 stopNode = stopNode.nextSibling ; 170 continue ; 171 } 172 173 var stopTag = stopNode.tagName.toLowerCase() ; 174 if ( FCKListsLib.BlockElements[stopTag] || FCKListsLib.EmptyElements[stopTag] ) 175 break ; 176 stopNode = stopNode.nextSibling ; 177 } 178 179 // note that the dummy marker below is NEEDED, otherwise the caret's behavior will 180 // be broken in Gecko. 181 var marker = FCK.EditorDocument.createTextNode( '' ) ; 182 if ( stopNode ) 183 node.parentNode.insertBefore( marker, stopNode ) ; 184 else 185 node.parentNode.appendChild( marker ) ; 186 range.setStart( marker, 0 ) ; 187 range.setEnd( marker, 0 ) ; 188 } 189 } 190 191 selection.removeAllRanges() ; 192 selection.addRange( range ) ; 193 FCK.Events.FireEvent( "OnSelectionChange" ) ; 194 } 195 196 setTimeout( moveCursor, 1 ) ; 197 } 198 199 this._FillEmptyBlock = function( emptyBlockNode ) 200 { 201 if ( ! emptyBlockNode || emptyBlockNode.nodeType != 1 ) 202 return ; 203 var nodeTag = emptyBlockNode.tagName.toLowerCase() ; 204 if ( nodeTag != 'p' && nodeTag != 'div' ) 205 return ; 206 if ( emptyBlockNode.firstChild ) 207 return ; 208 FCKTools.AppendBogusBr( emptyBlockNode ) ; 209 } 210 211 this._ExecCheckEmptyBlock = function() 212 { 213 FCK._FillEmptyBlock( FCK.EditorDocument.body.firstChild ) ; 214 var sel = FCK.EditorWindow.getSelection() ; 215 if ( !sel || sel.rangeCount < 1 ) 216 return ; 217 var range = sel.getRangeAt( 0 ); 218 FCK._FillEmptyBlock( range.startContainer ) ; 219 } 220 221 this.ExecOnSelectionChangeTimer = function() 222 { 223 if ( FCK.LastOnChangeTimer ) 224 window.clearTimeout( FCK.LastOnChangeTimer ) ; 225 226 FCK.LastOnChangeTimer = window.setTimeout( FCK.ExecOnSelectionChange, 100 ) ; 227 } 228 229 this.EditorDocument.addEventListener( 'mouseup', this.ExecOnSelectionChange, false ) ; 230 231 // On Gecko, firing the "OnSelectionChange" event on every key press started to be too much 232 // slow. So, a timer has been implemented to solve performance issues when typing to quickly. 233 this.EditorDocument.addEventListener( 'keyup', this.ExecOnSelectionChangeTimer, false ) ; 234 235 this._DblClickListener = function( e ) 236 { 237 FCK.OnDoubleClick( e.target ) ; 238 e.stopPropagation() ; 239 } 240 this.EditorDocument.addEventListener( 'dblclick', this._DblClickListener, true ) ; 241 242 // Record changes for the undo system when there are key down events. 243 this.EditorDocument.addEventListener( 'keydown', this._KeyDownListener, false ) ; 244 245 // Hooks for data object drops 246 if ( FCKBrowserInfo.IsGecko ) 247 { 248 this.EditorWindow.addEventListener( 'dragdrop', this._ExecDrop, true ) ; 249 } 250 else if ( FCKBrowserInfo.IsSafari ) 251 { 252 var cancelHandler = function( evt ){ if ( ! FCK.MouseDownFlag ) evt.returnValue = false ; } 253 this.EditorDocument.addEventListener( 'dragenter', cancelHandler, true ) ; 254 this.EditorDocument.addEventListener( 'dragover', cancelHandler, true ) ; 255 this.EditorDocument.addEventListener( 'drop', this._ExecDrop, true ) ; 256 this.EditorDocument.addEventListener( 'mousedown', 257 function( ev ) 258 { 259 var element = ev.srcElement ; 260 261 if ( element.nodeName.IEquals( 'IMG', 'HR', 'INPUT', 'TEXTAREA', 'SELECT' ) ) 262 { 263 FCKSelection.SelectNode( element ) ; 264 } 265 }, true ) ; 266 267 this.EditorDocument.addEventListener( 'mouseup', 268 function( ev ) 269 { 270 if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) ) 271 ev.preventDefault() 272 }, true ) ; 273 274 this.EditorDocument.addEventListener( 'click', 275 function( ev ) 276 { 277 if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) ) 278 ev.preventDefault() 279 }, true ) ; 280 } 281 282 // Kludge for buggy Gecko caret positioning logic (Bug #393 and #1056) 283 if ( FCKBrowserInfo.IsGecko || FCKBrowserInfo.IsOpera ) 284 { 285 this.EditorDocument.addEventListener( 'keypress', this._ExecCheckCaret, false ) ; 286 this.EditorDocument.addEventListener( 'click', this._ExecCheckCaret, false ) ; 287 } 288 if ( FCKBrowserInfo.IsGecko ) 289 this.AttachToOnSelectionChange( this._ExecCheckEmptyBlock ) ; 290 291 // Reset the context menu. 292 FCK.ContextMenu._InnerContextMenu.SetMouseClickWindow( FCK.EditorWindow ) ; 293 FCK.ContextMenu._InnerContextMenu.AttachToElement( FCK.EditorDocument ) ; 294} 295 296FCK.MakeEditable = function() 297{ 298 this.EditingArea.MakeEditable() ; 299} 300 301// Disable the context menu in the editor (outside the editing area). 302function Document_OnContextMenu( e ) 303{ 304 if ( !e.target._FCKShowContextMenu ) 305 e.preventDefault() ; 306} 307document.oncontextmenu = Document_OnContextMenu ; 308 309// GetNamedCommandState overload for Gecko. 310FCK._BaseGetNamedCommandState = FCK.GetNamedCommandState ; 311FCK.GetNamedCommandState = function( commandName ) 312{ 313 switch ( commandName ) 314 { 315 case 'Unlink' : 316 return FCKSelection.HasAncestorNode('A') ? FCK_TRISTATE_OFF : FCK_TRISTATE_DISABLED ; 317 default : 318 return FCK._BaseGetNamedCommandState( commandName ) ; 319 } 320} 321 322// Named commands to be handled by this browsers specific implementation. 323FCK.RedirectNamedCommands = 324{ 325 Print : true, 326 Paste : true, 327 328 Cut : true, 329 Copy : true 330} ; 331 332// ExecuteNamedCommand overload for Gecko. 333FCK.ExecuteRedirectedNamedCommand = function( commandName, commandParameter ) 334{ 335 switch ( commandName ) 336 { 337 case 'Print' : 338 FCK.EditorWindow.print() ; 339 break ; 340 case 'Paste' : 341 try 342 { 343 // Force the paste dialog for Safari (#50). 344 if ( FCKBrowserInfo.IsSafari ) 345 throw '' ; 346 347 if ( FCK.Paste() ) 348 FCK.ExecuteNamedCommand( 'Paste', null, true ) ; 349 } 350 catch (e) { FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ; } 351 break ; 352 case 'Cut' : 353 try { FCK.ExecuteNamedCommand( 'Cut', null, true ) ; } 354 catch (e) { alert(FCKLang.PasteErrorCut) ; } 355 break ; 356 case 'Copy' : 357 try { FCK.ExecuteNamedCommand( 'Copy', null, true ) ; } 358 catch (e) { alert(FCKLang.PasteErrorCopy) ; } 359 break ; 360 default : 361 FCK.ExecuteNamedCommand( commandName, commandParameter ) ; 362 } 363} 364 365FCK._ExecPaste = function() 366{ 367 // Save a snapshot for undo before actually paste the text 368 FCKUndo.SaveUndoStep() ; 369 370 if ( FCKConfig.ForcePasteAsPlainText ) 371 { 372 FCK.PasteAsPlainText() ; 373 return false ; 374 } 375 376 /* For now, the AutoDetectPasteFromWord feature is IE only. */ 377 return true ; 378} 379 380//** 381// FCK.InsertHtml: Inserts HTML at the current cursor location. Deletes the 382// selected content if any. 383FCK.InsertHtml = function( html ) 384{ 385 html = FCKConfig.ProtectedSource.Protect( html ) ; 386 html = FCK.ProtectEvents( html ) ; 387 html = FCK.ProtectUrls( html ) ; 388 html = FCK.ProtectTags( html ) ; 389 390 // Save an undo snapshot first. 391 FCKUndo.SaveUndoStep() ; 392 393 // Insert the HTML code. 394 this.EditorDocument.execCommand( 'inserthtml', false, html ) ; 395 this.Focus() ; 396 397 // For some strange reason the SaveUndoStep() call doesn't activate the undo button at the first InsertHtml() call. 398 this.Events.FireEvent( "OnSelectionChange" ) ; 399} 400 401FCK.PasteAsPlainText = function() 402{ 403 // TODO: Implement the "Paste as Plain Text" code. 404 405 // If the function is called immediately Firefox 2 does automatically paste the contents as soon as the new dialog is created 406 // so we run it in a Timeout and the paste event can be cancelled 407 FCKTools.RunFunction( FCKDialog.OpenDialog, FCKDialog, ['FCKDialog_Paste', FCKLang.PasteAsText, 'dialog/fck_paste.html', 400, 330, 'PlainText'] ) ; 408 409/* 410 var sText = FCKTools.HTMLEncode( clipboardData.getData("Text") ) ; 411 sText = sText.replace( /\n/g, '<BR>' ) ; 412 this.InsertHtml( sText ) ; 413*/ 414} 415/* 416FCK.PasteFromWord = function() 417{ 418 // TODO: Implement the "Paste as Plain Text" code. 419 420 FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.PasteFromWord, 'dialog/fck_paste.html', 400, 330, 'Word' ) ; 421 422// FCK.CleanAndPaste( FCK.GetClipboardHTML() ) ; 423} 424*/ 425FCK.GetClipboardHTML = function() 426{ 427 return '' ; 428} 429 430FCK.CreateLink = function( url, noUndo ) 431{ 432 // Creates the array that will be returned. It contains one or more created links (see #220). 433 var aCreatedLinks = new Array() ; 434 435 FCK.ExecuteNamedCommand( 'Unlink', null, false, !!noUndo ) ; 436 437 if ( url.length > 0 ) 438 { 439 // Generate a temporary name for the link. 440 var sTempUrl = 'javascript:void(0);/*' + ( new Date().getTime() ) + '*/' ; 441 442 // Use the internal "CreateLink" command to create the link. 443 FCK.ExecuteNamedCommand( 'CreateLink', sTempUrl, false, !!noUndo ) ; 444 445 // Retrieve the just created links using XPath. 446 var oLinksInteractor = this.EditorDocument.evaluate("//a[@href='" + sTempUrl + "']", this.EditorDocument.body, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) ; 447 448 // Add all links to the returning array. 449 for ( var i = 0 ; i < oLinksInteractor.snapshotLength ; i++ ) 450 { 451 var oLink = oLinksInteractor.snapshotItem( i ) ; 452 oLink.href = url ; 453 aCreatedLinks.push( oLink ) ; 454 } 455 } 456 457 return aCreatedLinks ; 458} 459