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 * FCKEditingArea Class: renders an editable area. 22 */ 23 24/** 25 * @constructor 26 * @param {String} targetElement The element that will hold the editing area. Any child element present in the target will be deleted. 27 */ 28var FCKEditingArea = function( targetElement ) 29{ 30 this.TargetElement = targetElement ; 31 this.Mode = FCK_EDITMODE_WYSIWYG ; 32 33 if ( FCK.IECleanup ) 34 FCK.IECleanup.AddItem( this, FCKEditingArea_Cleanup ) ; 35} 36 37 38/** 39 * @param {String} html The complete HTML for the page, including DOCTYPE and the <html> tag. 40 */ 41FCKEditingArea.prototype.Start = function( html, secondCall ) 42{ 43 var eTargetElement = this.TargetElement ; 44 var oTargetDocument = FCKTools.GetElementDocument( eTargetElement ) ; 45 46 // Remove all child nodes from the target. 47 var oChild ; 48 while( ( oChild = eTargetElement.firstChild ) ) // Only one "=". 49 { 50 // Set innerHTML = '' to avoid memory leak. 51 if ( oChild.contentWindow ) 52 oChild.contentWindow.document.body.innerHTML = '' ; 53 54 eTargetElement.removeChild( oChild ) ; 55 } 56 57 if ( this.Mode == FCK_EDITMODE_WYSIWYG ) 58 { 59 // Create the editing area IFRAME. 60 var oIFrame = this.IFrame = oTargetDocument.createElement( 'iframe' ) ; 61 62 // Firefox will render the tables inside the body in Quirks mode if the 63 // source of the iframe is set to javascript. see #515 64 if ( !FCKBrowserInfo.IsGecko ) 65 oIFrame.src = 'javascript:void(0)' ; 66 67 oIFrame.frameBorder = 0 ; 68 oIFrame.width = oIFrame.height = '100%' ; 69 70 // Append the new IFRAME to the target. 71 eTargetElement.appendChild( oIFrame ) ; 72 73 // IE has a bug with the <base> tag... it must have a </base> closer, 74 // otherwise the all successive tags will be set as children nodes of the <base>. 75 if ( FCKBrowserInfo.IsIE ) 76 html = html.replace( /(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi, '$1></base>' ) ; 77 else if ( !secondCall ) 78 { 79 // Gecko moves some tags out of the body to the head, so we must use 80 // innerHTML to set the body contents (SF BUG 1526154). 81 82 // Extract the BODY contents from the html. 83 var oMatchBefore = html.match( FCKRegexLib.BeforeBody ) ; 84 var oMatchAfter = html.match( FCKRegexLib.AfterBody ) ; 85 86 if ( oMatchBefore && oMatchAfter ) 87 { 88 var sBody = html.substr( oMatchBefore[1].length, 89 html.length - oMatchBefore[1].length - oMatchAfter[1].length ) ; // This is the BODY tag contents. 90 91 html = 92 oMatchBefore[1] + // This is the HTML until the <body...> tag, inclusive. 93 ' ' + 94 oMatchAfter[1] ; // This is the HTML from the </body> tag, inclusive. 95 96 // If nothing in the body, place a BOGUS tag so the cursor will appear. 97 if ( FCKBrowserInfo.IsGecko && ( sBody.length == 0 || FCKRegexLib.EmptyParagraph.test( sBody ) ) ) 98 sBody = '<br type="_moz">' ; 99 100 this._BodyHTML = sBody ; 101 102 } 103 else 104 this._BodyHTML = html ; // Invalid HTML input. 105 } 106 107 // Get the window and document objects used to interact with the newly created IFRAME. 108 this.Window = oIFrame.contentWindow ; 109 110 // IE: Avoid JavaScript errors thrown by the editing are source (like tags events). 111 // TODO: This error handler is not being fired. 112 // this.Window.onerror = function() { alert( 'Error!' ) ; return true ; } 113 114 var oDoc = this.Document = this.Window.document ; 115 116 oDoc.open() ; 117 oDoc.write( html ) ; 118 oDoc.close() ; 119 120 // Firefox 1.0.x is buggy... ohh yes... so let's do it two times and it 121 // will magically work. 122 if ( FCKBrowserInfo.IsGecko10 && !secondCall ) 123 { 124 this.Start( html, true ) ; 125 return ; 126 } 127 128 this.Window._FCKEditingArea = this ; 129 130 // FF 1.0.x is buggy... we must wait a lot to enable editing because 131 // sometimes the content simply disappears, for example when pasting 132 // "bla1!<img src='some_url'>!bla2" in the source and then switching 133 // back to design. 134 if ( FCKBrowserInfo.IsGecko10 ) 135 this.Window.setTimeout( FCKEditingArea_CompleteStart, 500 ) ; 136 else 137 FCKEditingArea_CompleteStart.call( this.Window ) ; 138 } 139 else 140 { 141 var eTextarea = this.Textarea = oTargetDocument.createElement( 'textarea' ) ; 142 eTextarea.className = 'SourceField' ; 143 eTextarea.dir = 'ltr' ; 144 FCKDomTools.SetElementStyles( eTextarea, 145 { 146 width : '100%', 147 height : '100%', 148 border : 'none', 149 resize : 'none', 150 outline : 'none' 151 } ) ; 152 eTargetElement.appendChild( eTextarea ) ; 153 154 eTextarea.value = html ; 155 156 // Fire the "OnLoad" event. 157 FCKTools.RunFunction( this.OnLoad ) ; 158 } 159} 160 161// "this" here is FCKEditingArea.Window 162function FCKEditingArea_CompleteStart() 163{ 164 // On Firefox, the DOM takes a little to become available. So we must wait for it in a loop. 165 if ( !this.document.body ) 166 { 167 this.setTimeout( FCKEditingArea_CompleteStart, 50 ) ; 168 return ; 169 } 170 171 var oEditorArea = this._FCKEditingArea ; 172 173 oEditorArea.MakeEditable() ; 174 175 // Fire the "OnLoad" event. 176 FCKTools.RunFunction( oEditorArea.OnLoad ) ; 177} 178 179FCKEditingArea.prototype.MakeEditable = function() 180{ 181 var oDoc = this.Document ; 182 183 if ( FCKBrowserInfo.IsIE ) 184 { 185 // Kludge for #141 and #523 186 oDoc.body.disabled = true ; 187 oDoc.body.contentEditable = true ; 188 oDoc.body.removeAttribute( "disabled" ) ; 189 190 /* The following commands don't throw errors, but have no effect. 191 oDoc.execCommand( 'AutoDetect', false, false ) ; 192 oDoc.execCommand( 'KeepSelection', false, true ) ; 193 */ 194 } 195 else 196 { 197 try 198 { 199 // Disable Firefox 2 Spell Checker. 200 oDoc.body.spellcheck = ( this.FFSpellChecker !== false ) ; 201 202 if ( this._BodyHTML ) 203 { 204 oDoc.body.innerHTML = this._BodyHTML ; 205 this._BodyHTML = null ; 206 } 207 208 oDoc.designMode = 'on' ; 209 210 // Tell Gecko to use or not the <SPAN> tag for the bold, italic and underline. 211 try 212 { 213 oDoc.execCommand( 'styleWithCSS', false, FCKConfig.GeckoUseSPAN ) ; 214 } 215 catch (e) 216 { 217 // As evidenced here, useCSS is deprecated in favor of styleWithCSS: 218 // http://www.mozilla.org/editor/midas-spec.html 219 oDoc.execCommand( 'useCSS', false, !FCKConfig.GeckoUseSPAN ) ; 220 } 221 222 // Analyzing Firefox 1.5 source code, it seams that there is support for a 223 // "insertBrOnReturn" command. Applying it gives no error, but it doesn't 224 // gives the same behavior that you have with IE. It works only if you are 225 // already inside a paragraph and it doesn't render correctly in the first enter. 226 // oDoc.execCommand( 'insertBrOnReturn', false, false ) ; 227 228 // Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects (by Alfonso Martinez) 229 oDoc.execCommand( 'enableObjectResizing', false, !FCKConfig.DisableObjectResizing ) ; 230 231 // Disable the standard table editing features of Firefox. 232 oDoc.execCommand( 'enableInlineTableEditing', false, !FCKConfig.DisableFFTableHandles ) ; 233 } 234 catch (e) 235 { 236 // In Firefox if the iframe is initially hidden it can't be set to designMode and it raises an exception 237 // So we set up a DOM Mutation event Listener on the HTML, as it will raise several events when the document is visible again 238 FCKTools.AddEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; 239 } 240 241 } 242} 243 244// This function processes the notifications of the DOM Mutation event on the document 245// We use it to know that the document will be ready to be editable again (or we hope so) 246function FCKEditingArea_Document_AttributeNodeModified( evt ) 247{ 248 var editingArea = evt.currentTarget.contentWindow._FCKEditingArea ; 249 250 // We want to run our function after the events no longer fire, so we can know that it's a stable situation 251 if ( editingArea._timer ) 252 window.clearTimeout( editingArea._timer ) ; 253 254 editingArea._timer = FCKTools.SetTimeout( FCKEditingArea_MakeEditableByMutation, 1000, editingArea ) ; 255} 256 257// This function ideally should be called after the document is visible, it does clean up of the 258// mutation tracking and tries again to make the area editable. 259function FCKEditingArea_MakeEditableByMutation() 260{ 261 // Clean up 262 delete this._timer ; 263 // Now we don't want to keep on getting this event 264 FCKTools.RemoveEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; 265 // Let's try now to set the editing area editable 266 // If it fails it will set up the Mutation Listener again automatically 267 this.MakeEditable() ; 268} 269 270FCKEditingArea.prototype.Focus = function() 271{ 272 try 273 { 274 if ( this.Mode == FCK_EDITMODE_WYSIWYG ) 275 { 276 // The following check is important to avoid IE entering in a focus loop. Ref: 277 // http://sourceforge.net/tracker/index.php?func=detail&aid=1567060&group_id=75348&atid=543653 278 if ( FCKBrowserInfo.IsIE && this.Document.hasFocus() ) 279 this._EnsureFocusIE() ; 280 281 if ( FCKBrowserInfo.IsSafari ) 282 this.IFrame.focus() ; 283 else 284 { 285 this.Window.focus() ; 286 287 // In IE it can happen that the document is in theory focused but the active element is outside it 288 if ( FCKBrowserInfo.IsIE ) 289 this._EnsureFocusIE() ; 290 } 291 } 292 else 293 { 294 var oDoc = FCKTools.GetElementDocument( this.Textarea ) ; 295 if ( (!oDoc.hasFocus || oDoc.hasFocus() ) && oDoc.activeElement == this.Textarea ) 296 return ; 297 298 this.Textarea.focus() ; 299 } 300 } 301 catch(e) {} 302} 303 304FCKEditingArea.prototype._EnsureFocusIE = function() 305{ 306 // In IE it can happen that the document is in theory focused but the active element is outside it 307 this.Document.body.setActive() ; 308 309 // Kludge for #141... yet more code to workaround IE bugs 310 var range = this.Document.selection.createRange() ; 311 312 // Only apply the fix when in a block and the block is empty. 313 var parentNode = range.parentElement() ; 314 315 if ( ! ( parentNode.childNodes.length == 0 && ( 316 FCKListsLib.BlockElements[parentNode.nodeName.toLowerCase()] || 317 FCKListsLib.NonEmptyBlockElements[parentNode.nodeName.toLowerCase()] ) ) ) 318 return ; 319 320 var oldLength = range.text.length ; 321 range.moveEnd( "character", 1 ) ; 322 range.select() ; 323 if ( range.text.length > oldLength ) 324 { 325 range.moveEnd( "character", -1 ) ; 326 range.select() ; 327 } 328} 329 330function FCKEditingArea_Cleanup() 331{ 332 if ( this.Document ) 333 this.Document.body.innerHTML = "" ; 334 this.TargetElement = null ; 335 this.IFrame = null ; 336 this.Document = null ; 337 this.Textarea = null ; 338 339 if ( this.Window ) 340 { 341 this.Window._FCKEditingArea = null ; 342 this.Window = null ; 343 } 344} 345