| 1 |
/* |
| 2 |
* FCKeditor - The text editor for Internet - http://www.fckeditor.net |
| 3 |
* Copyright (C) 2003-2009 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 |
*/ |
| 28 |
var 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 |
*/ |
| 41 |
FCKEditingArea.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 |
while( eTargetElement.firstChild ) |
| 48 |
eTargetElement.removeChild( eTargetElement.firstChild ) ; |
| 49 |
|
| 50 |
if ( this.Mode == FCK_EDITMODE_WYSIWYG ) |
| 51 |
{ |
| 52 |
// For FF, document.domain must be set only when different, otherwhise |
| 53 |
// we'll strangely have "Permission denied" issues. |
| 54 |
if ( FCK_IS_CUSTOM_DOMAIN ) |
| 55 |
html = '<script>document.domain="' + FCK_RUNTIME_DOMAIN + '";</script>' + html ; |
| 56 |
|
| 57 |
// IE has a bug with the <base> tag... it must have a </base> closer, |
| 58 |
// otherwise the all successive tags will be set as children nodes of the <base>. |
| 59 |
if ( FCKBrowserInfo.IsIE ) |
| 60 |
html = html.replace( /(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi, '$1></base>' ) ; |
| 61 |
else if ( !secondCall ) |
| 62 |
{ |
| 63 |
// Gecko moves some tags out of the body to the head, so we must use |
| 64 |
// innerHTML to set the body contents (SF BUG 1526154). |
| 65 |
|
| 66 |
// Extract the BODY contents from the html. |
| 67 |
var oMatchBefore = html.match( FCKRegexLib.BeforeBody ) ; |
| 68 |
var oMatchAfter = html.match( FCKRegexLib.AfterBody ) ; |
| 69 |
|
| 70 |
if ( oMatchBefore && oMatchAfter ) |
| 71 |
{ |
| 72 |
var sBody = html.substr( oMatchBefore[1].length, |
| 73 |
html.length - oMatchBefore[1].length - oMatchAfter[1].length ) ; // This is the BODY tag contents. |
| 74 |
|
| 75 |
html = |
| 76 |
oMatchBefore[1] + // This is the HTML until the <body...> tag, inclusive. |
| 77 |
' ' + |
| 78 |
oMatchAfter[1] ; // This is the HTML from the </body> tag, inclusive. |
| 79 |
|
| 80 |
// If nothing in the body, place a BOGUS tag so the cursor will appear. |
| 81 |
if ( FCKBrowserInfo.IsGecko && ( sBody.length == 0 || FCKRegexLib.EmptyParagraph.test( sBody ) ) ) |
| 82 |
sBody = '<br type="_moz">' ; |
| 83 |
|
| 84 |
this._BodyHTML = sBody ; |
| 85 |
|
| 86 |
} |
| 87 |
else |
| 88 |
this._BodyHTML = html ; // Invalid HTML input. |
| 89 |
} |
| 90 |
|
| 91 |
// Create the editing area IFRAME. |
| 92 |
var oIFrame = this.IFrame = oTargetDocument.createElement( 'iframe' ) ; |
| 93 |
|
| 94 |
// IE: Avoid JavaScript errors thrown by the editing are source (like tags events). |
| 95 |
// See #1055. |
| 96 |
var sOverrideError = '<script type="text/javascript" _fcktemp="true">window.onerror=function(){return true;};</script>' ; |
| 97 |
|
| 98 |
oIFrame.frameBorder = 0 ; |
| 99 |
oIFrame.style.width = oIFrame.style.height = '100%' ; |
| 100 |
|
| 101 |
if ( FCK_IS_CUSTOM_DOMAIN && FCKBrowserInfo.IsIE ) |
| 102 |
{ |
| 103 |
window._FCKHtmlToLoad = html.replace( /<head>/i, '<head>' + sOverrideError ) ; |
| 104 |
oIFrame.src = 'javascript:void( (function(){' + |
| 105 |
'document.open() ;' + |
| 106 |
'document.domain="' + document.domain + '" ;' + |
| 107 |
'document.write( window.parent._FCKHtmlToLoad );' + |
| 108 |
'document.close() ;' + |
| 109 |
'window.parent._FCKHtmlToLoad = null ;' + |
| 110 |
'})() )' ; |
| 111 |
} |
| 112 |
else if ( !FCKBrowserInfo.IsGecko ) |
| 113 |
{ |
| 114 |
// Firefox will render the tables inside the body in Quirks mode if the |
| 115 |
// source of the iframe is set to javascript. see #515 |
| 116 |
oIFrame.src = 'javascript:void(0)' ; |
| 117 |
} |
| 118 |
|
| 119 |
// Append the new IFRAME to the target. For IE, it must be done after |
| 120 |
// setting the "src", to avoid the "secure/unsecure" message under HTTPS. |
| 121 |
eTargetElement.appendChild( oIFrame ) ; |
| 122 |
|
| 123 |
// Get the window and document objects used to interact with the newly created IFRAME. |
| 124 |
this.Window = oIFrame.contentWindow ; |
| 125 |
|
| 126 |
// IE: Avoid JavaScript errors thrown by the editing are source (like tags events). |
| 127 |
// TODO: This error handler is not being fired. |
| 128 |
// this.Window.onerror = function() { alert( 'Error!' ) ; return true ; } |
| 129 |
|
| 130 |
if ( !FCK_IS_CUSTOM_DOMAIN || !FCKBrowserInfo.IsIE ) |
| 131 |
{ |
| 132 |
var oDoc = this.Window.document ; |
| 133 |
|
| 134 |
oDoc.open() ; |
| 135 |
oDoc.write( html.replace( /<head>/i, '<head>' + sOverrideError ) ) ; |
| 136 |
oDoc.close() ; |
| 137 |
} |
| 138 |
|
| 139 |
if ( FCKBrowserInfo.IsAIR ) |
| 140 |
FCKAdobeAIR.EditingArea_Start( oDoc, html ) ; |
| 141 |
|
| 142 |
// Firefox 1.0.x is buggy... ohh yes... so let's do it two times and it |
| 143 |
// will magically work. |
| 144 |
if ( FCKBrowserInfo.IsGecko10 && !secondCall ) |
| 145 |
{ |
| 146 |
this.Start( html, true ) ; |
| 147 |
return ; |
| 148 |
} |
| 149 |
|
| 150 |
if ( oIFrame.readyState && oIFrame.readyState != 'completed' ) |
| 151 |
{ |
| 152 |
var editArea = this ; |
| 153 |
|
| 154 |
// Using a IE alternative for DOMContentLoaded, similar to the |
| 155 |
// solution proposed at http://javascript.nwbox.com/IEContentLoaded/ |
| 156 |
setTimeout( function() |
| 157 |
{ |
| 158 |
try |
| 159 |
{ |
| 160 |
editArea.Window.document.documentElement.doScroll("left") ; |
| 161 |
} |
| 162 |
catch(e) |
| 163 |
{ |
| 164 |
setTimeout( arguments.callee, 0 ) ; |
| 165 |
return ; |
| 166 |
} |
| 167 |
editArea.Window._FCKEditingArea = editArea ; |
| 168 |
FCKEditingArea_CompleteStart.call( editArea.Window ) ; |
| 169 |
}, 0 ) ; |
| 170 |
} |
| 171 |
else |
| 172 |
{ |
| 173 |
this.Window._FCKEditingArea = this ; |
| 174 |
|
| 175 |
// FF 1.0.x is buggy... we must wait a lot to enable editing because |
| 176 |
// sometimes the content simply disappears, for example when pasting |
| 177 |
// "bla1!<img src='some_url'>!bla2" in the source and then switching |
| 178 |
// back to design. |
| 179 |
if ( FCKBrowserInfo.IsGecko10 ) |
| 180 |
this.Window.setTimeout( FCKEditingArea_CompleteStart, 500 ) ; |
| 181 |
else |
| 182 |
FCKEditingArea_CompleteStart.call( this.Window ) ; |
| 183 |
} |
| 184 |
} |
| 185 |
else |
| 186 |
{ |
| 187 |
var eTextarea = this.Textarea = oTargetDocument.createElement( 'textarea' ) ; |
| 188 |
eTextarea.className = 'SourceField' ; |
| 189 |
eTextarea.dir = 'ltr' ; |
| 190 |
FCKDomTools.SetElementStyles( eTextarea, |
| 191 |
{ |
| 192 |
width : '100%', |
| 193 |
height : '100%', |
| 194 |
border : 'none', |
| 195 |
resize : 'none', |
| 196 |
outline : 'none' |
| 197 |
} ) ; |
| 198 |
eTargetElement.appendChild( eTextarea ) ; |
| 199 |
|
| 200 |
eTextarea.value = html ; |
| 201 |
|
| 202 |
// Fire the "OnLoad" event. |
| 203 |
FCKTools.RunFunction( this.OnLoad ) ; |
| 204 |
} |
| 205 |
} |
| 206 |
|
| 207 |
// "this" here is FCKEditingArea.Window |
| 208 |
function FCKEditingArea_CompleteStart() |
| 209 |
{ |
| 210 |
// On Firefox, the DOM takes a little to become available. So we must wait for it in a loop. |
| 211 |
if ( !this.document.body ) |
| 212 |
{ |
| 213 |
this.setTimeout( FCKEditingArea_CompleteStart, 50 ) ; |
| 214 |
return ; |
| 215 |
} |
| 216 |
|
| 217 |
var oEditorArea = this._FCKEditingArea ; |
| 218 |
|
| 219 |
// Save this reference to be re-used later. |
| 220 |
oEditorArea.Document = oEditorArea.Window.document ; |
| 221 |
|
| 222 |
oEditorArea.MakeEditable() ; |
| 223 |
|
| 224 |
// Fire the "OnLoad" event. |
| 225 |
FCKTools.RunFunction( oEditorArea.OnLoad ) ; |
| 226 |
} |
| 227 |
|
| 228 |
FCKEditingArea.prototype.MakeEditable = function() |
| 229 |
{ |
| 230 |
var oDoc = this.Document ; |
| 231 |
|
| 232 |
if ( FCKBrowserInfo.IsIE ) |
| 233 |
{ |
| 234 |
// Kludge for #141 and #523 |
| 235 |
oDoc.body.disabled = true ; |
| 236 |
oDoc.body.contentEditable = true ; |
| 237 |
oDoc.body.removeAttribute( "disabled" ) ; |
| 238 |
|
| 239 |
/* The following commands don't throw errors, but have no effect. |
| 240 |
oDoc.execCommand( 'AutoDetect', false, false ) ; |
| 241 |
oDoc.execCommand( 'KeepSelection', false, true ) ; |
| 242 |
*/ |
| 243 |
} |
| 244 |
else |
| 245 |
{ |
| 246 |
try |
| 247 |
{ |
| 248 |
// Disable Firefox 2 Spell Checker. |
| 249 |
oDoc.body.spellcheck = ( this.FFSpellChecker !== false ) ; |
| 250 |
|
| 251 |
if ( this._BodyHTML ) |
| 252 |
{ |
| 253 |
oDoc.body.innerHTML = this._BodyHTML ; |
| 254 |
oDoc.body.offsetLeft ; // Don't remove, this is a hack to fix Opera 9.50, see #2264. |
| 255 |
this._BodyHTML = null ; |
| 256 |
} |
| 257 |
|
| 258 |
oDoc.designMode = 'on' ; |
| 259 |
|
| 260 |
// Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects (by Alfonso Martinez) |
| 261 |
oDoc.execCommand( 'enableObjectResizing', false, !FCKConfig.DisableObjectResizing ) ; |
| 262 |
|
| 263 |
// Disable the standard table editing features of Firefox. |
| 264 |
oDoc.execCommand( 'enableInlineTableEditing', false, !FCKConfig.DisableFFTableHandles ) ; |
| 265 |
} |
| 266 |
catch (e) |
| 267 |
{ |
| 268 |
// In Firefox if the iframe is initially hidden it can't be set to designMode and it raises an exception |
| 269 |
// So we set up a DOM Mutation event Listener on the HTML, as it will raise several events when the document is visible again |
| 270 |
FCKTools.AddEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; |
| 271 |
} |
| 272 |
|
| 273 |
} |
| 274 |
} |
| 275 |
|
| 276 |
// This function processes the notifications of the DOM Mutation event on the document |
| 277 |
// We use it to know that the document will be ready to be editable again (or we hope so) |
| 278 |
function FCKEditingArea_Document_AttributeNodeModified( evt ) |
| 279 |
{ |
| 280 |
var editingArea = evt.currentTarget.contentWindow._FCKEditingArea ; |
| 281 |
|
| 282 |
// We want to run our function after the events no longer fire, so we can know that it's a stable situation |
| 283 |
if ( editingArea._timer ) |
| 284 |
window.clearTimeout( editingArea._timer ) ; |
| 285 |
|
| 286 |
editingArea._timer = FCKTools.SetTimeout( FCKEditingArea_MakeEditableByMutation, 1000, editingArea ) ; |
| 287 |
} |
| 288 |
|
| 289 |
// This function ideally should be called after the document is visible, it does clean up of the |
| 290 |
// mutation tracking and tries again to make the area editable. |
| 291 |
function FCKEditingArea_MakeEditableByMutation() |
| 292 |
{ |
| 293 |
// Clean up |
| 294 |
delete this._timer ; |
| 295 |
// Now we don't want to keep on getting this event |
| 296 |
FCKTools.RemoveEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ; |
| 297 |
// Let's try now to set the editing area editable |
| 298 |
// If it fails it will set up the Mutation Listener again automatically |
| 299 |
this.MakeEditable() ; |
| 300 |
} |
| 301 |
|
| 302 |
FCKEditingArea.prototype.Focus = function() |
| 303 |
{ |
| 304 |
try |
| 305 |
{ |
| 306 |
if ( this.Mode == FCK_EDITMODE_WYSIWYG ) |
| 307 |
{ |
| 308 |
if ( FCKBrowserInfo.IsIE ) |
| 309 |
this._FocusIE() ; |
| 310 |
else |
| 311 |
this.Window.focus() ; |
| 312 |
} |
| 313 |
else |
| 314 |
{ |
| 315 |
var oDoc = FCKTools.GetElementDocument( this.Textarea ) ; |
| 316 |
if ( (!oDoc.hasFocus || oDoc.hasFocus() ) && oDoc.activeElement == this.Textarea ) |
| 317 |
return ; |
| 318 |
|
| 319 |
this.Textarea.focus() ; |
| 320 |
} |
| 321 |
} |
| 322 |
catch(e) {} |
| 323 |
} |
| 324 |
|
| 325 |
FCKEditingArea.prototype._FocusIE = function() |
| 326 |
{ |
| 327 |
// In IE it can happen that the document is in theory focused but the |
| 328 |
// active element is outside of it. |
| 329 |
this.Document.body.setActive() ; |
| 330 |
|
| 331 |
this.Window.focus() ; |
| 332 |
|
| 333 |
// Kludge for #141... yet more code to workaround IE bugs |
| 334 |
var range = this.Document.selection.createRange() ; |
| 335 |
|
| 336 |
var parentNode = range.parentElement() ; |
| 337 |
var parentTag = parentNode.nodeName.toLowerCase() ; |
| 338 |
|
| 339 |
// Only apply the fix when in a block, and the block is empty. |
| 340 |
if ( parentNode.childNodes.length > 0 || |
| 341 |
!( FCKListsLib.BlockElements[parentTag] || |
| 342 |
FCKListsLib.NonEmptyBlockElements[parentTag] ) ) |
| 343 |
{ |
| 344 |
return ; |
| 345 |
} |
| 346 |
|
| 347 |
// Force the selection to happen, in this way we guarantee the focus will |
| 348 |
// be there. |
| 349 |
range = new FCKDomRange( this.Window ) ; |
| 350 |
range.MoveToElementEditStart( parentNode ) ; |
| 351 |
range.Select() ; |
| 352 |
} |
| 353 |
|
| 354 |
function FCKEditingArea_Cleanup() |
| 355 |
{ |
| 356 |
if ( this.Document ) |
| 357 |
this.Document.body.innerHTML = "" ; |
| 358 |
this.TargetElement = null ; |
| 359 |
this.IFrame = null ; |
| 360 |
this.Document = null ; |
| 361 |
this.Textarea = null ; |
| 362 |
|
| 363 |
if ( this.Window ) |
| 364 |
{ |
| 365 |
this.Window._FCKEditingArea = null ; |
| 366 |
this.Window = null ; |
| 367 |
} |
| 368 |
} |
| 369 |
|