source: sandbox/filemanager/tp/fckeditor/editor/_source/classes/fckdomrange.js @ 1575

Revision 1575, 27.3 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Implentação, melhorias do modulo gerenciador de arquivos

  • Property svn:executable set to *
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 * Class for working with a selection range, much like the W3C DOM Range, but
22 * it is not intended to be an implementation of the W3C interface.
23 */
24
25var FCKDomRange = function( sourceWindow )
26{
27        this.Window = sourceWindow ;
28        this._Cache = {} ;
29}
30
31FCKDomRange.prototype =
32{
33
34        _UpdateElementInfo : function()
35        {
36                var innerRange = this._Range ;
37
38                if ( !innerRange )
39                        this.Release( true ) ;
40                else
41                {
42                        // For text nodes, the node itself is the StartNode.
43                        var eStart      = innerRange.startContainer ;
44
45                        var oElementPath = new FCKElementPath( eStart ) ;
46                        this.StartNode                  = eStart.nodeType == 3 ? eStart : eStart.childNodes[ innerRange.startOffset ] ;
47                        this.StartContainer             = eStart ;
48                        this.StartBlock                 = oElementPath.Block ;
49                        this.StartBlockLimit    = oElementPath.BlockLimit ;
50
51                        if ( innerRange.collapsed )
52                        {
53                                this.EndNode            = this.StartNode ;
54                                this.EndContainer       = this.StartContainer ;
55                                this.EndBlock           = this.StartBlock ;
56                                this.EndBlockLimit      = this.StartBlockLimit ;
57                        }
58                        else
59                        {
60                                var eEnd        = innerRange.endContainer ;
61
62                                if ( eStart != eEnd )
63                                        oElementPath = new FCKElementPath( eEnd ) ;
64
65                                // The innerRange.endContainer[ innerRange.endOffset ] is not
66                                // usually part of the range, but the marker for the range end. So,
67                                // let's get the previous available node as the real end.
68                                var eEndNode = eEnd ;
69                                if ( innerRange.endOffset == 0 )
70                                {
71                                        while ( eEndNode && !eEndNode.previousSibling )
72                                                eEndNode = eEndNode.parentNode ;
73
74                                        if ( eEndNode )
75                                                eEndNode = eEndNode.previousSibling ;
76                                }
77                                else if ( eEndNode.nodeType == 1 )
78                                        eEndNode = eEndNode.childNodes[ innerRange.endOffset - 1 ] ;
79
80                                this.EndNode                    = eEndNode ;
81                                this.EndContainer               = eEnd ;
82                                this.EndBlock                   = oElementPath.Block ;
83                                this.EndBlockLimit              = oElementPath.BlockLimit ;
84                        }
85                }
86
87                this._Cache = {} ;
88        },
89
90        CreateRange : function()
91        {
92                return new FCKW3CRange( this.Window.document ) ;
93        },
94
95        DeleteContents : function()
96        {
97                if ( this._Range )
98                {
99                        this._Range.deleteContents() ;
100                        this._UpdateElementInfo() ;
101                }
102        },
103
104        ExtractContents : function()
105        {
106                if ( this._Range )
107                {
108                        var docFrag = this._Range.extractContents() ;
109                        this._UpdateElementInfo() ;
110                        return docFrag ;
111                }
112                return null ;
113        },
114
115        CheckIsCollapsed : function()
116        {
117                if ( this._Range )
118                        return this._Range.collapsed ;
119
120                return false ;
121        },
122
123        Collapse : function( toStart )
124        {
125                if ( this._Range )
126                        this._Range.collapse( toStart ) ;
127
128                this._UpdateElementInfo() ;
129        },
130
131        Clone : function()
132        {
133                var oClone = FCKTools.CloneObject( this ) ;
134
135                if ( this._Range )
136                        oClone._Range = this._Range.cloneRange() ;
137
138                return oClone ;
139        },
140
141        MoveToNodeContents : function( targetNode )
142        {
143                if ( !this._Range )
144                        this._Range = this.CreateRange() ;
145
146                this._Range.selectNodeContents( targetNode ) ;
147
148                this._UpdateElementInfo() ;
149        },
150
151        MoveToElementStart : function( targetElement )
152        {
153                this.SetStart(targetElement,1) ;
154                this.SetEnd(targetElement,1) ;
155        },
156
157        // Moves to the first editing point inside a element. For example, in a
158        // element tree like "<p><b><i></i></b> Text</p>", the start editing point
159        // is "<p><b><i>^</i></b> Text</p>" (inside <i>).
160        MoveToElementEditStart : function( targetElement )
161        {
162                var editableElement ;
163
164                while ( targetElement && targetElement.nodeType == 1 )
165                {
166                        if ( FCKDomTools.CheckIsEditable( targetElement ) )
167                                editableElement = targetElement ;
168                        else if ( editableElement )
169                                break ;         // If we already found an editable element, stop the loop.
170
171                        targetElement = targetElement.firstChild ;
172                }
173
174                if ( editableElement )
175                        this.MoveToElementStart( editableElement ) ;
176        },
177
178        InsertNode : function( node )
179        {
180                if ( this._Range )
181                        this._Range.insertNode( node ) ;
182        },
183
184        CheckIsEmpty : function()
185        {
186                if ( this.CheckIsCollapsed() )
187                        return true ;
188
189                // Inserts the contents of the range in a div tag.
190                var eToolDiv = this.Window.document.createElement( 'div' ) ;
191                this._Range.cloneContents().AppendTo( eToolDiv ) ;
192
193                FCKDomTools.TrimNode( eToolDiv ) ;
194
195                return ( eToolDiv.innerHTML.length == 0 ) ;
196        },
197
198        /**
199         * Checks if the start boundary of the current range is "visually" (like a
200         * selection caret) at the beginning of the block. It means that some
201         * things could be brefore the range, like spaces or empty inline elements,
202         * but it would still be considered at the beginning of the block.
203         */
204        CheckStartOfBlock : function()
205        {
206                var cache = this._Cache ;
207                var bIsStartOfBlock = cache.IsStartOfBlock ;
208
209                if ( bIsStartOfBlock != undefined )
210                        return bIsStartOfBlock ;
211
212                // Take the block reference.
213                var block = this.StartBlock || this.StartBlockLimit ;
214
215                var container   = this._Range.startContainer ;
216                var offset              = this._Range.startOffset ;
217                var currentNode ;
218
219                if ( offset > 0 )
220                {
221                        // First, check the start container. If it is a text node, get the
222                        // substring of the node value before the range offset.
223                        if ( container.nodeType == 3 )
224                        {
225                                var textValue = container.nodeValue.substr( 0, offset ).Trim() ;
226
227                                // If we have some text left in the container, we are not at
228                                // the end for the block.
229                                if ( textValue.length != 0 )
230                                        return cache.IsStartOfBlock = false ;
231                        }
232                        else
233                                currentNode = container.childNodes[ offset - 1 ] ;
234                }
235
236                // We'll not have a currentNode if the container was a text node, or
237                // the offset is zero.
238                if ( !currentNode )
239                        currentNode = FCKDomTools.GetPreviousSourceNode( container, true, null, block ) ;
240
241                while ( currentNode )
242                {
243                        switch ( currentNode.nodeType )
244                        {
245                                case 1 :
246                                        // It's not an inline element.
247                                        if ( !FCKListsLib.InlineChildReqElements[ currentNode.nodeName.toLowerCase() ] )
248                                                return cache.IsStartOfBlock = false ;
249
250                                        break ;
251
252                                case 3 :
253                                        // It's a text node with real text.
254                                        if ( currentNode.nodeValue.Trim().length > 0 )
255                                                return cache.IsStartOfBlock = false ;
256                        }
257
258                        currentNode = FCKDomTools.GetPreviousSourceNode( currentNode, false, null, block ) ;
259                }
260
261                return cache.IsStartOfBlock = true ;
262        },
263
264        /**
265         * Checks if the end boundary of the current range is "visually" (like a
266         * selection caret) at the end of the block. It means that some things
267         * could be after the range, like spaces, empty inline elements, or a
268         * single <br>, but it would still be considered at the end of the block.
269         */
270        CheckEndOfBlock : function( refreshSelection )
271        {
272                var isEndOfBlock = this._Cache.IsEndOfBlock ;
273
274                if ( isEndOfBlock != undefined )
275                        return isEndOfBlock ;
276
277                // Take the block reference.
278                var block = this.EndBlock || this.EndBlockLimit ;
279
280                var container   = this._Range.endContainer ;
281                var offset                      = this._Range.endOffset ;
282                var currentNode ;
283
284                // First, check the end container. If it is a text node, get the
285                // substring of the node value after the range offset.
286                if ( container.nodeType == 3 )
287                {
288                        var textValue = container.nodeValue ;
289                        if ( offset < textValue.length )
290                        {
291                                textValue = textValue.substr( offset ) ;
292
293                                // If we have some text left in the container, we are not at
294                                // the end for the block.
295                                if ( textValue.Trim().length != 0 )
296                                        return this._Cache.IsEndOfBlock = false ;
297                        }
298                }
299                else
300                        currentNode = container.childNodes[ offset ] ;
301
302                // We'll not have a currentNode if the container was a text node, of
303                // the offset is out the container children limits (after it probably).
304                if ( !currentNode )
305                        currentNode = FCKDomTools.GetNextSourceNode( container, true, null, block ) ;
306
307                var hadBr = false ;
308
309                while ( currentNode )
310                {
311                        switch ( currentNode.nodeType )
312                        {
313                                case 1 :
314                                        var nodeName = currentNode.nodeName.toLowerCase() ;
315
316                                        // It's an inline element.
317                                        if ( FCKListsLib.InlineChildReqElements[ nodeName ] )
318                                                break ;
319
320                                        // It is the first <br> found.
321                                        if ( nodeName == 'br' && !hadBr )
322                                        {
323                                                hadBr = true ;
324                                                break ;
325                                        }
326
327                                        return this._Cache.IsEndOfBlock = false ;
328
329                                case 3 :
330                                        // It's a text node with real text.
331                                        if ( currentNode.nodeValue.Trim().length > 0 )
332                                                return this._Cache.IsEndOfBlock = false ;
333                        }
334
335                        currentNode = FCKDomTools.GetNextSourceNode( currentNode, false, null, block ) ;
336                }
337
338                if ( refreshSelection )
339                        this.Select() ;
340
341                return this._Cache.IsEndOfBlock = true ;
342        },
343
344        // This is an "intrusive" way to create a bookmark. It includes <span> tags
345        // in the range boundaries. The advantage of it is that it is possible to
346        // handle DOM mutations when moving back to the bookmark.
347        // Attention: the inclusion of nodes in the DOM is a design choice and
348        // should not be changed as there are other points in the code that may be
349        // using those nodes to perform operations. See GetBookmarkNode.
350        // For performance, includeNodes=true if intended to SelectBookmark.
351        CreateBookmark : function( includeNodes )
352        {
353                // Create the bookmark info (random IDs).
354                var oBookmark =
355                {
356                        StartId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S',
357                        EndId   : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'
358                } ;
359
360                var oDoc = this.Window.document ;
361                var eStartSpan ;
362                var eEndSpan ;
363                var oClone ;
364
365                // For collapsed ranges, add just the start marker.
366                if ( !this.CheckIsCollapsed() )
367                {
368                        eEndSpan = oDoc.createElement( 'span' ) ;
369                        eEndSpan.style.display = 'none' ;
370                        eEndSpan.id = oBookmark.EndId ;
371                        eEndSpan.setAttribute( '_fck_bookmark', true ) ;
372
373                        // For IE, it must have something inside, otherwise it may be
374                        // removed during DOM operations.
375//                      if ( FCKBrowserInfo.IsIE )
376                                eEndSpan.innerHTML = '&nbsp;' ;
377
378                        oClone = this.Clone() ;
379                        oClone.Collapse( false ) ;
380                        oClone.InsertNode( eEndSpan ) ;
381                }
382
383                eStartSpan = oDoc.createElement( 'span' ) ;
384                eStartSpan.style.display = 'none' ;
385                eStartSpan.id = oBookmark.StartId ;
386                eStartSpan.setAttribute( '_fck_bookmark', true ) ;
387
388                // For IE, it must have something inside, otherwise it may be removed
389                // during DOM operations.
390//              if ( FCKBrowserInfo.IsIE )
391                        eStartSpan.innerHTML = '&nbsp;' ;
392
393                oClone = this.Clone() ;
394                oClone.Collapse( true ) ;
395                oClone.InsertNode( eStartSpan ) ;
396
397                if ( includeNodes )
398                {
399                        oBookmark.StartNode = eStartSpan ;
400                        oBookmark.EndNode = eEndSpan ;
401                }
402
403                // Update the range position.
404                if ( eEndSpan )
405                {
406                        this.SetStart( eStartSpan, 4 ) ;
407                        this.SetEnd( eEndSpan, 3 ) ;
408                }
409                else
410                        this.MoveToPosition( eStartSpan, 4 ) ;
411
412                return oBookmark ;
413        },
414
415        // This one should be a part of a hypothetic "bookmark" object.
416        GetBookmarkNode : function( bookmark, start )
417        {
418                var doc = this.Window.document ;
419
420                if ( start )
421                        return bookmark.StartNode || doc.getElementById( bookmark.StartId ) ;
422                else
423                        return bookmark.EndNode || doc.getElementById( bookmark.EndId ) ;
424        },
425
426        MoveToBookmark : function( bookmark, preserveBookmark )
427        {
428                var eStartSpan  = this.GetBookmarkNode( bookmark, true ) ;
429                var eEndSpan    = this.GetBookmarkNode( bookmark, false ) ;
430
431                this.SetStart( eStartSpan, 3 ) ;
432
433                if ( !preserveBookmark )
434                        FCKDomTools.RemoveNode( eStartSpan ) ;
435
436                // If collapsed, the end span will not be available.
437                if ( eEndSpan )
438                {
439                        this.SetEnd( eEndSpan, 3 ) ;
440
441                        if ( !preserveBookmark )
442                                FCKDomTools.RemoveNode( eEndSpan ) ;
443                }
444                else
445                        this.Collapse( true ) ;
446
447                this._UpdateElementInfo() ;
448        },
449
450        // Non-intrusive bookmark algorithm
451        CreateBookmark2 : function()
452        {
453                // If there is no range then get out of here.
454                // It happens on initial load in Safari #962 and if the editor it's hidden also in Firefox
455                if ( ! this._Range )
456                        return { "Start" : 0, "End" : 0 } ;
457
458                // First, we record down the offset values
459                var bookmark =
460                {
461                        "Start" : [ this._Range.startOffset ],
462                        "End" : [ this._Range.endOffset ]
463                } ;
464                // Since we're treating the document tree as normalized, we need to backtrack the text lengths
465                // of previous text nodes into the offset value.
466                var curStart = this._Range.startContainer.previousSibling ;
467                var curEnd = this._Range.endContainer.previousSibling ;
468
469                // Also note that the node that we use for "address base" would change during backtracking.
470                var addrStart = this._Range.startContainer ;
471                var addrEnd = this._Range.endContainer ;
472                while ( curStart && curStart.nodeType == 3 && addrStart.nodeType == 3 )
473                {
474                        bookmark.Start[0] += curStart.length ;
475                        addrStart = curStart ;
476                        curStart = curStart.previousSibling ;
477                }
478                while ( curEnd && curEnd.nodeType == 3 && addrEnd.nodeType == 3 )
479                {
480                        bookmark.End[0] += curEnd.length ;
481                        addrEnd = curEnd ;
482                        curEnd = curEnd.previousSibling ;
483                }
484
485                // If the object pointed to by the startOffset and endOffset are text nodes, we need
486                // to backtrack and add in the text offset to the bookmark addresses.
487                if ( addrStart.nodeType == 1 && addrStart.childNodes[bookmark.Start[0]] && addrStart.childNodes[bookmark.Start[0]].nodeType == 3 )
488                {
489                        var curNode = addrStart.childNodes[bookmark.Start[0]] ;
490                        var offset = 0 ;
491                        while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
492                        {
493                                curNode = curNode.previousSibling ;
494                                offset += curNode.length ;
495                        }
496                        addrStart = curNode ;
497                        bookmark.Start[0] = offset ;
498                }
499                if ( addrEnd.nodeType == 1 && addrEnd.childNodes[bookmark.End[0]] && addrEnd.childNodes[bookmark.End[0]].nodeType == 3 )
500                {
501                        var curNode = addrEnd.childNodes[bookmark.End[0]] ;
502                        var offset = 0 ;
503                        while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
504                        {
505                                curNode = curNode.previousSibling ;
506                                offset += curNode.length ;
507                        }
508                        addrEnd = curNode ;
509                        bookmark.End[0] = offset ;
510                }
511
512                // Then, we record down the precise position of the container nodes
513                // by walking up the DOM tree and counting their childNode index
514                bookmark.Start = FCKDomTools.GetNodeAddress( addrStart, true ).concat( bookmark.Start ) ;
515                bookmark.End = FCKDomTools.GetNodeAddress( addrEnd, true ).concat( bookmark.End ) ;
516                return bookmark;
517        },
518
519        MoveToBookmark2 : function( bookmark )
520        {
521                // Reverse the childNode counting algorithm in CreateBookmark2()
522                var curStart = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.Start.slice( 0, -1 ), true ) ;
523                var curEnd = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.End.slice( 0, -1 ), true ) ;
524
525                // Generate the W3C Range object and update relevant data
526                this.Release( true ) ;
527                this._Range = new FCKW3CRange( this.Window.document ) ;
528                var startOffset = bookmark.Start[ bookmark.Start.length - 1 ] ;
529                var endOffset = bookmark.End[ bookmark.End.length - 1 ] ;
530                while ( curStart.nodeType == 3 && startOffset > curStart.length )
531                {
532                        if ( ! curStart.nextSibling || curStart.nextSibling.nodeType != 3 )
533                                break ;
534                        startOffset -= curStart.length ;
535                        curStart = curStart.nextSibling ;
536                }
537                while ( curEnd.nodeType == 3 && endOffset > curEnd.length )
538                {
539                        if ( ! curEnd.nextSibling || curEnd.nextSibling.nodeType != 3 )
540                                break ;
541                        endOffset -= curEnd.length ;
542                        curEnd = curEnd.nextSibling ;
543                }
544                this._Range.setStart( curStart, startOffset ) ;
545                this._Range.setEnd( curEnd, endOffset ) ;
546                this._UpdateElementInfo() ;
547        },
548
549        MoveToPosition : function( targetElement, position )
550        {
551                this.SetStart( targetElement, position ) ;
552                this.Collapse( true ) ;
553        },
554
555        /*
556         * Moves the position of the start boundary of the range to a specific position
557         * relatively to a element.
558         *              @position:
559         *                      1 = After Start         <target>^contents</target>
560         *                      2 = Before End          <target>contents^</target>
561         *                      3 = Before Start        ^<target>contents</target>
562         *                      4 = After End           <target>contents</target>^
563         */
564        SetStart : function( targetElement, position, noInfoUpdate )
565        {
566                var oRange = this._Range ;
567                if ( !oRange )
568                        oRange = this._Range = this.CreateRange() ;
569
570                switch( position )
571                {
572                        case 1 :                // After Start          <target>^contents</target>
573                                oRange.setStart( targetElement, 0 ) ;
574                                break ;
575
576                        case 2 :                // Before End           <target>contents^</target>
577                                oRange.setStart( targetElement, targetElement.childNodes.length ) ;
578                                break ;
579
580                        case 3 :                // Before Start         ^<target>contents</target>
581                                oRange.setStartBefore( targetElement ) ;
582                                break ;
583
584                        case 4 :                // After End            <target>contents</target>^
585                                oRange.setStartAfter( targetElement ) ;
586                }
587
588                if ( !noInfoUpdate )
589                        this._UpdateElementInfo() ;
590        },
591
592        /*
593         * Moves the position of the start boundary of the range to a specific position
594         * relatively to a element.
595         *              @position:
596         *                      1 = After Start         <target>^contents</target>
597         *                      2 = Before End          <target>contents^</target>
598         *                      3 = Before Start        ^<target>contents</target>
599         *                      4 = After End           <target>contents</target>^
600         */
601        SetEnd : function( targetElement, position, noInfoUpdate )
602        {
603                var oRange = this._Range ;
604                if ( !oRange )
605                        oRange = this._Range = this.CreateRange() ;
606
607                switch( position )
608                {
609                        case 1 :                // After Start          <target>^contents</target>
610                                oRange.setEnd( targetElement, 0 ) ;
611                                break ;
612
613                        case 2 :                // Before End           <target>contents^</target>
614                                oRange.setEnd( targetElement, targetElement.childNodes.length ) ;
615                                break ;
616
617                        case 3 :                // Before Start         ^<target>contents</target>
618                                oRange.setEndBefore( targetElement ) ;
619                                break ;
620
621                        case 4 :                // After End            <target>contents</target>^
622                                oRange.setEndAfter( targetElement ) ;
623                }
624
625                if ( !noInfoUpdate )
626                        this._UpdateElementInfo() ;
627        },
628
629        Expand : function( unit )
630        {
631                var oNode, oSibling ;
632
633                switch ( unit )
634                {
635                        // Expand the range to include all inline parent elements if we are
636                        // are in their boundary limits.
637                        // For example (where [ ] are the range limits):
638                        //      Before =>               Some <b>[<i>Some sample text]</i></b>.
639                        //      After =>                Some [<b><i>Some sample text</i></b>].
640                        case 'inline_elements' :
641                                // Expand the start boundary.
642                                if ( this._Range.startOffset == 0 )
643                                {
644                                        oNode = this._Range.startContainer ;
645
646                                        if ( oNode.nodeType != 1 )
647                                                oNode = oNode.previousSibling ? null : oNode.parentNode ;
648
649                                        if ( oNode )
650                                        {
651                                                while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
652                                                {
653                                                        this._Range.setStartBefore( oNode ) ;
654
655                                                        if ( oNode != oNode.parentNode.firstChild )
656                                                                break ;
657
658                                                        oNode = oNode.parentNode ;
659                                                }
660                                        }
661                                }
662
663                                // Expand the end boundary.
664                                oNode = this._Range.endContainer ;
665                                var offset = this._Range.endOffset ;
666
667                                if ( ( oNode.nodeType == 3 && offset >= oNode.nodeValue.length ) || ( oNode.nodeType == 1 && offset >= oNode.childNodes.length ) || ( oNode.nodeType != 1 && oNode.nodeType != 3 ) )
668                                {
669                                        if ( oNode.nodeType != 1 )
670                                                oNode = oNode.nextSibling ? null : oNode.parentNode ;
671
672                                        if ( oNode )
673                                        {
674                                                while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
675                                                {
676                                                        this._Range.setEndAfter( oNode ) ;
677
678                                                        if ( oNode != oNode.parentNode.lastChild )
679                                                                break ;
680
681                                                        oNode = oNode.parentNode ;
682                                                }
683                                        }
684                                }
685
686                                break ;
687
688                        case 'block_contents' :
689                        case 'list_contents' :
690                                var boundarySet = FCKListsLib.BlockBoundaries ;
691                                if ( unit == 'list_contents' || FCKConfig.EnterMode == 'br' )
692                                        boundarySet = FCKListsLib.ListBoundaries ;
693
694                                if ( this.StartBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' )
695                                        this.SetStart( this.StartBlock, 1 ) ;
696                                else
697                                {
698                                        // Get the start node for the current range.
699                                        oNode = this._Range.startContainer ;
700
701                                        // If it is an element, get the node right before of it (in source order).
702                                        if ( oNode.nodeType == 1 )
703                                        {
704                                                var lastNode = oNode.childNodes[ this._Range.startOffset ] ;
705                                                if ( lastNode )
706                                                        oNode = FCKDomTools.GetPreviousSourceNode( lastNode, true ) ;
707                                                else
708                                                        oNode = oNode.lastChild || oNode ;
709                                        }
710
711                                        // We must look for the left boundary, relative to the range
712                                        // start, which is limited by a block element.
713                                        while ( oNode
714                                                        && ( oNode.nodeType != 1
715                                                                || ( oNode != this.StartBlockLimit
716                                                                        && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
717                                        {
718                                                this._Range.setStartBefore( oNode ) ;
719                                                oNode = oNode.previousSibling || oNode.parentNode ;
720                                        }
721                                }
722
723                                if ( this.EndBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' && this.EndBlock.nodeName.toLowerCase() != 'li' )
724                                        this.SetEnd( this.EndBlock, 2 ) ;
725                                else
726                                {
727                                        oNode = this._Range.endContainer ;
728                                        if ( oNode.nodeType == 1 )
729                                                oNode = oNode.childNodes[ this._Range.endOffset ] || oNode.lastChild ;
730
731                                        // We must look for the right boundary, relative to the range
732                                        // end, which is limited by a block element.
733                                        while ( oNode
734                                                        && ( oNode.nodeType != 1
735                                                                || ( oNode != this.StartBlockLimit
736                                                                        && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
737                                        {
738                                                this._Range.setEndAfter( oNode ) ;
739                                                oNode = oNode.nextSibling || oNode.parentNode ;
740                                        }
741
742                                        // In EnterMode='br', the end <br> boundary element must
743                                        // be included in the expanded range.
744                                        if ( oNode && oNode.nodeName.toLowerCase() == 'br' )
745                                                this._Range.setEndAfter( oNode ) ;
746                                }
747
748                                this._UpdateElementInfo() ;
749                }
750        },
751
752        /**
753         * Split the block element for the current range. It deletes the contents
754         * of the range and splits the block in the collapsed position, resulting
755         * in two sucessive blocks. The range is then positioned in the middle of
756         * them.
757         *
758         * It returns and object with the following properties:
759         *              - PreviousBlock : a reference to the block element that preceeds
760         *                the range after the split.
761         *              - NextBlock : a reference to the block element that follows the
762         *                range after the split.
763         *              - WasStartOfBlock : a boolean indicating that the range was
764         *                originaly at the start of the block.
765         *              - WasEndOfBlock : a boolean indicating that the range was originaly
766         *                at the end of the block.
767         *
768         * If the range was originaly at the start of the block, no split will happen
769         * and the PreviousBlock value will be null. The same is valid for the
770         * NextBlock value if the range was at the end of the block.
771         */
772        SplitBlock : function( forceBlockTag )
773        {
774                var blockTag = forceBlockTag || FCKConfig.EnterMode ;
775
776                if ( !this._Range )
777                        this.MoveToSelection() ;
778
779                // The range boundaries must be in the same "block limit" element.
780                if ( this.StartBlockLimit == this.EndBlockLimit )
781                {
782                        // Get the current blocks.
783                        var eStartBlock         = this.StartBlock ;
784                        var eEndBlock           = this.EndBlock ;
785                        var oElementPath        = null ;
786
787                        if ( blockTag != 'br' )
788                        {
789                                if ( !eStartBlock )
790                                {
791                                        eStartBlock = this.FixBlock( true, blockTag ) ;
792                                        eEndBlock       = this.EndBlock ;       // FixBlock may have fixed the EndBlock too.
793                                }
794
795                                if ( !eEndBlock )
796                                        eEndBlock = this.FixBlock( false, blockTag ) ;
797                        }
798
799                        // Get the range position.
800                        var bIsStartOfBlock     = ( eStartBlock != null && this.CheckStartOfBlock() ) ;
801                        var bIsEndOfBlock       = ( eEndBlock != null && this.CheckEndOfBlock() ) ;
802
803                        // Delete the current contents.
804                        if ( !this.CheckIsEmpty() )
805                                this.DeleteContents() ;
806
807                        if ( eStartBlock && eEndBlock && eStartBlock == eEndBlock )
808                        {
809                                if ( bIsEndOfBlock )
810                                {
811                                        oElementPath = new FCKElementPath( this.StartContainer ) ;
812                                        this.MoveToPosition( eEndBlock, 4 ) ;
813                                        eEndBlock = null ;
814                                }
815                                else if ( bIsStartOfBlock )
816                                {
817                                        oElementPath = new FCKElementPath( this.StartContainer ) ;
818                                        this.MoveToPosition( eStartBlock, 3 ) ;
819                                        eStartBlock = null ;
820                                }
821                                else
822                                {
823                                        // Extract the contents of the block from the selection point to the end of its contents.
824                                        this.SetEnd( eStartBlock, 2 ) ;
825                                        var eDocFrag = this.ExtractContents() ;
826
827                                        // Duplicate the block element after it.
828                                        eEndBlock = eStartBlock.cloneNode( false ) ;
829                                        eEndBlock.removeAttribute( 'id', false ) ;
830
831                                        // Place the extracted contents in the duplicated block.
832                                        eDocFrag.AppendTo( eEndBlock ) ;
833
834                                        FCKDomTools.InsertAfterNode( eStartBlock, eEndBlock ) ;
835
836                                        this.MoveToPosition( eStartBlock, 4 ) ;
837
838                                        // In Gecko, the last child node must be a bogus <br>.
839                                        // Note: bogus <br> added under <ul> or <ol> would cause lists to be incorrectly rendered.
840                                        if ( FCKBrowserInfo.IsGecko &&
841                                                        ! eStartBlock.nodeName.IEquals( ['ul', 'ol'] ) )
842                                                FCKTools.AppendBogusBr( eStartBlock ) ;
843                                }
844                        }
845
846                        return {
847                                PreviousBlock   : eStartBlock,
848                                NextBlock               : eEndBlock,
849                                WasStartOfBlock : bIsStartOfBlock,
850                                WasEndOfBlock   : bIsEndOfBlock,
851                                ElementPath             : oElementPath
852                        } ;
853                }
854
855                return null ;
856        },
857
858        // Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
859        FixBlock : function( isStart, blockTag )
860        {
861                // Bookmark the range so we can restore it later.
862                var oBookmark = this.CreateBookmark() ;
863
864                // Collapse the range to the requested ending boundary.
865                this.Collapse( isStart ) ;
866
867                // Expands it to the block contents.
868                this.Expand( 'block_contents' ) ;
869
870                // Create the fixed block.
871                var oFixedBlock = this.Window.document.createElement( blockTag ) ;
872
873                // Move the contents of the temporary range to the fixed block.
874                this.ExtractContents().AppendTo( oFixedBlock ) ;
875                FCKDomTools.TrimNode( oFixedBlock ) ;
876
877                // If the fixed block is empty (not counting bookmark nodes)
878                // Add a <br /> inside to expand it.
879                if ( FCKDomTools.CheckIsEmptyElement(oFixedBlock, function( element ) { return element.getAttribute('_fck_bookmark') != 'true' ; } )
880                                && FCKBrowserInfo.IsGeckoLike )
881                                FCKTools.AppendBogusBr( oFixedBlock ) ;
882
883                // Insert the fixed block into the DOM.
884                this.InsertNode( oFixedBlock ) ;
885
886                // Move the range back to the bookmarked place.
887                this.MoveToBookmark( oBookmark ) ;
888
889                return oFixedBlock ;
890        },
891
892        Release : function( preserveWindow )
893        {
894                if ( !preserveWindow )
895                        this.Window = null ;
896
897                this.StartNode = null ;
898                this.StartContainer = null ;
899                this.StartBlock = null ;
900                this.StartBlockLimit = null ;
901                this.EndNode = null ;
902                this.EndContainer = null ;
903                this.EndBlock = null ;
904                this.EndBlockLimit = null ;
905                this._Range = null ;
906                this._Cache = null ;
907        },
908
909        CheckHasRange : function()
910        {
911                return !!this._Range ;
912        },
913
914        GetTouchedStartNode : function()
915        {
916                var range = this._Range ;
917                var container = range.startContainer ;
918
919                if ( range.collapsed || container.nodeType != 1 )
920                        return container ;
921
922                return container.childNodes[ range.startOffset ] || container ;
923        },
924
925        GetTouchedEndNode : function()
926        {
927                var range = this._Range ;
928                var container = range.endContainer ;
929
930                if ( range.collapsed || container.nodeType != 1 )
931                        return container ;
932
933                return container.childNodes[ range.endOffset - 1 ] || container ;
934        }
935} ;
Note: See TracBrowser for help on using the repository browser.