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