2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
\r
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
\r
5 * == BEGIN LICENSE ==
\r
7 * Licensed under the terms of any of the following licenses at your
\r
10 * - GNU General Public License Version 2 or later (the "GPL")
\r
11 * http://www.gnu.org/licenses/gpl.html
\r
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
\r
14 * http://www.gnu.org/licenses/lgpl.html
\r
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
\r
17 * http://www.mozilla.org/MPL/MPL-1.1.html
\r
21 * Creation and initialization of the "FCK" object. This is the main
\r
22 * object that represents an editor instance.
\r
23 * (Gecko specific implementations)
\r
26 FCK.Description = "FCKeditor for Gecko Browsers" ;
\r
28 FCK.InitializeBehaviors = function()
\r
30 // When calling "SetData", the editing area IFRAME gets a fixed height. So we must recalculate it.
\r
31 if ( window.onresize ) // Not for Safari/Opera.
\r
34 FCKFocusManager.AddWindow( this.EditorWindow ) ;
\r
36 this.ExecOnSelectionChange = function()
\r
38 FCK.Events.FireEvent( "OnSelectionChange" ) ;
\r
41 this._ExecDrop = function( evt )
\r
43 if ( FCK.MouseDownFlag )
\r
45 FCK.MouseDownFlag = false ;
\r
49 if ( FCKConfig.ForcePasteAsPlainText )
\r
51 if ( evt.dataTransfer )
\r
53 var text = evt.dataTransfer.getData( 'Text' ) ;
\r
54 text = FCKTools.HTMLEncode( text ) ;
\r
55 text = FCKTools.ProcessLineBreaks( window, FCKConfig, text ) ;
\r
56 FCK.InsertHtml( text ) ;
\r
58 else if ( FCKConfig.ShowDropDialog )
\r
59 FCK.PasteAsPlainText() ;
\r
61 evt.preventDefault() ;
\r
62 evt.stopPropagation() ;
\r
66 this._ExecCheckCaret = function( evt )
\r
68 if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )
\r
71 if ( evt.type == 'keypress' )
\r
73 var keyCode = evt.keyCode ;
\r
74 // ignore if positioning key is not pressed.
\r
75 // left or up arrow keys need to be processed as well, since <a> links can be expanded in Gecko's editor
\r
76 // when the caret moved left or up from another block element below.
\r
77 if ( keyCode < 33 || keyCode > 40 )
\r
81 var blockEmptyStop = function( node )
\r
83 if ( node.nodeType != 1 )
\r
85 var tag = node.tagName.toLowerCase() ;
\r
86 return ( FCKListsLib.BlockElements[tag] || FCKListsLib.EmptyElements[tag] ) ;
\r
89 var moveCursor = function()
\r
91 var selection = FCKSelection.GetSelection() ;
\r
92 var range = selection.getRangeAt(0) ;
\r
93 if ( ! range || ! range.collapsed )
\r
96 var node = range.endContainer ;
\r
98 // only perform the patched behavior if we're at the end of a text node.
\r
99 if ( node.nodeType != 3 )
\r
102 if ( node.nodeValue.length != range.endOffset )
\r
105 // only perform the patched behavior if we're in an <a> tag, or the End key is pressed.
\r
106 var parentTag = node.parentNode.tagName.toLowerCase() ;
\r
107 if ( ! ( parentTag == 'a' || ( !FCKBrowserInfo.IsOpera && String(node.parentNode.contentEditable) == 'false' ) ||
\r
108 ( ! ( FCKListsLib.BlockElements[parentTag] || FCKListsLib.NonEmptyBlockElements[parentTag] )
\r
109 && keyCode == 35 ) ) )
\r
112 // our caret has moved to just after the last character of a text node under an unknown tag, how to proceed?
\r
113 // first, see if there are other text nodes by DFS walking from this text node.
\r
114 // - if the DFS has scanned all nodes under my parent, then go the next step.
\r
115 // - if there is a text node after me but still under my parent, then do nothing and return.
\r
116 var nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode, blockEmptyStop ) ;
\r
117 if ( nextTextNode )
\r
120 // we're pretty sure we need to move the caret forcefully from here.
\r
121 range = FCK.EditorDocument.createRange() ;
\r
123 nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode.parentNode, blockEmptyStop ) ;
\r
124 if ( nextTextNode )
\r
126 // Opera thinks the dummy empty text node we append beyond the end of <a> nodes occupies a caret
\r
127 // position. So if the user presses the left key and we reset the caret position here, the user
\r
128 // wouldn't be able to go back.
\r
129 if ( FCKBrowserInfo.IsOpera && keyCode == 37 )
\r
132 // now we want to get out of our current parent node, adopt the next parent, and move the caret to
\r
133 // the appropriate text node under our new parent.
\r
134 // our new parent might be our current parent's siblings if we are lucky.
\r
135 range.setStart( nextTextNode, 0 ) ;
\r
136 range.setEnd( nextTextNode, 0 ) ;
\r
140 // no suitable next siblings under our grandparent! what to do next?
\r
141 while ( node.parentNode
\r
142 && node.parentNode != FCK.EditorDocument.body
\r
143 && node.parentNode != FCK.EditorDocument.documentElement
\r
144 && node == node.parentNode.lastChild
\r
145 && ( ! FCKListsLib.BlockElements[node.parentNode.tagName.toLowerCase()]
\r
146 && ! FCKListsLib.NonEmptyBlockElements[node.parentNode.tagName.toLowerCase()] ) )
\r
147 node = node.parentNode ;
\r
150 if ( FCKListsLib.BlockElements[ parentTag ]
\r
151 || FCKListsLib.EmptyElements[ parentTag ]
\r
152 || node == FCK.EditorDocument.body )
\r
154 // if our parent is a block node, move to the end of our parent.
\r
155 range.setStart( node, node.childNodes.length ) ;
\r
156 range.setEnd( node, node.childNodes.length ) ;
\r
160 // things are a little bit more interesting if our parent is not a block node
\r
161 // due to the weired ways how Gecko's caret acts...
\r
162 var stopNode = node.nextSibling ;
\r
164 // find out the next block/empty element at our grandparent, we'll
\r
165 // move the caret just before it.
\r
168 if ( stopNode.nodeType != 1 )
\r
170 stopNode = stopNode.nextSibling ;
\r
174 var stopTag = stopNode.tagName.toLowerCase() ;
\r
175 if ( FCKListsLib.BlockElements[stopTag] || FCKListsLib.EmptyElements[stopTag]
\r
176 || FCKListsLib.NonEmptyBlockElements[stopTag] )
\r
178 stopNode = stopNode.nextSibling ;
\r
181 // note that the dummy marker below is NEEDED, otherwise the caret's behavior will
\r
182 // be broken in Gecko.
\r
183 var marker = FCK.EditorDocument.createTextNode( '' ) ;
\r
185 node.parentNode.insertBefore( marker, stopNode ) ;
\r
187 node.parentNode.appendChild( marker ) ;
\r
188 range.setStart( marker, 0 ) ;
\r
189 range.setEnd( marker, 0 ) ;
\r
193 selection.removeAllRanges() ;
\r
194 selection.addRange( range ) ;
\r
195 FCK.Events.FireEvent( "OnSelectionChange" ) ;
\r
198 setTimeout( moveCursor, 1 ) ;
\r
201 this.ExecOnSelectionChangeTimer = function()
\r
203 if ( FCK.LastOnChangeTimer )
\r
204 window.clearTimeout( FCK.LastOnChangeTimer ) ;
\r
206 FCK.LastOnChangeTimer = window.setTimeout( FCK.ExecOnSelectionChange, 100 ) ;
\r
209 this.EditorDocument.addEventListener( 'mouseup', this.ExecOnSelectionChange, false ) ;
\r
211 // On Gecko, firing the "OnSelectionChange" event on every key press started to be too much
\r
212 // slow. So, a timer has been implemented to solve performance issues when typing to quickly.
\r
213 this.EditorDocument.addEventListener( 'keyup', this.ExecOnSelectionChangeTimer, false ) ;
\r
215 this._DblClickListener = function( e )
\r
217 FCK.OnDoubleClick( e.target ) ;
\r
218 e.stopPropagation() ;
\r
220 this.EditorDocument.addEventListener( 'dblclick', this._DblClickListener, true ) ;
\r
222 // Record changes for the undo system when there are key down events.
\r
223 this.EditorDocument.addEventListener( 'keydown', this._KeyDownListener, false ) ;
\r
225 // Hooks for data object drops
\r
226 if ( FCKBrowserInfo.IsGecko )
\r
228 this.EditorWindow.addEventListener( 'dragdrop', this._ExecDrop, true ) ;
\r
230 else if ( FCKBrowserInfo.IsSafari )
\r
232 this.EditorDocument.addEventListener( 'dragover', function ( evt )
\r
233 { if ( !FCK.MouseDownFlag && FCK.Config.ForcePasteAsPlainText ) evt.returnValue = false ; }, true ) ;
\r
234 this.EditorDocument.addEventListener( 'drop', this._ExecDrop, true ) ;
\r
235 this.EditorDocument.addEventListener( 'mousedown',
\r
238 var element = ev.srcElement ;
\r
240 if ( element.nodeName.IEquals( 'IMG', 'HR', 'INPUT', 'TEXTAREA', 'SELECT' ) )
\r
242 FCKSelection.SelectNode( element ) ;
\r
246 this.EditorDocument.addEventListener( 'mouseup',
\r
249 if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )
\r
250 ev.preventDefault()
\r
253 this.EditorDocument.addEventListener( 'click',
\r
256 if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )
\r
257 ev.preventDefault()
\r
261 // Kludge for buggy Gecko caret positioning logic (Bug #393 and #1056)
\r
262 if ( FCKBrowserInfo.IsGecko || FCKBrowserInfo.IsOpera )
\r
264 this.EditorDocument.addEventListener( 'keypress', this._ExecCheckCaret, false ) ;
\r
265 this.EditorDocument.addEventListener( 'click', this._ExecCheckCaret, false ) ;
\r
268 // Reset the context menu.
\r
269 FCK.ContextMenu._InnerContextMenu.SetMouseClickWindow( FCK.EditorWindow ) ;
\r
270 FCK.ContextMenu._InnerContextMenu.AttachToElement( FCK.EditorDocument ) ;
\r
273 FCK.MakeEditable = function()
\r
275 this.EditingArea.MakeEditable() ;
\r
278 // Disable the context menu in the editor (outside the editing area).
\r
279 function Document_OnContextMenu( e )
\r
281 if ( !e.target._FCKShowContextMenu )
\r
282 e.preventDefault() ;
\r
284 document.oncontextmenu = Document_OnContextMenu ;
\r
286 // GetNamedCommandState overload for Gecko.
\r
287 FCK._BaseGetNamedCommandState = FCK.GetNamedCommandState ;
\r
288 FCK.GetNamedCommandState = function( commandName )
\r
290 switch ( commandName )
\r
293 return FCKSelection.HasAncestorNode('A') ? FCK_TRISTATE_OFF : FCK_TRISTATE_DISABLED ;
\r
295 return FCK._BaseGetNamedCommandState( commandName ) ;
\r
299 // Named commands to be handled by this browsers specific implementation.
\r
300 FCK.RedirectNamedCommands =
\r
306 // ExecuteNamedCommand overload for Gecko.
\r
307 FCK.ExecuteRedirectedNamedCommand = function( commandName, commandParameter )
\r
309 switch ( commandName )
\r
312 FCK.EditorWindow.print() ;
\r
317 // Force the paste dialog for Safari (#50).
\r
318 if ( FCKBrowserInfo.IsSafari )
\r
322 FCK.ExecuteNamedCommand( 'Paste', null, true ) ;
\r
325 if ( FCKConfig.ForcePasteAsPlainText )
\r
326 FCK.PasteAsPlainText() ;
\r
328 FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ;
\r
332 FCK.ExecuteNamedCommand( commandName, commandParameter ) ;
\r
336 FCK._ExecPaste = function()
\r
338 // Save a snapshot for undo before actually paste the text
\r
339 FCKUndo.SaveUndoStep() ;
\r
341 if ( FCKConfig.ForcePasteAsPlainText )
\r
343 FCK.PasteAsPlainText() ;
\r
347 /* For now, the AutoDetectPasteFromWord feature is IE only. */
\r
352 // FCK.InsertHtml: Inserts HTML at the current cursor location. Deletes the
\r
353 // selected content if any.
\r
354 FCK.InsertHtml = function( html )
\r
356 var doc = FCK.EditorDocument,
\r
359 html = FCKConfig.ProtectedSource.Protect( html ) ;
\r
360 html = FCK.ProtectEvents( html ) ;
\r
361 html = FCK.ProtectUrls( html ) ;
\r
362 html = FCK.ProtectTags( html ) ;
\r
364 // Save an undo snapshot first.
\r
365 FCKUndo.SaveUndoStep() ;
\r
367 if ( FCKBrowserInfo.IsGecko )
\r
369 html = html.replace( / $/, '$&<span _fcktemp="1"/>' ) ;
\r
371 var docFrag = new FCKDocumentFragment( this.EditorDocument ) ;
\r
372 docFrag.AppendHtml( html ) ;
\r
374 var lastNode = docFrag.RootNode.lastChild ;
\r
376 range = new FCKDomRange( this.EditorWindow ) ;
\r
377 range.MoveToSelection() ;
\r
378 range.DeleteContents() ;
\r
379 range.InsertNode( docFrag.RootNode ) ;
\r
381 range.MoveToPosition( lastNode, 4 ) ;
\r
384 doc.execCommand( 'inserthtml', false, html ) ;
\r
388 // Save the caret position before calling document processor.
\r
391 range = new FCKDomRange( this.EditorWindow ) ;
\r
392 range.MoveToSelection() ;
\r
394 var bookmark = range.CreateBookmark() ;
\r
396 FCKDocumentProcessor.Process( doc ) ;
\r
398 // Restore caret position, ignore any errors in case the document
\r
399 // processor removed the bookmark <span>s for some reason.
\r
402 range.MoveToBookmark( bookmark ) ;
\r
407 // For some strange reason the SaveUndoStep() call doesn't activate the undo button at the first InsertHtml() call.
\r
408 this.Events.FireEvent( "OnSelectionChange" ) ;
\r
411 FCK.PasteAsPlainText = function()
\r
413 // TODO: Implement the "Paste as Plain Text" code.
\r
415 // If the function is called immediately Firefox 2 does automatically paste the contents as soon as the new dialog is created
\r
416 // so we run it in a Timeout and the paste event can be cancelled
\r
417 FCKTools.RunFunction( FCKDialog.OpenDialog, FCKDialog, ['FCKDialog_Paste', FCKLang.PasteAsText, 'dialog/fck_paste.html', 400, 330, 'PlainText'] ) ;
\r
420 var sText = FCKTools.HTMLEncode( clipboardData.getData("Text") ) ;
\r
421 sText = sText.replace( /\n/g, '<BR>' ) ;
\r
422 this.InsertHtml( sText ) ;
\r
426 FCK.PasteFromWord = function()
\r
428 // TODO: Implement the "Paste as Plain Text" code.
\r
430 FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.PasteFromWord, 'dialog/fck_paste.html', 400, 330, 'Word' ) ;
\r
432 // FCK.CleanAndPaste( FCK.GetClipboardHTML() ) ;
\r
435 FCK.GetClipboardHTML = function()
\r
440 FCK.CreateLink = function( url, noUndo )
\r
442 // Creates the array that will be returned. It contains one or more created links (see #220).
\r
443 var aCreatedLinks = new Array() ;
\r
445 // Only for Safari, a collapsed selection may create a link. All other
\r
446 // browser will have no links created. So, we check it here and return
\r
447 // immediatelly, having the same cross browser behavior.
\r
448 if ( FCKSelection.GetSelection().isCollapsed )
\r
449 return aCreatedLinks ;
\r
451 FCK.ExecuteNamedCommand( 'Unlink', null, false, !!noUndo ) ;
\r
453 if ( url.length > 0 )
\r
455 // Generate a temporary name for the link.
\r
456 var sTempUrl = 'javascript:void(0);/*' + ( new Date().getTime() ) + '*/' ;
\r
458 // Use the internal "CreateLink" command to create the link.
\r
459 FCK.ExecuteNamedCommand( 'CreateLink', sTempUrl, false, !!noUndo ) ;
\r
461 // Retrieve the just created links using XPath.
\r
462 var oLinksInteractor = this.EditorDocument.evaluate("//a[@href='" + sTempUrl + "']", this.EditorDocument.body, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) ;
\r
464 // Add all links to the returning array.
\r
465 for ( var i = 0 ; i < oLinksInteractor.snapshotLength ; i++ )
\r
467 var oLink = oLinksInteractor.snapshotItem( i ) ;
\r
470 aCreatedLinks.push( oLink ) ;
\r
474 return aCreatedLinks ;
\r
477 FCK._FillEmptyBlock = function( emptyBlockNode )
\r
479 if ( ! emptyBlockNode || emptyBlockNode.nodeType != 1 )
\r
481 var nodeTag = emptyBlockNode.tagName.toLowerCase() ;
\r
482 if ( nodeTag != 'p' && nodeTag != 'div' )
\r
484 if ( emptyBlockNode.firstChild )
\r
486 FCKTools.AppendBogusBr( emptyBlockNode ) ;
\r
489 FCK._ExecCheckEmptyBlock = function()
\r
491 FCK._FillEmptyBlock( FCK.EditorDocument.body.firstChild ) ;
\r
492 var sel = FCKSelection.GetSelection() ;
\r
493 if ( !sel || sel.rangeCount < 1 )
\r
495 var range = sel.getRangeAt( 0 );
\r
496 FCK._FillEmptyBlock( range.startContainer ) ;
\r