fckeditingarea.js
368 lines
| 11.5 KiB
| application/javascript
|
JavascriptLexer
|
r0 | /* | ||
* FCKeditor - The text editor for Internet - http://www.fckeditor.net | ||||
* Copyright (C) 2003-2008 Frederico Caldeira Knabben | ||||
* | ||||
* == BEGIN LICENSE == | ||||
* | ||||
* Licensed under the terms of any of the following licenses at your | ||||
* choice: | ||||
* | ||||
* - GNU General Public License Version 2 or later (the "GPL") | ||||
* http://www.gnu.org/licenses/gpl.html | ||||
* | ||||
* - GNU Lesser General Public License Version 2.1 or later (the "LGPL") | ||||
* http://www.gnu.org/licenses/lgpl.html | ||||
* | ||||
* - Mozilla Public License Version 1.1 or later (the "MPL") | ||||
* http://www.mozilla.org/MPL/MPL-1.1.html | ||||
* | ||||
* == END LICENSE == | ||||
* | ||||
* FCKEditingArea Class: renders an editable area. | ||||
*/ | ||||
/** | ||||
* @constructor | ||||
* @param {String} targetElement The element that will hold the editing area. Any child element present in the target will be deleted. | ||||
*/ | ||||
var FCKEditingArea = function( targetElement ) | ||||
{ | ||||
this.TargetElement = targetElement ; | ||||
this.Mode = FCK_EDITMODE_WYSIWYG ; | ||||
if ( FCK.IECleanup ) | ||||
FCK.IECleanup.AddItem( this, FCKEditingArea_Cleanup ) ; | ||||
} | ||||
/** | ||||
* @param {String} html The complete HTML for the page, including DOCTYPE and the <html> tag. | ||||
*/ | ||||
FCKEditingArea.prototype.Start = function( html, secondCall ) | ||||
{ | ||||
var eTargetElement = this.TargetElement ; | ||||
var oTargetDocument = FCKTools.GetElementDocument( eTargetElement ) ; | ||||
// Remove all child nodes from the target. | ||||
while( eTargetElement.firstChild ) | ||||
eTargetElement.removeChild( eTargetElement.firstChild ) ; | ||||
if ( this.Mode == FCK_EDITMODE_WYSIWYG ) | ||||
{ | ||||
// For FF, document.domain must be set only when different, otherwhise | ||||
// we'll strangely have "Permission denied" issues. | ||||
if ( FCK_IS_CUSTOM_DOMAIN ) | ||||
html = '<script>document.domain="' + FCK_RUNTIME_DOMAIN + '";</script>' + html ; | ||||
// IE has a bug with the <base> tag... it must have a </base> closer, | ||||
// otherwise the all successive tags will be set as children nodes of the <base>. | ||||
if ( FCKBrowserInfo.IsIE ) | ||||
html = html.replace( /(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi, '$1></base>' ) ; | ||||
else if ( !secondCall ) | ||||
{ | ||||
// Gecko moves some tags out of the body to the head, so we must use | ||||
// innerHTML to set the body contents (SF BUG 1526154). | ||||
// Extract the BODY contents from the html. | ||||
var oMatchBefore = html.match( FCKRegexLib.BeforeBody ) ; | ||||
var oMatchAfter = html.match( FCKRegexLib.AfterBody ) ; | ||||
if ( oMatchBefore && oMatchAfter ) | ||||
{ | ||||
var sBody = html.substr( oMatchBefore[1].length, | ||||
html.length - oMatchBefore[1].length - oMatchAfter[1].length ) ; // This is the BODY tag contents. | ||||
html = | ||||
oMatchBefore[1] + // This is the HTML until the <body...> tag, inclusive. | ||||
' ' + | ||||
oMatchAfter[1] ; // This is the HTML from the </body> tag, inclusive. | ||||
// If nothing in the body, place a BOGUS tag so the cursor will appear. | ||||
if ( FCKBrowserInfo.IsGecko && ( sBody.length == 0 || FCKRegexLib.EmptyParagraph.test( sBody ) ) ) | ||||
sBody = '<br type="_moz">' ; | ||||
this._BodyHTML = sBody ; | ||||
} | ||||
else | ||||
this._BodyHTML = html ; // Invalid HTML input. | ||||
} | ||||
// Create the editing area IFRAME. | ||||
var oIFrame = this.IFrame = oTargetDocument.createElement( 'iframe' ) ; | ||||
// IE: Avoid JavaScript errors thrown by the editing are source (like tags events). | ||||
// See #1055. | ||||
var sOverrideError = '<script type="text/javascript" _fcktemp="true">window.onerror=function(){return true;};</script>' ; | ||||
oIFrame.frameBorder = 0 ; | ||||
oIFrame.style.width = oIFrame.style.height = '100%' ; | ||||
if ( FCK_IS_CUSTOM_DOMAIN && FCKBrowserInfo.IsIE ) | ||||
{ | ||||
window._FCKHtmlToLoad = html.replace( /<head>/i, '<head>' + sOverrideError ) ; | ||||
oIFrame.src = 'javascript:void( (function(){' + | ||||
'document.open() ;' + | ||||
'document.domain="' + document.domain + '" ;' + | ||||
'document.write( window.parent._FCKHtmlToLoad );' + | ||||
'document.close() ;' + | ||||
'window.parent._FCKHtmlToLoad = null ;' + | ||||
'})() )' ; | ||||
} | ||||
else if ( !FCKBrowserInfo.IsGecko ) | ||||
{ | ||||
// Firefox will render the tables inside the body in Quirks mode if the | ||||
// source of the iframe is set to javascript. see #515 | ||||
oIFrame.src = 'javascript:void(0)' ; | ||||
} | ||||
// Append the new IFRAME to the target. For IE, it must be done after | ||||
// setting the "src", to avoid the "secure/unsecure" message under HTTPS. | ||||
eTargetElement.appendChild( oIFrame ) ; | ||||
// Get the window and document objects used to interact with the newly created IFRAME. | ||||
this.Window = oIFrame.contentWindow ; | ||||
// IE: Avoid JavaScript errors thrown by the editing are source (like tags events). | ||||
// TODO: This error handler is not being fired. | ||||
// this.Window.onerror = function() { alert( 'Error!' ) ; return true ; } | ||||
if ( !FCK_IS_CUSTOM_DOMAIN || !FCKBrowserInfo.IsIE ) | ||||
{ | ||||
var oDoc = this.Window.document ; | ||||
oDoc.open() ; | ||||
oDoc.write( html.replace( /<head>/i, '<head>' + sOverrideError ) ) ; | ||||
oDoc.close() ; | ||||
} | ||||
if ( FCKBrowserInfo.IsAIR ) | ||||
FCKAdobeAIR.EditingArea_Start( oDoc, html ) ; | ||||
// Firefox 1.0.x is buggy... ohh yes... so let's do it two times and it | ||||
// will magically work. | ||||
if ( FCKBrowserInfo.IsGecko10 && !secondCall ) | ||||
{ | ||||
this.Start( html, true ) ; | ||||
return ; | ||||
} | ||||
if ( oIFrame.readyState && oIFrame.readyState != 'completed' ) | ||||
{ | ||||
var editArea = this ; | ||||
// Using a IE alternative for DOMContentLoaded, similar to the | ||||
// solution proposed at http://javascript.nwbox.com/IEContentLoaded/ | ||||
setTimeout( function() | ||||
{ | ||||
try | ||||
{ | ||||
editArea.Window.document.documentElement.doScroll("left") ; | ||||
} | ||||
catch(e) | ||||
{ | ||||
setTimeout( arguments.callee, 0 ) ; | ||||
return ; | ||||
} | ||||
editArea.Window._FCKEditingArea = editArea ; | ||||
FCKEditingArea_CompleteStart.call( editArea.Window ) ; | ||||
}, 0 ) ; | ||||
} | ||||
else | ||||
{ | ||||
this.Window._FCKEditingArea = this ; | ||||
// FF 1.0.x is buggy... we must wait a lot to enable editing because | ||||
// sometimes the content simply disappears, for example when pasting | ||||
// "bla1!<img src='some_url'>!bla2" in the source and then switching | ||||
// back to design. | ||||
if ( FCKBrowserInfo.IsGecko10 ) | ||||
this.Window.setTimeout( FCKEditingArea_CompleteStart, 500 ) ; | ||||
else | ||||
FCKEditingArea_CompleteStart.call( this.Window ) ; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
var eTextarea = this.Textarea = oTargetDocument.createElement( 'textarea' ) ; | ||||
eTextarea.className = 'SourceField' ; | ||||
eTextarea.dir = 'ltr' ; | ||||
FCKDomTools.SetElementStyles( eTextarea, | ||||
{ | ||||
width : '100%', | ||||
height : '100%', | ||||
border : 'none', | ||||
resize : 'none', | ||||
outline : 'none' | ||||
} ) ; | ||||
eTargetElement.appendChild( eTextarea ) ; | ||||
eTextarea.value = html ; | ||||
// Fire the "OnLoad" event. | ||||
FCKTools.RunFunction( this.OnLoad ) ; | ||||
} | ||||
} | ||||
// "this" here is FCKEditingArea.Window | ||||
function FCKEditingArea_CompleteStart() | ||||
{ | ||||
// On Firefox, the DOM takes a little to become available. So we must wait for it in a loop. | ||||
if ( !this.document.body ) | ||||
{ | ||||
this.setTimeout( FCKEditingArea_CompleteStart, 50 ) ; | ||||
return ; | ||||
} | ||||
var oEditorArea = this._FCKEditingArea ; | ||||
// Save this reference to be re-used later. | ||||
oEditorArea.Document = oEditorArea.Window.document ; | ||||
oEditorArea.MakeEditable() ; | ||||
// Fire the "OnLoad" event. | ||||
FCKTools.RunFunction( oEditorArea.OnLoad ) ; | ||||
} | ||||
FCKEditingArea.prototype.MakeEditable = function() | ||||
{ | ||||
var oDoc = this.Document ; | ||||
if ( FCKBrowserInfo.IsIE ) | ||||
{ | ||||
// Kludge for #141 and #523 | ||||
oDoc.body.disabled = true ; | ||||
oDoc.body.contentEditable = true ; | ||||
oDoc.body.removeAttribute( "disabled" ) ; | ||||
/* The following commands don't throw errors, but have no effect. | ||||
oDoc.execCommand( 'AutoDetect', false, false ) ; | ||||
oDoc.execCommand( 'KeepSelection', false, true ) ; | ||||
*/ | ||||
} | ||||
else | ||||
{ | ||||
try | ||||
{ | ||||
// Disable Firefox 2 Spell Checker. | ||||
oDoc.body.spellcheck = ( this.FFSpellChecker !== false ) ; | ||||
if ( this._BodyHTML ) | ||||
{ | ||||
oDoc.body.innerHTML = this._BodyHTML ; | ||||
oDoc.body.offsetLeft ; // Don't remove, this is a hack to fix Opera 9.50, see #2264. | ||||
this._BodyHTML = null ; | ||||
} | ||||
oDoc.designMode = 'on' ; | ||||
// Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects (by Alfonso Martinez) | ||||
oDoc.execCommand( 'enableObjectResizing', false, !FCKConfig.DisableObjectResizing ) ; | ||||
// Disable the standard table editing features of Firefox. | ||||
oDoc.execCommand( 'enableInlineTableEditing', false, !FCKConfig.DisableFFTableHandles ) ; | ||||
} | ||||
catch (e) | ||||
{ | ||||
// In Firefox if the iframe is initially hidden it can't be set to designMode and it raises an exception | ||||
// So we set up a DOM Mutation event Listener on the HTML, as it will raise several events when the document is visible again | ||||
FCKTools.AddEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; | ||||
} | ||||
} | ||||
} | ||||
// This function processes the notifications of the DOM Mutation event on the document | ||||
// We use it to know that the document will be ready to be editable again (or we hope so) | ||||
function FCKEditingArea_Document_AttributeNodeModified( evt ) | ||||
{ | ||||
var editingArea = evt.currentTarget.contentWindow._FCKEditingArea ; | ||||
// We want to run our function after the events no longer fire, so we can know that it's a stable situation | ||||
if ( editingArea._timer ) | ||||
window.clearTimeout( editingArea._timer ) ; | ||||
editingArea._timer = FCKTools.SetTimeout( FCKEditingArea_MakeEditableByMutation, 1000, editingArea ) ; | ||||
} | ||||
// This function ideally should be called after the document is visible, it does clean up of the | ||||
// mutation tracking and tries again to make the area editable. | ||||
function FCKEditingArea_MakeEditableByMutation() | ||||
{ | ||||
// Clean up | ||||
delete this._timer ; | ||||
// Now we don't want to keep on getting this event | ||||
FCKTools.RemoveEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; | ||||
// Let's try now to set the editing area editable | ||||
// If it fails it will set up the Mutation Listener again automatically | ||||
this.MakeEditable() ; | ||||
} | ||||
FCKEditingArea.prototype.Focus = function() | ||||
{ | ||||
try | ||||
{ | ||||
if ( this.Mode == FCK_EDITMODE_WYSIWYG ) | ||||
{ | ||||
if ( FCKBrowserInfo.IsIE ) | ||||
this._FocusIE() ; | ||||
else | ||||
this.Window.focus() ; | ||||
} | ||||
else | ||||
{ | ||||
var oDoc = FCKTools.GetElementDocument( this.Textarea ) ; | ||||
if ( (!oDoc.hasFocus || oDoc.hasFocus() ) && oDoc.activeElement == this.Textarea ) | ||||
return ; | ||||
this.Textarea.focus() ; | ||||
} | ||||
} | ||||
catch(e) {} | ||||
} | ||||
FCKEditingArea.prototype._FocusIE = function() | ||||
{ | ||||
// In IE it can happen that the document is in theory focused but the | ||||
// active element is outside of it. | ||||
this.Document.body.setActive() ; | ||||
this.Window.focus() ; | ||||
// Kludge for #141... yet more code to workaround IE bugs | ||||
var range = this.Document.selection.createRange() ; | ||||
var parentNode = range.parentElement() ; | ||||
var parentTag = parentNode.nodeName.toLowerCase() ; | ||||
// Only apply the fix when in a block, and the block is empty. | ||||
if ( parentNode.childNodes.length > 0 || | ||||
!( FCKListsLib.BlockElements[parentTag] || | ||||
FCKListsLib.NonEmptyBlockElements[parentTag] ) ) | ||||
{ | ||||
return ; | ||||
} | ||||
// Force the selection to happen, in this way we guarantee the focus will | ||||
// be there. | ||||
range = new FCKDomRange( this.Window ) ; | ||||
range.MoveToElementEditStart( parentNode ) ; | ||||
range.Select() ; | ||||
} | ||||
function FCKEditingArea_Cleanup() | ||||
{ | ||||
if ( this.Document ) | ||||
this.Document.body.innerHTML = "" ; | ||||
this.TargetElement = null ; | ||||
this.IFrame = null ; | ||||
this.Document = null ; | ||||
this.Textarea = null ; | ||||
if ( this.Window ) | ||||
{ | ||||
this.Window._FCKEditingArea = null ; | ||||
this.Window = null ; | ||||
} | ||||
} | ||||