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					'&nbsp;' +
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