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 | {
|
---|
358 | // Avoid IE crash if an object is selected on unload #2201
|
---|
359 | this.Document.selection.empty() ;
|
---|
360 | this.Document.body.innerHTML = "" ;
|
---|
361 | }
|
---|
362 | this.TargetElement = null ;
|
---|
363 | this.IFrame = null ;
|
---|
364 | this.Document = null ;
|
---|
365 | this.Textarea = null ;
|
---|
366 |
|
---|
367 | if ( this.Window )
|
---|
368 | {
|
---|
369 | this.Window._FCKEditingArea = null ;
|
---|
370 | this.Window = null ;
|
---|
371 | }
|
---|
372 | }
|
---|