forked from science-ation/science-ation
328 lines
10 KiB
JavaScript
328 lines
10 KiB
JavaScript
/*
|
|
* FCKeditor - The text editor for Internet - http://www.fckeditor.net
|
|
* Copyright (C) 2003-2008 Frederico Caldeira Knabben
|
|
*
|
|
* == BEGIN LICENSE ==
|
|
*
|
|
* Licensed under the terms of any of the following licenses at your
|
|
* choice:
|
|
*
|
|
* - GNU General Public License Version 2 or later (the "GPL")
|
|
* http://www.gnu.org/licenses/gpl.html
|
|
*
|
|
* - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
|
|
* http://www.gnu.org/licenses/lgpl.html
|
|
*
|
|
* - Mozilla Public License Version 1.1 or later (the "MPL")
|
|
* http://www.mozilla.org/MPL/MPL-1.1.html
|
|
*
|
|
* == END LICENSE ==
|
|
*
|
|
* This class can be used to interate through nodes inside a range.
|
|
*
|
|
* During interation, the provided range can become invalid, due to document
|
|
* mutations, so CreateBookmark() used to restore it after processing, if
|
|
* needed.
|
|
*/
|
|
|
|
var FCKDomRangeIterator = function( range )
|
|
{
|
|
/**
|
|
* The FCKDomRange object that marks the interation boundaries.
|
|
*/
|
|
this.Range = range ;
|
|
|
|
/**
|
|
* Indicates that <br> elements must be used as paragraph boundaries.
|
|
*/
|
|
this.ForceBrBreak = false ;
|
|
|
|
/**
|
|
* Guarantees that the iterator will always return "real" block elements.
|
|
* If "false", elements like <li>, <th> and <td> are returned. If "true", a
|
|
* dedicated block element block element will be created inside those
|
|
* elements to hold the selected content.
|
|
*/
|
|
this.EnforceRealBlocks = false ;
|
|
}
|
|
|
|
FCKDomRangeIterator.CreateFromSelection = function( targetWindow )
|
|
{
|
|
var range = new FCKDomRange( targetWindow ) ;
|
|
range.MoveToSelection() ;
|
|
return new FCKDomRangeIterator( range ) ;
|
|
}
|
|
|
|
FCKDomRangeIterator.prototype =
|
|
{
|
|
/**
|
|
* Get the next paragraph element. It automatically breaks the document
|
|
* when necessary to generate block elements for the paragraphs.
|
|
*/
|
|
GetNextParagraph : function()
|
|
{
|
|
// The block element to be returned.
|
|
var block ;
|
|
|
|
// The range object used to identify the paragraph contents.
|
|
var range ;
|
|
|
|
// Indicated that the current element in the loop is the last one.
|
|
var isLast ;
|
|
|
|
// Instructs to cleanup remaining BRs.
|
|
var removePreviousBr ;
|
|
var removeLastBr ;
|
|
|
|
var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;
|
|
|
|
// This is the first iteration. Let's initialize it.
|
|
if ( !this._LastNode )
|
|
{
|
|
var range = this.Range.Clone() ;
|
|
range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;
|
|
|
|
this._NextNode = range.GetTouchedStartNode() ;
|
|
this._LastNode = range.GetTouchedEndNode() ;
|
|
|
|
// Let's reuse this variable.
|
|
range = null ;
|
|
}
|
|
|
|
var currentNode = this._NextNode ;
|
|
var lastNode = this._LastNode ;
|
|
|
|
this._NextNode = null ;
|
|
|
|
while ( currentNode )
|
|
{
|
|
// closeRange indicates that a paragraph boundary has been found,
|
|
// so the range can be closed.
|
|
var closeRange = false ;
|
|
|
|
// includeNode indicates that the current node is good to be part
|
|
// of the range. By default, any non-element node is ok for it.
|
|
var includeNode = ( currentNode.nodeType != 1 ) ;
|
|
|
|
var continueFromSibling = false ;
|
|
|
|
// If it is an element node, let's check if it can be part of the
|
|
// range.
|
|
if ( !includeNode )
|
|
{
|
|
var nodeName = currentNode.nodeName.toLowerCase() ;
|
|
|
|
if ( boundarySet[ nodeName ] && ( !FCKBrowserInfo.IsIE || currentNode.scopeName == 'HTML' ) )
|
|
{
|
|
// <br> boundaries must be part of the range. It will
|
|
// happen only if ForceBrBreak.
|
|
if ( nodeName == 'br' )
|
|
includeNode = true ;
|
|
else if ( !range && currentNode.childNodes.length == 0 && nodeName != 'hr' )
|
|
{
|
|
// If we have found an empty block, and haven't started
|
|
// the range yet, it means we must return this block.
|
|
block = currentNode ;
|
|
isLast = currentNode == lastNode ;
|
|
break ;
|
|
}
|
|
|
|
// The range must finish right before the boundary,
|
|
// including possibly skipped empty spaces. (#1603)
|
|
if ( range )
|
|
{
|
|
range.SetEnd( currentNode, 3, true ) ;
|
|
|
|
// The found boundary must be set as the next one at this
|
|
// point. (#1717)
|
|
if ( nodeName != 'br' )
|
|
this._NextNode = FCKDomTools.GetNextSourceNode( currentNode, true, null, lastNode ) ;
|
|
}
|
|
|
|
closeRange = true ;
|
|
}
|
|
else
|
|
{
|
|
// If we have child nodes, let's check them.
|
|
if ( currentNode.firstChild )
|
|
{
|
|
// If we don't have a range yet, let's start it.
|
|
if ( !range )
|
|
{
|
|
range = new FCKDomRange( this.Range.Window ) ;
|
|
range.SetStart( currentNode, 3, true ) ;
|
|
}
|
|
|
|
currentNode = currentNode.firstChild ;
|
|
continue ;
|
|
}
|
|
includeNode = true ;
|
|
}
|
|
}
|
|
else if ( currentNode.nodeType == 3 )
|
|
{
|
|
// Ignore normal whitespaces (i.e. not including or
|
|
// other unicode whitespaces) before/after a block node.
|
|
if ( /^[\r\n\t ]+$/.test( currentNode.nodeValue ) )
|
|
includeNode = false ;
|
|
}
|
|
|
|
// The current node is good to be part of the range and we are
|
|
// starting a new range, initialize it first.
|
|
if ( includeNode && !range )
|
|
{
|
|
range = new FCKDomRange( this.Range.Window ) ;
|
|
range.SetStart( currentNode, 3, true ) ;
|
|
}
|
|
|
|
// The last node has been found.
|
|
isLast = ( ( !closeRange || includeNode ) && currentNode == lastNode ) ;
|
|
// isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;
|
|
|
|
// If we are in an element boundary, let's check if it is time
|
|
// to close the range, otherwise we include the parent within it.
|
|
if ( range && !closeRange )
|
|
{
|
|
while ( !currentNode.nextSibling && !isLast )
|
|
{
|
|
var parentNode = currentNode.parentNode ;
|
|
|
|
if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )
|
|
{
|
|
closeRange = true ;
|
|
isLast = isLast || ( parentNode == lastNode ) ;
|
|
break ;
|
|
}
|
|
|
|
currentNode = parentNode ;
|
|
includeNode = true ;
|
|
isLast = ( currentNode == lastNode ) ;
|
|
continueFromSibling = true ;
|
|
}
|
|
}
|
|
|
|
// Now finally include the node.
|
|
if ( includeNode )
|
|
range.SetEnd( currentNode, 4, true ) ;
|
|
|
|
// We have found a block boundary. Let's close the range and move out of the
|
|
// loop.
|
|
if ( ( closeRange || isLast ) && range )
|
|
{
|
|
range._UpdateElementInfo() ;
|
|
|
|
if ( range.StartNode == range.EndNode
|
|
&& range.StartNode.parentNode == range.StartBlockLimit
|
|
&& range.StartNode.getAttribute && range.StartNode.getAttribute( '_fck_bookmark' ) )
|
|
range = null ;
|
|
else
|
|
break ;
|
|
}
|
|
|
|
if ( isLast )
|
|
break ;
|
|
|
|
currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;
|
|
}
|
|
|
|
// Now, based on the processed range, look for (or create) the block to be returned.
|
|
if ( !block )
|
|
{
|
|
// If no range has been found, this is the end.
|
|
if ( !range )
|
|
{
|
|
this._NextNode = null ;
|
|
return null ;
|
|
}
|
|
|
|
block = range.StartBlock ;
|
|
|
|
if ( !block
|
|
&& !this.EnforceRealBlocks
|
|
&& range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )
|
|
&& range.CheckStartOfBlock()
|
|
&& range.CheckEndOfBlock() )
|
|
{
|
|
block = range.StartBlockLimit ;
|
|
}
|
|
else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )
|
|
{
|
|
// Create the fixed block.
|
|
block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;
|
|
|
|
// Move the contents of the temporary range to the fixed block.
|
|
range.ExtractContents().AppendTo( block ) ;
|
|
FCKDomTools.TrimNode( block ) ;
|
|
|
|
// Insert the fixed block into the DOM.
|
|
range.InsertNode( block ) ;
|
|
|
|
removePreviousBr = true ;
|
|
removeLastBr = true ;
|
|
}
|
|
else if ( block.nodeName.toLowerCase() != 'li' )
|
|
{
|
|
// If the range doesn't includes the entire contents of the
|
|
// block, we must split it, isolating the range in a dedicated
|
|
// block.
|
|
if ( !range.CheckStartOfBlock() || !range.CheckEndOfBlock() )
|
|
{
|
|
// The resulting block will be a clone of the current one.
|
|
block = block.cloneNode( false ) ;
|
|
|
|
// Extract the range contents, moving it to the new block.
|
|
range.ExtractContents().AppendTo( block ) ;
|
|
FCKDomTools.TrimNode( block ) ;
|
|
|
|
// Split the block. At this point, the range will be in the
|
|
// right position for our intents.
|
|
var splitInfo = range.SplitBlock() ;
|
|
|
|
removePreviousBr = !splitInfo.WasStartOfBlock ;
|
|
removeLastBr = !splitInfo.WasEndOfBlock ;
|
|
|
|
// Insert the new block into the DOM.
|
|
range.InsertNode( block ) ;
|
|
}
|
|
}
|
|
else if ( !isLast )
|
|
{
|
|
// LIs are returned as is, with all their children (due to the
|
|
// nested lists). But, the next node is the node right after
|
|
// the current range, which could be an <li> child (nested
|
|
// lists) or the next sibling <li>.
|
|
|
|
this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;
|
|
return block ;
|
|
}
|
|
}
|
|
|
|
if ( removePreviousBr )
|
|
{
|
|
var previousSibling = block.previousSibling ;
|
|
if ( previousSibling && previousSibling.nodeType == 1 )
|
|
{
|
|
if ( previousSibling.nodeName.toLowerCase() == 'br' )
|
|
previousSibling.parentNode.removeChild( previousSibling ) ;
|
|
else if ( previousSibling.lastChild && previousSibling.lastChild.nodeName.IEquals( 'br' ) )
|
|
previousSibling.removeChild( previousSibling.lastChild ) ;
|
|
}
|
|
}
|
|
|
|
if ( removeLastBr )
|
|
{
|
|
var lastChild = block.lastChild ;
|
|
if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )
|
|
block.removeChild( lastChild ) ;
|
|
}
|
|
|
|
// Get a reference for the next element. This is important because the
|
|
// above block can be removed or changed, so we can rely on it for the
|
|
// next interation.
|
|
if ( !this._NextNode )
|
|
this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ;
|
|
|
|
return block ;
|
|
}
|
|
} ;
|