/** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ ( function() { /** * Represents a list os CKEDITOR.dom.range objects, which can be easily * iterated sequentially. * * @class * @extends Array * @constructor Creates a rangeList class instance. * @param {CKEDITOR.dom.range/CKEDITOR.dom.range[]} [ranges] The ranges contained on this list. * Note that, if an array of ranges is specified, the range sequence * should match its DOM order. This class will not help to sort them. */ CKEDITOR.dom.rangeList = function( ranges ) { if ( ranges instanceof CKEDITOR.dom.rangeList ) return ranges; if ( !ranges ) ranges = []; else if ( ranges instanceof CKEDITOR.dom.range ) ranges = [ ranges ]; return CKEDITOR.tools.extend( ranges, mixins ); }; var mixins = { /** * Creates an instance of the rangeList iterator, it should be used * only when the ranges processing could be DOM intrusive, which * means it may pollute and break other ranges in this list. * Otherwise, it's enough to just iterate over this array in a for loop. * * @returns {CKEDITOR.dom.rangeListIterator} */ createIterator: function() { var rangeList = this, bookmark = CKEDITOR.dom.walker.bookmark(), bookmarks = [], current; return { /** * Retrieves the next range in the list. * * @member CKEDITOR.dom.rangeListIterator * @param {Boolean} [mergeConsequent=false] Whether join two adjacent * ranges into single, e.g. consequent table cells. */ getNextRange: function( mergeConsequent ) { current = current === undefined ? 0 : current + 1; var range = rangeList[ current ]; // Multiple ranges might be mangled by each other. if ( range && rangeList.length > 1 ) { // Bookmarking all other ranges on the first iteration, // the range correctness after it doesn't matter since we'll // restore them before the next iteration. if ( !current ) { // Make sure bookmark correctness by reverse processing. for ( var i = rangeList.length - 1; i >= 0; i-- ) bookmarks.unshift( rangeList[ i ].createBookmark( true ) ); } if ( mergeConsequent ) { // Figure out how many ranges should be merged. var mergeCount = 0; while ( rangeList[ current + mergeCount + 1 ] ) { var doc = range.document, found = 0, left = doc.getById( bookmarks[ mergeCount ].endNode ), right = doc.getById( bookmarks[ mergeCount + 1 ].startNode ), next; // Check subsequent range. while ( 1 ) { next = left.getNextSourceNode( false ); if ( !right.equals( next ) ) { // This could be yet another bookmark or // walking across block boundaries. if ( bookmark( next ) || ( next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() ) ) { left = next; continue; } } else { found = 1; } break; } if ( !found ) break; mergeCount++; } } range.moveToBookmark( bookmarks.shift() ); // Merge ranges finally after moving to bookmarks. while ( mergeCount-- ) { next = rangeList[ ++current ]; next.moveToBookmark( bookmarks.shift() ); range.setEnd( next.endContainer, next.endOffset ); } } return range; } }; }, /** * Create bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark}. * * @param {Boolean} [serializable=false] See {@link CKEDITOR.dom.range#createBookmark}. * @returns {Array} Array of bookmarks. */ createBookmarks: function( serializable ) { var retval = [], bookmark; for ( var i = 0; i < this.length; i++ ) { retval.push( bookmark = this[ i ].createBookmark( serializable, true ) ); // Updating the container & offset values for ranges // that have been touched. for ( var j = i + 1; j < this.length; j++ ) { this[ j ] = updateDirtyRange( bookmark, this[ j ] ); this[ j ] = updateDirtyRange( bookmark, this[ j ], true ); } } return retval; }, /** * Create "unobtrusive" bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark2}. * * @param {Boolean} [normalized=false] See {@link CKEDITOR.dom.range#createBookmark2}. * @returns {Array} Array of bookmarks. */ createBookmarks2: function( normalized ) { var bookmarks = []; for ( var i = 0; i < this.length; i++ ) bookmarks.push( this[ i ].createBookmark2( normalized ) ); return bookmarks; }, /** * Move each range in the list to the position specified by a list of bookmarks. * * @param {Array} bookmarks The list of bookmarks, each one matching a range in the list. */ moveToBookmarks: function( bookmarks ) { for ( var i = 0; i < this.length; i++ ) this[ i ].moveToBookmark( bookmarks[ i ] ); } }; // Update the specified range which has been mangled by previous insertion of // range bookmark nodes.(http://dev.ckeditor.com/ticket/3256) function updateDirtyRange( bookmark, dirtyRange, checkEnd ) { var serializable = bookmark.serializable, container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ], offset = checkEnd ? 'endOffset' : 'startOffset'; var bookmarkStart = serializable ? dirtyRange.document.getById( bookmark.startNode ) : bookmark.startNode; var bookmarkEnd = serializable ? dirtyRange.document.getById( bookmark.endNode ) : bookmark.endNode; if ( container.equals( bookmarkStart.getPrevious() ) ) { dirtyRange.startOffset = dirtyRange.startOffset - container.getLength() - bookmarkEnd.getPrevious().getLength(); container = bookmarkEnd.getNext(); } else if ( container.equals( bookmarkEnd.getPrevious() ) ) { dirtyRange.startOffset = dirtyRange.startOffset - container.getLength(); container = bookmarkEnd.getNext(); } container.equals( bookmarkStart.getParent() ) && dirtyRange[ offset ]++; container.equals( bookmarkEnd.getParent() ) && dirtyRange[ offset ]++; // Update and return this range. dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ] = container; return dirtyRange; } } )(); /** * (Virtual Class) Do not call this constructor. This class is not really part * of the API. It just describes the return type of {@link CKEDITOR.dom.rangeList#createIterator}. * * @class CKEDITOR.dom.rangeListIterator */