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