source: extensions/FCKEditor/editor/_source/internals/fcktools.js @ 3295

Last change on this file since 3295 was 3295, checked in by patdenice, 15 years ago

New extension added:
FCK Editor (2.0.a)

File size: 19.1 KB
Line 
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 * 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/**
35 * Fixes relative URL entries defined inside CSS styles by appending a prefix
36 * to them.
37 * @param (String) cssStyles The CSS styles definition possibly containing url()
38 *              paths.
39 * @param (String) urlFixPrefix The prefix to append to relative URLs.
40 */
41FCKTools.FixCssUrls = function( urlFixPrefix, cssStyles )
42{
43        if ( !urlFixPrefix || urlFixPrefix.length == 0 )
44                return cssStyles ;
45
46        return cssStyles.replace( /url\s*\(([\s'"]*)(.*?)([\s"']*)\)/g, function( match, opener, path, closer )
47                {
48                        if ( /^\/|^\w?:/.test( path ) )
49                                return match ;
50                        else
51                                return 'url(' + opener + urlFixPrefix + path + closer + ')' ;
52                } ) ;
53}
54
55FCKTools._GetUrlFixedCss = function( cssStyles, urlFixPrefix )
56{
57        var match = cssStyles.match( /^([^|]+)\|([\s\S]*)/ ) ;
58
59        if ( match )
60                return FCKTools.FixCssUrls( match[1], match[2] ) ;
61        else
62                return cssStyles ;
63}
64
65/**
66 * Appends a <link css> or <style> element to the document.
67 * @param (Object) documentElement The DOM document object to which append the
68 *              stylesheet.
69 * @param (Variant) cssFileOrDef A String pointing to the CSS file URL or an
70 *              Array with many CSS file URLs or the CSS definitions for the <style>
71 *              element.
72 * @return {Array} An array containing all elements created in the target
73 *              document. It may include <link> or <style> elements, depending on the
74 *              value passed with cssFileOrDef.
75 */
76FCKTools.AppendStyleSheet = function( domDocument, cssFileOrArrayOrDef )
77{
78        if ( !cssFileOrArrayOrDef )
79                return [] ;
80
81        if ( typeof( cssFileOrArrayOrDef ) == 'string' )
82        {
83                // Test if the passed argument is an URL.
84                if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
85                {
86                        // The string may have several URLs separated by comma.
87                        return this.AppendStyleSheet( domDocument, cssFileOrArrayOrDef.split(',') ) ;
88                }
89                else
90                        return [ this.AppendStyleString( domDocument, FCKTools._GetUrlFixedCss( cssFileOrArrayOrDef ) ) ] ;
91        }
92        else
93        {
94                var styles = [] ;
95                for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
96                        styles.push( this._AppendStyleSheet( domDocument, cssFileOrArrayOrDef[i] ) ) ;
97                return styles ;
98        }
99}
100
101FCKTools.GetStyleHtml = (function()
102{
103        var getStyle = function( styleDef, markTemp )
104        {
105                if ( styleDef.length == 0 )
106                        return '' ;
107
108                var temp = markTemp ? ' _fcktemp="true"' : '' ;
109                return '<' + 'style type="text/css"' + temp + '>' + styleDef + '<' + '/style>' ;
110        }
111
112        var getLink = function( cssFileUrl, markTemp )
113        {
114                if ( cssFileUrl.length == 0 )
115                        return '' ;
116
117                var temp = markTemp ? ' _fcktemp="true"' : '' ;
118                return '<' + 'link href="' + cssFileUrl + '" type="text/css" rel="stylesheet" ' + temp + '/>' ;
119        }
120
121        return function( cssFileOrArrayOrDef, markTemp )
122        {
123                if ( !cssFileOrArrayOrDef )
124                        return '' ;
125
126                if ( typeof( cssFileOrArrayOrDef ) == 'string' )
127                {
128                        // Test if the passed argument is an URL.
129                        if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
130                        {
131                                // The string may have several URLs separated by comma.
132                                return this.GetStyleHtml( cssFileOrArrayOrDef.split(','), markTemp ) ;
133                        }
134                        else
135                                return getStyle( this._GetUrlFixedCss( cssFileOrArrayOrDef ), markTemp ) ;
136                }
137                else
138                {
139                        var html = '' ;
140
141                        for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
142                                html += getLink( cssFileOrArrayOrDef[i], markTemp ) ;
143
144                        return html ;
145                }
146        }
147})() ;
148
149FCKTools.GetElementDocument = function ( element )
150{
151        return element.ownerDocument || element.document ;
152}
153
154// Get the window object where the element is placed in.
155FCKTools.GetElementWindow = function( element )
156{
157        return this.GetDocumentWindow( this.GetElementDocument( element ) ) ;
158}
159
160FCKTools.GetDocumentWindow = function( document )
161{
162        // With Safari, there is not way to retrieve the window from the document, so we must fix it.
163        if ( FCKBrowserInfo.IsSafari && !document.parentWindow )
164                this.FixDocumentParentWindow( window.top ) ;
165
166        return document.parentWindow || document.defaultView ;
167}
168
169/*
170        This is a Safari specific function that fix the reference to the parent
171        window from the document object.
172*/
173FCKTools.FixDocumentParentWindow = function( targetWindow )
174{
175        if ( targetWindow.document )
176                targetWindow.document.parentWindow = targetWindow ;
177
178        for ( var i = 0 ; i < targetWindow.frames.length ; i++ )
179                FCKTools.FixDocumentParentWindow( targetWindow.frames[i] ) ;
180}
181
182FCKTools.HTMLEncode = function( text )
183{
184        if ( !text )
185                return '' ;
186
187        text = text.replace( /&/g, '&amp;' ) ;
188        text = text.replace( /</g, '&lt;' ) ;
189        text = text.replace( />/g, '&gt;' ) ;
190
191        return text ;
192}
193
194FCKTools.HTMLDecode = function( text )
195{
196        if ( !text )
197                return '' ;
198
199        text = text.replace( /&gt;/g, '>' ) ;
200        text = text.replace( /&lt;/g, '<' ) ;
201        text = text.replace( /&amp;/g, '&' ) ;
202
203        return text ;
204}
205
206FCKTools._ProcessLineBreaksForPMode = function( oEditor, text, liState, node, strArray )
207{
208        var closeState = 0 ;
209        var blockStartTag = "<p>" ;
210        var blockEndTag = "</p>" ;
211        var lineBreakTag = "<br />" ;
212        if ( liState )
213        {
214                blockStartTag = "<li>" ;
215                blockEndTag = "</li>" ;
216                closeState = 1 ;
217        }
218
219        // Are we currently inside a <p> tag now?
220        // If yes, close it at the next double line break.
221        while ( node && node != oEditor.FCK.EditorDocument.body )
222        {
223                if ( node.tagName.toLowerCase() == 'p' )
224                {
225                        closeState = 1 ;
226                        break;
227                }
228                node = node.parentNode ;
229        }
230
231        for ( var i = 0 ; i < text.length ; i++ )
232        {
233                var c = text.charAt( i ) ;
234                if ( c == '\r' )
235                        continue ;
236
237                if ( c != '\n' )
238                {
239                        strArray.push( c ) ;
240                        continue ;
241                }
242
243                // Now we have encountered a line break.
244                // Check if the next character is also a line break.
245                var n = text.charAt( i + 1 ) ;
246                if ( n == '\r' )
247                {
248                        i++ ;
249                        n = text.charAt( i + 1 ) ;
250                }
251                if ( n == '\n' )
252                {
253                        i++ ;   // ignore next character - we have already processed it.
254                        if ( closeState )
255                                strArray.push( blockEndTag ) ;
256                        strArray.push( blockStartTag ) ;
257                        closeState = 1 ;
258                }
259                else
260                        strArray.push( lineBreakTag ) ;
261        }
262}
263
264FCKTools._ProcessLineBreaksForDivMode = function( oEditor, text, liState, node, strArray )
265{
266        var closeState = 0 ;
267        var blockStartTag = "<div>" ;
268        var blockEndTag = "</div>" ;
269        if ( liState )
270        {
271                blockStartTag = "<li>" ;
272                blockEndTag = "</li>" ;
273                closeState = 1 ;
274        }
275
276        // Are we currently inside a <div> tag now?
277        // If yes, close it at the next double line break.
278        while ( node && node != oEditor.FCK.EditorDocument.body )
279        {
280                if ( node.tagName.toLowerCase() == 'div' )
281                {
282                        closeState = 1 ;
283                        break ;
284                }
285                node = node.parentNode ;
286        }
287
288        for ( var i = 0 ; i < text.length ; i++ )
289        {
290                var c = text.charAt( i ) ;
291                if ( c == '\r' )
292                        continue ;
293
294                if ( c != '\n' )
295                {
296                        strArray.push( c ) ;
297                        continue ;
298                }
299
300                if ( closeState )
301                {
302                        if ( strArray[ strArray.length - 1 ] == blockStartTag )
303                        {
304                                // A div tag must have some contents inside for it to be visible.
305                                strArray.push( "&nbsp;" ) ;
306                        }
307                        strArray.push( blockEndTag ) ;
308                }
309                strArray.push( blockStartTag ) ;
310                closeState = 1 ;
311        }
312        if ( closeState )
313                strArray.push( blockEndTag ) ;
314}
315
316FCKTools._ProcessLineBreaksForBrMode = function( oEditor, text, liState, node, strArray )
317{
318        var closeState = 0 ;
319        var blockStartTag = "<br />" ;
320        var blockEndTag = "" ;
321        if ( liState )
322        {
323                blockStartTag = "<li>" ;
324                blockEndTag = "</li>" ;
325                closeState = 1 ;
326        }
327
328        for ( var i = 0 ; i < text.length ; i++ )
329        {
330                var c = text.charAt( i ) ;
331                if ( c == '\r' )
332                        continue ;
333
334                if ( c != '\n' )
335                {
336                        strArray.push( c ) ;
337                        continue ;
338                }
339
340                if ( closeState && blockEndTag.length )
341                        strArray.push ( blockEndTag ) ;
342                strArray.push( blockStartTag ) ;
343                closeState = 1 ;
344        }
345}
346
347FCKTools.ProcessLineBreaks = function( oEditor, oConfig, text )
348{
349        var enterMode = oConfig.EnterMode.toLowerCase() ;
350        var strArray = [] ;
351
352        // Is the caret or selection inside an <li> tag now?
353        var liState = 0 ;
354        var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
355        range.MoveToSelection() ;
356        var node = range._Range.startContainer ;
357        while ( node && node.nodeType != 1 )
358                node = node.parentNode ;
359        if ( node && node.tagName.toLowerCase() == 'li' )
360                liState = 1 ;
361
362        if ( enterMode == 'p' )
363                this._ProcessLineBreaksForPMode( oEditor, text, liState, node, strArray ) ;
364        else if ( enterMode == 'div' )
365                this._ProcessLineBreaksForDivMode( oEditor, text, liState, node, strArray ) ;
366        else if ( enterMode == 'br' )
367                this._ProcessLineBreaksForBrMode( oEditor, text, liState, node, strArray ) ;
368        return strArray.join( "" ) ;
369}
370
371/**
372 * Adds an option to a SELECT element.
373 */
374FCKTools.AddSelectOption = function( selectElement, optionText, optionValue )
375{
376        var oOption = FCKTools.GetElementDocument( selectElement ).createElement( "OPTION" ) ;
377
378        oOption.text    = optionText ;
379        oOption.value   = optionValue ;
380
381        selectElement.options.add(oOption) ;
382
383        return oOption ;
384}
385
386FCKTools.RunFunction = function( func, thisObject, paramsArray, timerWindow )
387{
388        if ( func )
389                this.SetTimeout( func, 0, thisObject, paramsArray, timerWindow ) ;
390}
391
392FCKTools.SetTimeout = function( func, milliseconds, thisObject, paramsArray, timerWindow )
393{
394        return ( timerWindow || window ).setTimeout(
395                function()
396                {
397                        if ( paramsArray )
398                                func.apply( thisObject, [].concat( paramsArray ) ) ;
399                        else
400                                func.apply( thisObject ) ;
401                },
402                milliseconds ) ;
403}
404
405FCKTools.SetInterval = function( func, milliseconds, thisObject, paramsArray, timerWindow )
406{
407        return ( timerWindow || window ).setInterval(
408                function()
409                {
410                        func.apply( thisObject, paramsArray || [] ) ;
411                },
412                milliseconds ) ;
413}
414
415FCKTools.ConvertStyleSizeToHtml = function( size )
416{
417        return size.EndsWith( '%' ) ? size : parseInt( size, 10 ) ;
418}
419
420FCKTools.ConvertHtmlSizeToStyle = function( size )
421{
422        return size.EndsWith( '%' ) ? size : ( size + 'px' ) ;
423}
424
425// START iCM MODIFICATIONS
426// Amended to accept a list of one or more ascensor tag names
427// Amended to check the element itself before working back up through the parent hierarchy
428FCKTools.GetElementAscensor = function( element, ascensorTagNames )
429{
430//      var e = element.parentNode ;
431        var e = element ;
432        var lstTags = "," + ascensorTagNames.toUpperCase() + "," ;
433
434        while ( e )
435        {
436                if ( lstTags.indexOf( "," + e.nodeName.toUpperCase() + "," ) != -1 )
437                        return e ;
438
439                e = e.parentNode ;
440        }
441        return null ;
442}
443// END iCM MODIFICATIONS
444
445FCKTools.CreateEventListener = function( func, params )
446{
447        var f = function()
448        {
449                var aAllParams = [] ;
450
451                for ( var i = 0 ; i < arguments.length ; i++ )
452                        aAllParams.push( arguments[i] ) ;
453
454                func.apply( this, aAllParams.concat( params ) ) ;
455        }
456
457        return f ;
458}
459
460FCKTools.IsStrictMode = function( document )
461{
462        // There is no compatMode in Safari, but it seams that it always behave as
463        // CSS1Compat, so let's assume it as the default for that browser.
464        return ( 'CSS1Compat' == ( document.compatMode || ( FCKBrowserInfo.IsSafari ? 'CSS1Compat' : null ) ) ) ;
465}
466
467// Transforms a "arguments" object to an array.
468FCKTools.ArgumentsToArray = function( args, startIndex, maxLength )
469{
470        startIndex = startIndex || 0 ;
471        maxLength = maxLength || args.length ;
472
473        var argsArray = new Array() ;
474
475        for ( var i = startIndex ; i < startIndex + maxLength && i < args.length ; i++ )
476                argsArray.push( args[i] ) ;
477
478        return argsArray ;
479}
480
481FCKTools.CloneObject = function( sourceObject )
482{
483        var fCloneCreator = function() {} ;
484        fCloneCreator.prototype = sourceObject ;
485        return new fCloneCreator ;
486}
487
488// Appends a bogus <br> at the end of the element, if not yet available.
489FCKTools.AppendBogusBr = function( element )
490{
491        if ( !element )
492                return ;
493
494        var eLastChild = this.GetLastItem( element.getElementsByTagName('br') ) ;
495
496        if ( !eLastChild || ( eLastChild.getAttribute( 'type', 2 ) != '_moz' && eLastChild.getAttribute( '_moz_dirty' ) == null ) )
497        {
498                var doc = this.GetElementDocument( element ) ;
499
500                if ( FCKBrowserInfo.IsOpera )
501                        element.appendChild( doc.createTextNode('') ) ;
502                else
503                        element.appendChild( this.CreateBogusBR( doc ) ) ;
504        }
505}
506
507FCKTools.GetLastItem = function( list )
508{
509        if ( list.length > 0 )
510                return list[ list.length - 1 ] ;
511
512        return null ;
513}
514
515FCKTools.GetDocumentPosition = function( w, node )
516{
517        var x = 0 ;
518        var y = 0 ;
519        var curNode = node ;
520        var prevNode = null ;
521        var curWindow = FCKTools.GetElementWindow( curNode ) ;
522        while ( curNode && !( curWindow == w && ( curNode == w.document.body || curNode == w.document.documentElement ) ) )
523        {
524                x += curNode.offsetLeft - curNode.scrollLeft ;
525                y += curNode.offsetTop - curNode.scrollTop ;
526
527                if ( ! FCKBrowserInfo.IsOpera )
528                {
529                        var scrollNode = prevNode ;
530                        while ( scrollNode && scrollNode != curNode )
531                        {
532                                x -= scrollNode.scrollLeft ;
533                                y -= scrollNode.scrollTop ;
534                                scrollNode = scrollNode.parentNode ;
535                        }
536                }
537
538                prevNode = curNode ;
539                if ( curNode.offsetParent )
540                        curNode = curNode.offsetParent ;
541                else
542                {
543                        if ( curWindow != w )
544                        {
545                                curNode = curWindow.frameElement ;
546                                prevNode = null ;
547                                if ( curNode )
548                                        curWindow = curNode.contentWindow.parent ;
549                        }
550                        else
551                                curNode = null ;
552                }
553        }
554
555        // document.body is a special case when it comes to offsetTop and offsetLeft values.
556        // 1. It matters if document.body itself is a positioned element;
557        // 2. It matters is when we're in IE and the element has no positioned ancestor.
558        // Otherwise the values should be ignored.
559        if ( FCKDomTools.GetCurrentElementStyle( w.document.body, 'position') != 'static'
560                        || ( FCKBrowserInfo.IsIE && FCKDomTools.GetPositionedAncestor( node ) == null ) )
561        {
562                x += w.document.body.offsetLeft ;
563                y += w.document.body.offsetTop ;
564        }
565
566        return { "x" : x, "y" : y } ;
567}
568
569FCKTools.GetWindowPosition = function( w, node )
570{
571        var pos = this.GetDocumentPosition( w, node ) ;
572        var scroll = FCKTools.GetScrollPosition( w ) ;
573        pos.x -= scroll.X ;
574        pos.y -= scroll.Y ;
575        return pos ;
576}
577
578FCKTools.ProtectFormStyles = function( formNode )
579{
580        if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
581                return [] ;
582        var hijackRecord = [] ;
583        var hijackNames = [ 'style', 'className' ] ;
584        for ( var i = 0 ; i < hijackNames.length ; i++ )
585        {
586                var name = hijackNames[i] ;
587                if ( formNode.elements.namedItem( name ) )
588                {
589                        var hijackNode = formNode.elements.namedItem( name ) ;
590                        hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ) ;
591                        formNode.removeChild( hijackNode ) ;
592                }
593        }
594        return hijackRecord ;
595}
596
597FCKTools.RestoreFormStyles = function( formNode, hijackRecord )
598{
599        if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
600                return ;
601        if ( hijackRecord.length > 0 )
602        {
603                for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- )
604                {
605                        var node = hijackRecord[i][0] ;
606                        var sibling = hijackRecord[i][1] ;
607                        if ( sibling )
608                                formNode.insertBefore( node, sibling ) ;
609                        else
610                                formNode.appendChild( node ) ;
611                }
612        }
613}
614
615// Perform a one-step DFS walk.
616FCKTools.GetNextNode = function( node, limitNode )
617{
618        if ( node.firstChild )
619                return node.firstChild ;
620        else if ( node.nextSibling )
621                return node.nextSibling ;
622        else
623        {
624                var ancestor = node.parentNode ;
625                while ( ancestor )
626                {
627                        if ( ancestor == limitNode )
628                                return null ;
629                        if ( ancestor.nextSibling )
630                                return ancestor.nextSibling ;
631                        else
632                                ancestor = ancestor.parentNode ;
633                }
634        }
635        return null ;
636}
637
638FCKTools.GetNextTextNode = function( textnode, limitNode, checkStop )
639{
640        node = this.GetNextNode( textnode, limitNode ) ;
641        if ( checkStop && node && checkStop( node ) )
642                return null ;
643        while ( node && node.nodeType != 3 )
644        {
645                node = this.GetNextNode( node, limitNode ) ;
646                if ( checkStop && node && checkStop( node ) )
647                        return null ;
648        }
649        return node ;
650}
651
652/**
653 * Merge all objects passed by argument into a single object.
654 */
655FCKTools.Merge = function()
656{
657        var args = arguments ;
658        var o = args[0] ;
659
660        for ( var i = 1 ; i < args.length ; i++ )
661        {
662                var arg = args[i] ;
663                for ( var p in arg )
664                        o[p] = arg[p] ;
665        }
666
667        return o ;
668}
669
670/**
671 * Check if the passed argument is a real Array. It may not working when
672 * calling it cross windows.
673 */
674FCKTools.IsArray = function( it )
675{
676        return ( it instanceof Array ) ;
677}
678
679/**
680 * Appends a "length" property to an object, containing the number of
681 * properties available on it, excluded the append property itself.
682 */
683FCKTools.AppendLengthProperty = function( targetObject, propertyName )
684{
685        var counter = 0 ;
686
687        for ( var n in targetObject )
688                counter++ ;
689
690        return targetObject[ propertyName || 'length' ] = counter ;
691}
692
693/**
694 * Gets the browser parsed version of a css text (style attribute value). On
695 * some cases, the browser makes changes to the css text, returning a different
696 * value. For example, hexadecimal colors get transformed to rgb().
697 */
698FCKTools.NormalizeCssText = function( unparsedCssText )
699{
700        // Injects the style in a temporary span object, so the browser parses it,
701        // retrieving its final format.
702        var tempSpan = document.createElement( 'span' ) ;
703        tempSpan.style.cssText = unparsedCssText ;
704        return tempSpan.style.cssText ;
705}
706
707/**
708 * Binding the "this" reference to an object for a function.
709 */
710FCKTools.Bind = function( subject, func )
711{
712  return function(){ return func.apply( subject, arguments ) ; } ;
713}
714
715/**
716 * Retrieve the correct "empty iframe" URL for the current browser, which
717 * causes the minimum fuzz (e.g. security warnings in HTTPS, DNS error in
718 * IE5.5, etc.) for that browser, making the iframe ready to DOM use whithout
719 * having to loading an external file.
720 */
721FCKTools.GetVoidUrl = function()
722{
723        if ( FCK_IS_CUSTOM_DOMAIN )
724                return "javascript: void( function(){" +
725                        "document.open();" +
726                        "document.write('<html><head><title></title></head><body></body></html>');" +
727                        "document.domain = '" + FCK_RUNTIME_DOMAIN + "';" +
728                        "document.close();" +
729                        "}() ) ;";
730
731        if ( FCKBrowserInfo.IsIE )
732        {
733                if ( FCKBrowserInfo.IsIE7 || !FCKBrowserInfo.IsIE6 )
734                        return "" ;                                     // IE7+ / IE5.5
735                else
736                        return "javascript: '';" ;      // IE6+
737        }
738
739        return "javascript: void(0);" ;         // All other browsers.
740}
741
742FCKTools.ResetStyles = function( element )
743{
744        element.style.cssText = 'margin:0;' +
745                'padding:0;' +
746                'border:0;' +
747                'background-color:transparent;' +
748                'background-image:none;' ;
749}
Note: See TracBrowser for help on using the repository browser.