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 * Utility functions.
22 */
23
24var FCKTools = new Object() ;
25
26FCKTools.CreateBogusBR = function( targetDocument )
27{
28	var eBR = targetDocument.createElement( 'br' ) ;
29//	eBR.setAttribute( '_moz_editor_bogus_node', 'TRUE' ) ;
30	eBR.setAttribute( 'type', '_moz' ) ;
31	return eBR ;
32}
33
34// Returns a reference to the appended style sheet or an array with all the appended references
35FCKTools.AppendStyleSheet = function( documentElement, cssFileUrlOrArray )
36{
37	if ( typeof( cssFileUrlOrArray ) == 'string' )
38		return this._AppendStyleSheet( documentElement, cssFileUrlOrArray ) ;
39	else
40	{
41		var aStyleSheeArray = new Array() ;
42
43		for ( var i = 0 ; i < cssFileUrlOrArray.length ; i++ )
44			aStyleSheeArray.push(this._AppendStyleSheet( documentElement, cssFileUrlOrArray[i] ) ) ;
45
46		return aStyleSheeArray ;
47	}
48}
49
50FCKTools.AppendStyleString = function ( documentElement, cssStyles )
51{
52	this._AppendStyleString( documentElement, cssStyles ) ;
53}
54
55FCKTools.GetElementDocument = function ( element )
56{
57	return element.ownerDocument || element.document ;
58}
59
60// Get the window object where the element is placed in.
61FCKTools.GetElementWindow = function( element )
62{
63	return this.GetDocumentWindow( this.GetElementDocument( element ) ) ;
64}
65
66FCKTools.GetDocumentWindow = function( document )
67{
68	// With Safari, there is not way to retrieve the window from the document, so we must fix it.
69	if ( FCKBrowserInfo.IsSafari && !document.parentWindow )
70		this.FixDocumentParentWindow( window.top ) ;
71
72	return document.parentWindow || document.defaultView ;
73}
74
75/*
76	This is a Safari specific function that fix the reference to the parent
77	window from the document object.
78*/
79FCKTools.FixDocumentParentWindow = function( targetWindow )
80{
81	targetWindow.document.parentWindow = targetWindow ;
82
83	for ( var i = 0 ; i < targetWindow.frames.length ; i++ )
84		FCKTools.FixDocumentParentWindow( targetWindow.frames[i] ) ;
85}
86
87FCKTools.HTMLEncode = function( text )
88{
89	if ( !text )
90		return '' ;
91
92	text = text.replace( /&/g, '&amp;' ) ;
93	text = text.replace( /</g, '&lt;' ) ;
94	text = text.replace( />/g, '&gt;' ) ;
95
96	return text ;
97}
98
99FCKTools.HTMLDecode = function( text )
100{
101	if ( !text )
102		return '' ;
103
104	text = text.replace( /&gt;/g, '>' ) ;
105	text = text.replace( /&lt;/g, '<' ) ;
106	text = text.replace( /&amp;/g, '&' ) ;
107
108	return text ;
109}
110
111FCKTools._ProcessLineBreaksForPMode = function( oEditor, text, liState, node, strArray )
112{
113	var closeState = 0 ;
114	var blockStartTag = "<p>" ;
115	var blockEndTag = "</p>" ;
116	var lineBreakTag = "<br />" ;
117	if ( liState )
118	{
119		blockStartTag = "<li>" ;
120		blockEndTag = "</li>" ;
121		closeState = 1 ;
122	}
123
124	// Are we currently inside a <p> tag now?
125	// If yes, close it at the next double line break.
126	while ( node && node != oEditor.FCK.EditorDocument.body )
127	{
128		if ( node.tagName.toLowerCase() == 'p' )
129		{
130			closeState = 1 ;
131			break;
132		}
133		node = node.parentNode ;
134	}
135
136	for ( var i = 0 ; i < text.length ; i++ )
137	{
138		var c = text.charAt( i ) ;
139		if ( c == '\r' )
140			continue ;
141
142		if ( c != '\n' )
143		{
144			strArray.push( c ) ;
145			continue ;
146		}
147
148		// Now we have encountered a line break.
149		// Check if the next character is also a line break.
150		var n = text.charAt( i + 1 ) ;
151		if ( n == '\r' )
152		{
153			i++ ;
154			n = text.charAt( i + 1 ) ;
155		}
156		if ( n == '\n' )
157		{
158			i++ ;	// ignore next character - we have already processed it.
159			if ( closeState )
160				strArray.push( blockEndTag ) ;
161			strArray.push( blockStartTag ) ;
162			closeState = 1 ;
163		}
164		else
165			strArray.push( lineBreakTag ) ;
166	}
167}
168
169FCKTools._ProcessLineBreaksForDivMode = function( oEditor, text, liState, node, strArray )
170{
171	var closeState = 0 ;
172	var blockStartTag = "<div>" ;
173	var blockEndTag = "</div>" ;
174	if ( liState )
175	{
176		blockStartTag = "<li>" ;
177		blockEndTag = "</li>" ;
178		closeState = 1 ;
179	}
180
181	// Are we currently inside a <div> tag now?
182	// If yes, close it at the next double line break.
183	while ( node && node != oEditor.FCK.EditorDocument.body )
184	{
185		if ( node.tagName.toLowerCase() == 'div' )
186		{
187			closeState = 1 ;
188			break ;
189		}
190		node = node.parentNode ;
191	}
192
193	for ( var i = 0 ; i < text.length ; i++ )
194	{
195		var c = text.charAt( i ) ;
196		if ( c == '\r' )
197			continue ;
198
199		if ( c != '\n' )
200		{
201			strArray.push( c ) ;
202			continue ;
203		}
204
205		if ( closeState )
206		{
207			if ( strArray[ strArray.length - 1 ] == blockStartTag )
208			{
209				// A div tag must have some contents inside for it to be visible.
210				strArray.push( "&nbsp;" ) ;
211			}
212			strArray.push( blockEndTag ) ;
213		}
214		strArray.push( blockStartTag ) ;
215		closeState = 1 ;
216	}
217	if ( closeState )
218		strArray.push( blockEndTag ) ;
219}
220
221FCKTools._ProcessLineBreaksForBrMode = function( oEditor, text, liState, node, strArray )
222{
223	var closeState = 0 ;
224	var blockStartTag = "<br />" ;
225	var blockEndTag = "" ;
226	if ( liState )
227	{
228		blockStartTag = "<li>" ;
229		blockEndTag = "</li>" ;
230		closeState = 1 ;
231	}
232
233	for ( var i = 0 ; i < text.length ; i++ )
234	{
235		var c = text.charAt( i ) ;
236		if ( c == '\r' )
237			continue ;
238
239		if ( c != '\n' )
240		{
241			strArray.push( c ) ;
242			continue ;
243		}
244
245		if ( closeState && blockEndTag.length )
246			strArray.push ( blockEndTag ) ;
247		strArray.push( blockStartTag ) ;
248		closeState = 1 ;
249	}
250}
251
252FCKTools.ProcessLineBreaks = function( oEditor, oConfig, text )
253{
254	var enterMode = oConfig.EnterMode.toLowerCase() ;
255	var strArray = [] ;
256
257	// Is the caret or selection inside an <li> tag now?
258	var liState = 0 ;
259	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
260	range.MoveToSelection() ;
261	var node = range._Range.startContainer ;
262	while ( node && node.nodeType != 1 )
263		node = node.parentNode ;
264	if ( node && node.tagName.toLowerCase() == 'li' )
265		liState = 1 ;
266
267	if ( enterMode == 'p' )
268		this._ProcessLineBreaksForPMode( oEditor, text, liState, node, strArray ) ;
269	else if ( enterMode == 'div' )
270		this._ProcessLineBreaksForDivMode( oEditor, text, liState, node, strArray ) ;
271	else if ( enterMode == 'br' )
272		this._ProcessLineBreaksForBrMode( oEditor, text, liState, node, strArray ) ;
273	return strArray.join( "" ) ;
274}
275
276/**
277 * Adds an option to a SELECT element.
278 */
279FCKTools.AddSelectOption = function( selectElement, optionText, optionValue )
280{
281	var oOption = FCKTools.GetElementDocument( selectElement ).createElement( "OPTION" ) ;
282
283	oOption.text	= optionText ;
284	oOption.value	= optionValue ;
285
286	selectElement.options.add(oOption) ;
287
288	return oOption ;
289}
290
291FCKTools.RunFunction = function( func, thisObject, paramsArray, timerWindow )
292{
293	if ( func )
294		this.SetTimeout( func, 0, thisObject, paramsArray, timerWindow ) ;
295}
296
297FCKTools.SetTimeout = function( func, milliseconds, thisObject, paramsArray, timerWindow )
298{
299	return ( timerWindow || window ).setTimeout(
300		function()
301		{
302			if ( paramsArray )
303				func.apply( thisObject, [].concat( paramsArray ) ) ;
304			else
305				func.apply( thisObject ) ;
306		},
307		milliseconds ) ;
308}
309
310FCKTools.SetInterval = function( func, milliseconds, thisObject, paramsArray, timerWindow )
311{
312	return ( timerWindow || window ).setInterval(
313		function()
314		{
315			func.apply( thisObject, paramsArray || [] ) ;
316		},
317		milliseconds ) ;
318}
319
320FCKTools.ConvertStyleSizeToHtml = function( size )
321{
322	return size.EndsWith( '%' ) ? size : parseInt( size, 10 ) ;
323}
324
325FCKTools.ConvertHtmlSizeToStyle = function( size )
326{
327	return size.EndsWith( '%' ) ? size : ( size + 'px' ) ;
328}
329
330// START iCM MODIFICATIONS
331// Amended to accept a list of one or more ascensor tag names
332// Amended to check the element itself before working back up through the parent hierarchy
333FCKTools.GetElementAscensor = function( element, ascensorTagNames )
334{
335//	var e = element.parentNode ;
336	var e = element ;
337	var lstTags = "," + ascensorTagNames.toUpperCase() + "," ;
338
339	while ( e )
340	{
341		if ( lstTags.indexOf( "," + e.nodeName.toUpperCase() + "," ) != -1 )
342			return e ;
343
344		e = e.parentNode ;
345	}
346	return null ;
347}
348// END iCM MODIFICATIONS
349
350FCKTools.CreateEventListener = function( func, params )
351{
352	var f = function()
353	{
354		var aAllParams = [] ;
355
356		for ( var i = 0 ; i < arguments.length ; i++ )
357			aAllParams.push( arguments[i] ) ;
358
359		func.apply( this, aAllParams.concat( params ) ) ;
360	}
361
362	return f ;
363}
364
365FCKTools.IsStrictMode = function( document )
366{
367	// There is no compatMode in Safari, but it seams that it always behave as
368	// CSS1Compat, so let's assume it as the default.
369	return ( 'CSS1Compat' == ( document.compatMode || 'CSS1Compat' ) ) ;
370}
371
372// Transforms a "arguments" object to an array.
373FCKTools.ArgumentsToArray = function( args, startIndex, maxLength )
374{
375	startIndex = startIndex || 0 ;
376	maxLength = maxLength || args.length ;
377
378	var argsArray = new Array() ;
379
380	for ( var i = startIndex ; i < startIndex + maxLength && i < args.length ; i++ )
381		argsArray.push( args[i] ) ;
382
383	return argsArray ;
384}
385
386FCKTools.CloneObject = function( sourceObject )
387{
388	var fCloneCreator = function() {} ;
389	fCloneCreator.prototype = sourceObject ;
390	return new fCloneCreator ;
391}
392
393// Appends a bogus <br> at the end of the element, if not yet available.
394FCKTools.AppendBogusBr = function( element )
395{
396	if ( !element )
397		return ;
398
399	var eLastChild = this.GetLastItem( element.getElementsByTagName('br') ) ;
400
401	if ( !eLastChild || ( eLastChild.getAttribute( 'type', 2 ) != '_moz' && eLastChild.getAttribute( '_moz_dirty' ) == null ) )
402	{
403		var doc = this.GetElementDocument( element ) ;
404
405		if ( FCKBrowserInfo.IsOpera )
406			element.appendChild( doc.createTextNode('') ) ;
407		else
408			element.appendChild( this.CreateBogusBR( doc ) ) ;
409	}
410}
411
412FCKTools.GetLastItem = function( list )
413{
414	if ( list.length > 0 )
415		return list[ list.length - 1 ] ;
416
417	return null ;
418}
419
420FCKTools.GetDocumentPosition = function( w, node )
421{
422	var x = 0 ;
423	var y = 0 ;
424	var curNode = node ;
425	while ( curNode && curNode != w.document.body )
426	{
427		x += curNode.offsetLeft - curNode.scrollLeft ;
428		y += curNode.offsetTop - curNode.scrollTop ;
429		curNode = curNode.offsetParent ;
430	}
431	return { "x" : x, "y" : y } ;
432}
433
434FCKTools.GetWindowPosition = function( w, node )
435{
436	var pos = this.GetDocumentPosition( w, node ) ;
437	var scroll = FCKTools.GetScrollPosition( w ) ;
438	pos.x -= scroll.X ;
439	pos.y -= scroll.Y ;
440	return pos ;
441}
442
443FCKTools.ProtectFormStyles = function( formNode )
444{
445	if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
446		return [] ;
447	var hijackRecord = [] ;
448	var hijackNames = [ 'style', 'className' ] ;
449	for ( var i = 0 ; i < hijackNames.length ; i++ )
450	{
451		var name = hijackNames[i] ;
452		if ( formNode.elements.namedItem( name ) )
453		{
454			var hijackNode = formNode.elements.namedItem( name ) ;
455			hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ) ;
456			formNode.removeChild( hijackNode ) ;
457		}
458	}
459	return hijackRecord ;
460}
461
462FCKTools.RestoreFormStyles = function( formNode, hijackRecord )
463{
464	if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
465		return ;
466	if ( hijackRecord.length > 0 )
467	{
468		for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- )
469		{
470			var node = hijackRecord[i][0] ;
471			var sibling = hijackRecord[i][1] ;
472			if ( sibling )
473				formNode.insertBefore( node, sibling ) ;
474			else
475				formNode.appendChild( node ) ;
476		}
477	}
478}
479
480// Perform a one-step DFS walk.
481FCKTools.GetNextNode = function( node, limitNode )
482{
483	if ( node.firstChild )
484		return node.firstChild ;
485	else if ( node.nextSibling )
486		return node.nextSibling ;
487	else
488	{
489		var ancestor = node.parentNode ;
490		while ( ancestor )
491		{
492			if ( ancestor == limitNode )
493				return null ;
494			if ( ancestor.nextSibling )
495				return ancestor.nextSibling ;
496			else
497				ancestor = ancestor.parentNode ;
498		}
499	}
500	return null ;
501}
502
503FCKTools.GetNextTextNode = function( textnode, limitNode, checkStop )
504{
505	node = this.GetNextNode( textnode, limitNode ) ;
506	if ( checkStop && node && checkStop( node ) )
507		return null ;
508	while ( node && node.nodeType != 3 )
509	{
510		node = this.GetNextNode( node, limitNode ) ;
511		if ( checkStop && node && checkStop( node ) )
512			return null ;
513	}
514	return node ;
515}
516
517/**
518 * Merge all objects passed by argument into a single object.
519 */
520FCKTools.Merge = function()
521{
522	var args = arguments ;
523	var o = args[0] ;
524
525	for ( var i = 1 ; i < args.length ; i++ )
526	{
527		var arg = args[i] ;
528		for ( var p in arg )
529			o[p] = arg[p] ;
530	}
531
532	return o ;
533}
534
535/**
536 * Check if the passed argument is a real Array. It may not working when
537 * calling it cross windows.
538 */
539FCKTools.IsArray = function( it )
540{
541	return ( it instanceof Array ) ;
542}
543
544/**
545 * Appends a "length" property to an object, containing the number of
546 * properties available on it, excluded the append property itself.
547 */
548FCKTools.AppendLengthProperty = function( targetObject, propertyName )
549{
550	var counter = 0 ;
551
552	for ( var n in targetObject )
553		counter++ ;
554
555	return targetObject[ propertyName || 'length' ] = counter ;
556}
557
558/**
559 * Gets the browser parsed version of a css text (style attribute value). On
560 * some cases, the browser makes changes to the css text, returning a different
561 * value. For example, hexadecimal colors get transformed to rgb().
562 */
563FCKTools.NormalizeCssText = function( unparsedCssText )
564{
565	// Injects the style in a temporary span object, so the browser parses it,
566	// retrieving its final format.
567	var tempSpan = document.createElement( 'span' ) ;
568	tempSpan.style.cssText = unparsedCssText ;
569	return tempSpan.style.cssText ;
570}
571
572/**
573 * Utility function to wrap a call to an object's method,
574 * so it can be passed for example to an event handler,
575 * and then it will be executed with 'this' being the object.
576 */
577FCKTools.Hitch = function( obj, methodName )
578{
579  return function() { obj[methodName].apply(obj, arguments); } ;
580}
581