--- /dev/null
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2008 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ * - GNU General Public License Version 2 or later (the "GPL")\r
+ * http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ * http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ * - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ * http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Creation and initialization of the "FCK" object. This is the main\r
+ * object that represents an editor instance.\r
+ * (Gecko specific implementations)\r
+ */\r
+\r
+FCK.Description = "FCKeditor for Gecko Browsers" ;\r
+\r
+FCK.InitializeBehaviors = function()\r
+{\r
+ // When calling "SetData", the editing area IFRAME gets a fixed height. So we must recalculate it.\r
+ if ( window.onresize ) // Not for Safari/Opera.\r
+ window.onresize() ;\r
+\r
+ FCKFocusManager.AddWindow( this.EditorWindow ) ;\r
+\r
+ this.ExecOnSelectionChange = function()\r
+ {\r
+ FCK.Events.FireEvent( "OnSelectionChange" ) ;\r
+ }\r
+\r
+ this._ExecDrop = function( evt )\r
+ {\r
+ if ( FCK.MouseDownFlag )\r
+ {\r
+ FCK.MouseDownFlag = false ;\r
+ return ;\r
+ }\r
+\r
+ if ( FCKConfig.ForcePasteAsPlainText )\r
+ {\r
+ if ( evt.dataTransfer )\r
+ {\r
+ var text = evt.dataTransfer.getData( 'Text' ) ;\r
+ text = FCKTools.HTMLEncode( text ) ;\r
+ text = FCKTools.ProcessLineBreaks( window, FCKConfig, text ) ;\r
+ FCK.InsertHtml( text ) ;\r
+ }\r
+ else if ( FCKConfig.ShowDropDialog )\r
+ FCK.PasteAsPlainText() ;\r
+\r
+ evt.preventDefault() ;\r
+ evt.stopPropagation() ;\r
+ }\r
+ }\r
+\r
+ this._ExecCheckCaret = function( evt )\r
+ {\r
+ if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )\r
+ return ;\r
+\r
+ if ( evt.type == 'keypress' )\r
+ {\r
+ var keyCode = evt.keyCode ;\r
+ // ignore if positioning key is not pressed.\r
+ // left or up arrow keys need to be processed as well, since <a> links can be expanded in Gecko's editor\r
+ // when the caret moved left or up from another block element below.\r
+ if ( keyCode < 33 || keyCode > 40 )\r
+ return ;\r
+ }\r
+\r
+ var blockEmptyStop = function( node )\r
+ {\r
+ if ( node.nodeType != 1 )\r
+ return false ;\r
+ var tag = node.tagName.toLowerCase() ;\r
+ return ( FCKListsLib.BlockElements[tag] || FCKListsLib.EmptyElements[tag] ) ;\r
+ }\r
+\r
+ var moveCursor = function()\r
+ {\r
+ var selection = FCKSelection.GetSelection() ;\r
+ var range = selection.getRangeAt(0) ;\r
+ if ( ! range || ! range.collapsed )\r
+ return ;\r
+\r
+ var node = range.endContainer ;\r
+\r
+ // only perform the patched behavior if we're at the end of a text node.\r
+ if ( node.nodeType != 3 )\r
+ return ;\r
+\r
+ if ( node.nodeValue.length != range.endOffset )\r
+ return ;\r
+\r
+ // only perform the patched behavior if we're in an <a> tag, or the End key is pressed.\r
+ var parentTag = node.parentNode.tagName.toLowerCase() ;\r
+ if ( ! ( parentTag == 'a' || ( !FCKBrowserInfo.IsOpera && String(node.parentNode.contentEditable) == 'false' ) ||\r
+ ( ! ( FCKListsLib.BlockElements[parentTag] || FCKListsLib.NonEmptyBlockElements[parentTag] )\r
+ && keyCode == 35 ) ) )\r
+ return ;\r
+\r
+ // our caret has moved to just after the last character of a text node under an unknown tag, how to proceed?\r
+ // first, see if there are other text nodes by DFS walking from this text node.\r
+ // - if the DFS has scanned all nodes under my parent, then go the next step.\r
+ // - if there is a text node after me but still under my parent, then do nothing and return.\r
+ var nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode, blockEmptyStop ) ;\r
+ if ( nextTextNode )\r
+ return ;\r
+\r
+ // we're pretty sure we need to move the caret forcefully from here.\r
+ range = FCK.EditorDocument.createRange() ;\r
+\r
+ nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode.parentNode, blockEmptyStop ) ;\r
+ if ( nextTextNode )\r
+ {\r
+ // Opera thinks the dummy empty text node we append beyond the end of <a> nodes occupies a caret\r
+ // position. So if the user presses the left key and we reset the caret position here, the user\r
+ // wouldn't be able to go back.\r
+ if ( FCKBrowserInfo.IsOpera && keyCode == 37 )\r
+ return ;\r
+\r
+ // now we want to get out of our current parent node, adopt the next parent, and move the caret to\r
+ // the appropriate text node under our new parent.\r
+ // our new parent might be our current parent's siblings if we are lucky.\r
+ range.setStart( nextTextNode, 0 ) ;\r
+ range.setEnd( nextTextNode, 0 ) ;\r
+ }\r
+ else\r
+ {\r
+ // no suitable next siblings under our grandparent! what to do next?\r
+ while ( node.parentNode\r
+ && node.parentNode != FCK.EditorDocument.body\r
+ && node.parentNode != FCK.EditorDocument.documentElement\r
+ && node == node.parentNode.lastChild\r
+ && ( ! FCKListsLib.BlockElements[node.parentNode.tagName.toLowerCase()]\r
+ && ! FCKListsLib.NonEmptyBlockElements[node.parentNode.tagName.toLowerCase()] ) )\r
+ node = node.parentNode ;\r
+\r
+\r
+ if ( FCKListsLib.BlockElements[ parentTag ]\r
+ || FCKListsLib.EmptyElements[ parentTag ]\r
+ || node == FCK.EditorDocument.body )\r
+ {\r
+ // if our parent is a block node, move to the end of our parent.\r
+ range.setStart( node, node.childNodes.length ) ;\r
+ range.setEnd( node, node.childNodes.length ) ;\r
+ }\r
+ else\r
+ {\r
+ // things are a little bit more interesting if our parent is not a block node\r
+ // due to the weired ways how Gecko's caret acts...\r
+ var stopNode = node.nextSibling ;\r
+\r
+ // find out the next block/empty element at our grandparent, we'll\r
+ // move the caret just before it.\r
+ while ( stopNode )\r
+ {\r
+ if ( stopNode.nodeType != 1 )\r
+ {\r
+ stopNode = stopNode.nextSibling ;\r
+ continue ;\r
+ }\r
+\r
+ var stopTag = stopNode.tagName.toLowerCase() ;\r
+ if ( FCKListsLib.BlockElements[stopTag] || FCKListsLib.EmptyElements[stopTag]\r
+ || FCKListsLib.NonEmptyBlockElements[stopTag] )\r
+ break ;\r
+ stopNode = stopNode.nextSibling ;\r
+ }\r
+\r
+ // note that the dummy marker below is NEEDED, otherwise the caret's behavior will\r
+ // be broken in Gecko.\r
+ var marker = FCK.EditorDocument.createTextNode( '' ) ;\r
+ if ( stopNode )\r
+ node.parentNode.insertBefore( marker, stopNode ) ;\r
+ else\r
+ node.parentNode.appendChild( marker ) ;\r
+ range.setStart( marker, 0 ) ;\r
+ range.setEnd( marker, 0 ) ;\r
+ }\r
+ }\r
+\r
+ selection.removeAllRanges() ;\r
+ selection.addRange( range ) ;\r
+ FCK.Events.FireEvent( "OnSelectionChange" ) ;\r
+ }\r
+\r
+ setTimeout( moveCursor, 1 ) ;\r
+ }\r
+\r
+ this.ExecOnSelectionChangeTimer = function()\r
+ {\r
+ if ( FCK.LastOnChangeTimer )\r
+ window.clearTimeout( FCK.LastOnChangeTimer ) ;\r
+\r
+ FCK.LastOnChangeTimer = window.setTimeout( FCK.ExecOnSelectionChange, 100 ) ;\r
+ }\r
+\r
+ this.EditorDocument.addEventListener( 'mouseup', this.ExecOnSelectionChange, false ) ;\r
+\r
+ // On Gecko, firing the "OnSelectionChange" event on every key press started to be too much\r
+ // slow. So, a timer has been implemented to solve performance issues when typing to quickly.\r
+ this.EditorDocument.addEventListener( 'keyup', this.ExecOnSelectionChangeTimer, false ) ;\r
+\r
+ this._DblClickListener = function( e )\r
+ {\r
+ FCK.OnDoubleClick( e.target ) ;\r
+ e.stopPropagation() ;\r
+ }\r
+ this.EditorDocument.addEventListener( 'dblclick', this._DblClickListener, true ) ;\r
+\r
+ // Record changes for the undo system when there are key down events.\r
+ this.EditorDocument.addEventListener( 'keydown', this._KeyDownListener, false ) ;\r
+\r
+ // Hooks for data object drops\r
+ if ( FCKBrowserInfo.IsGecko )\r
+ {\r
+ this.EditorWindow.addEventListener( 'dragdrop', this._ExecDrop, true ) ;\r
+ }\r
+ else if ( FCKBrowserInfo.IsSafari )\r
+ {\r
+ var cancelHandler = function( evt ){ if ( ! FCK.MouseDownFlag ) evt.returnValue = false ; }\r
+ this.EditorDocument.addEventListener( 'dragenter', cancelHandler, true ) ;\r
+ this.EditorDocument.addEventListener( 'dragover', cancelHandler, true ) ;\r
+ this.EditorDocument.addEventListener( 'drop', this._ExecDrop, true ) ;\r
+ this.EditorDocument.addEventListener( 'mousedown',\r
+ function( ev )\r
+ {\r
+ var element = ev.srcElement ;\r
+\r
+ if ( element.nodeName.IEquals( 'IMG', 'HR', 'INPUT', 'TEXTAREA', 'SELECT' ) )\r
+ {\r
+ FCKSelection.SelectNode( element ) ;\r
+ }\r
+ }, true ) ;\r
+\r
+ this.EditorDocument.addEventListener( 'mouseup',\r
+ function( ev )\r
+ {\r
+ if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )\r
+ ev.preventDefault()\r
+ }, true ) ;\r
+\r
+ this.EditorDocument.addEventListener( 'click',\r
+ function( ev )\r
+ {\r
+ if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )\r
+ ev.preventDefault()\r
+ }, true ) ;\r
+ }\r
+\r
+ // Kludge for buggy Gecko caret positioning logic (Bug #393 and #1056)\r
+ if ( FCKBrowserInfo.IsGecko || FCKBrowserInfo.IsOpera )\r
+ {\r
+ this.EditorDocument.addEventListener( 'keypress', this._ExecCheckCaret, false ) ;\r
+ this.EditorDocument.addEventListener( 'click', this._ExecCheckCaret, false ) ;\r
+ }\r
+\r
+ // Reset the context menu.\r
+ FCK.ContextMenu._InnerContextMenu.SetMouseClickWindow( FCK.EditorWindow ) ;\r
+ FCK.ContextMenu._InnerContextMenu.AttachToElement( FCK.EditorDocument ) ;\r
+}\r
+\r
+FCK.MakeEditable = function()\r
+{\r
+ this.EditingArea.MakeEditable() ;\r
+}\r
+\r
+// Disable the context menu in the editor (outside the editing area).\r
+function Document_OnContextMenu( e )\r
+{\r
+ if ( !e.target._FCKShowContextMenu )\r
+ e.preventDefault() ;\r
+}\r
+document.oncontextmenu = Document_OnContextMenu ;\r
+\r
+// GetNamedCommandState overload for Gecko.\r
+FCK._BaseGetNamedCommandState = FCK.GetNamedCommandState ;\r
+FCK.GetNamedCommandState = function( commandName )\r
+{\r
+ switch ( commandName )\r
+ {\r
+ case 'Unlink' :\r
+ return FCKSelection.HasAncestorNode('A') ? FCK_TRISTATE_OFF : FCK_TRISTATE_DISABLED ;\r
+ default :\r
+ return FCK._BaseGetNamedCommandState( commandName ) ;\r
+ }\r
+}\r
+\r
+// Named commands to be handled by this browsers specific implementation.\r
+FCK.RedirectNamedCommands =\r
+{\r
+ Print : true,\r
+ Paste : true\r
+} ;\r
+\r
+// ExecuteNamedCommand overload for Gecko.\r
+FCK.ExecuteRedirectedNamedCommand = function( commandName, commandParameter )\r
+{\r
+ switch ( commandName )\r
+ {\r
+ case 'Print' :\r
+ FCK.EditorWindow.print() ;\r
+ break ;\r
+ case 'Paste' :\r
+ try\r
+ {\r
+ // Force the paste dialog for Safari (#50).\r
+ if ( FCKBrowserInfo.IsSafari )\r
+ throw '' ;\r
+\r
+ if ( FCK.Paste() )\r
+ FCK.ExecuteNamedCommand( 'Paste', null, true ) ;\r
+ }\r
+ catch (e) { FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ; }\r
+ break ;\r
+ default :\r
+ FCK.ExecuteNamedCommand( commandName, commandParameter ) ;\r
+ }\r
+}\r
+\r
+FCK._ExecPaste = function()\r
+{\r
+ // Save a snapshot for undo before actually paste the text\r
+ FCKUndo.SaveUndoStep() ;\r
+\r
+ if ( FCKConfig.ForcePasteAsPlainText )\r
+ {\r
+ FCK.PasteAsPlainText() ;\r
+ return false ;\r
+ }\r
+\r
+ /* For now, the AutoDetectPasteFromWord feature is IE only. */\r
+ return true ;\r
+}\r
+\r
+//**\r
+// FCK.InsertHtml: Inserts HTML at the current cursor location. Deletes the\r
+// selected content if any.\r
+FCK.InsertHtml = function( html )\r
+{\r
+ var doc = FCK.EditorDocument,\r
+ range;\r
+\r
+ html = FCKConfig.ProtectedSource.Protect( html ) ;\r
+ html = FCK.ProtectEvents( html ) ;\r
+ html = FCK.ProtectUrls( html ) ;\r
+ html = FCK.ProtectTags( html ) ;\r
+\r
+ // Save an undo snapshot first.\r
+ FCKUndo.SaveUndoStep() ;\r
+\r
+ if ( FCKBrowserInfo.IsGecko )\r
+ {\r
+ html = html.replace( / $/, '$&<span _fcktemp="1"/>' ) ;\r
+\r
+ var docFrag = new FCKDocumentFragment( this.EditorDocument ) ;\r
+ docFrag.AppendHtml( html ) ;\r
+\r
+ var lastNode = docFrag.RootNode.lastChild ;\r
+\r
+ range = new FCKDomRange( this.EditorWindow ) ;\r
+ range.MoveToSelection() ;\r
+ range.DeleteContents() ;\r
+ range.InsertNode( docFrag.RootNode ) ;\r
+\r
+ range.MoveToPosition( lastNode, 4 ) ;\r
+ }\r
+ else\r
+ doc.execCommand( 'inserthtml', false, html ) ;\r
+\r
+ this.Focus() ;\r
+\r
+ // Save the caret position before calling document processor.\r
+ if ( !range )\r
+ {\r
+ range = new FCKDomRange( this.EditorWindow ) ;\r
+ range.MoveToSelection() ;\r
+ }\r
+ var bookmark = range.CreateBookmark() ;\r
+\r
+ FCKDocumentProcessor.Process( doc ) ;\r
+\r
+ // Restore caret position, ignore any errors in case the document\r
+ // processor removed the bookmark <span>s for some reason.\r
+ try\r
+ {\r
+ range.MoveToBookmark( bookmark ) ;\r
+ range.Select() ;\r
+ }\r
+ catch ( e ) {}\r
+\r
+ // For some strange reason the SaveUndoStep() call doesn't activate the undo button at the first InsertHtml() call.\r
+ this.Events.FireEvent( "OnSelectionChange" ) ;\r
+}\r
+\r
+FCK.PasteAsPlainText = function()\r
+{\r
+ // TODO: Implement the "Paste as Plain Text" code.\r
+\r
+ // If the function is called immediately Firefox 2 does automatically paste the contents as soon as the new dialog is created\r
+ // so we run it in a Timeout and the paste event can be cancelled\r
+ FCKTools.RunFunction( FCKDialog.OpenDialog, FCKDialog, ['FCKDialog_Paste', FCKLang.PasteAsText, 'dialog/fck_paste.html', 400, 330, 'PlainText'] ) ;\r
+\r
+/*\r
+ var sText = FCKTools.HTMLEncode( clipboardData.getData("Text") ) ;\r
+ sText = sText.replace( /\n/g, '<BR>' ) ;\r
+ this.InsertHtml( sText ) ;\r
+*/\r
+}\r
+/*\r
+FCK.PasteFromWord = function()\r
+{\r
+ // TODO: Implement the "Paste as Plain Text" code.\r
+\r
+ FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.PasteFromWord, 'dialog/fck_paste.html', 400, 330, 'Word' ) ;\r
+\r
+// FCK.CleanAndPaste( FCK.GetClipboardHTML() ) ;\r
+}\r
+*/\r
+FCK.GetClipboardHTML = function()\r
+{\r
+ return '' ;\r
+}\r
+\r
+FCK.CreateLink = function( url, noUndo )\r
+{\r
+ // Creates the array that will be returned. It contains one or more created links (see #220).\r
+ var aCreatedLinks = new Array() ;\r
+\r
+ // Only for Safari, a collapsed selection may create a link. All other\r
+ // browser will have no links created. So, we check it here and return\r
+ // immediatelly, having the same cross browser behavior.\r
+ if ( FCKSelection.GetSelection().isCollapsed )\r
+ return aCreatedLinks ;\r
+\r
+ FCK.ExecuteNamedCommand( 'Unlink', null, false, !!noUndo ) ;\r
+\r
+ if ( url.length > 0 )\r
+ {\r
+ // Generate a temporary name for the link.\r
+ var sTempUrl = 'javascript:void(0);/*' + ( new Date().getTime() ) + '*/' ;\r
+\r
+ // Use the internal "CreateLink" command to create the link.\r
+ FCK.ExecuteNamedCommand( 'CreateLink', sTempUrl, false, !!noUndo ) ;\r
+\r
+ // Retrieve the just created links using XPath.\r
+ var oLinksInteractor = this.EditorDocument.evaluate("//a[@href='" + sTempUrl + "']", this.EditorDocument.body, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) ;\r
+\r
+ // Add all links to the returning array.\r
+ for ( var i = 0 ; i < oLinksInteractor.snapshotLength ; i++ )\r
+ {\r
+ var oLink = oLinksInteractor.snapshotItem( i ) ;\r
+ oLink.href = url ;\r
+\r
+ aCreatedLinks.push( oLink ) ;\r
+ }\r
+ }\r
+\r
+ return aCreatedLinks ;\r
+}\r
+\r
+FCK._FillEmptyBlock = function( emptyBlockNode )\r
+{\r
+ if ( ! emptyBlockNode || emptyBlockNode.nodeType != 1 )\r
+ return ;\r
+ var nodeTag = emptyBlockNode.tagName.toLowerCase() ;\r
+ if ( nodeTag != 'p' && nodeTag != 'div' )\r
+ return ;\r
+ if ( emptyBlockNode.firstChild )\r
+ return ;\r
+ FCKTools.AppendBogusBr( emptyBlockNode ) ;\r
+}\r
+\r
+FCK._ExecCheckEmptyBlock = function()\r
+{\r
+ FCK._FillEmptyBlock( FCK.EditorDocument.body.firstChild ) ;\r
+ var sel = FCKSelection.GetSelection() ;\r
+ if ( !sel || sel.rangeCount < 1 )\r
+ return ;\r
+ var range = sel.getRangeAt( 0 );\r
+ FCK._FillEmptyBlock( range.startContainer ) ;\r
+}\r