All files / src/dm ve.dm.BranchNode.js

100% Statements 70/70
94.87% Branches 37/39
100% Functions 8/8
100% Lines 69/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220                                      1x   15684x     15684x     15684x     15684x 38x                                   1x   1x                       1x 1x 1x                   1x 1x 1x 1x 1x                       1x 1x 1x                   1x 1x 1x 1x 1x                         1x 16018x 16018x     16018x 16018x 1159x 1159x     16018x 13516x 13516x 19687x 19687x       16018x 16018x 16018x   16018x               1x 16588x   16588x   16588x   4552x                     12036x 12036x 12036x 12036x     29347x 769x       29347x     29347x   29347x 2417x 2417x   2417x 2415x       29347x 29347x                   1x 539x   539x 189x   350x 663x 663x 336x     14x    
/*!
 * VisualEditor DataModel BranchNode class.
 *
 * @copyright See AUTHORS.txt
 */
 
/**
 * DataModel branch node.
 *
 * Branch nodes can have branch or leaf nodes as children.
 *
 * @abstract
 * @extends ve.dm.Node
 * @mixins ve.BranchNode
 *
 * @constructor
 * @param {Object} [element] Reference to element in linear model
 * @param {ve.dm.Node[]} [children] Child nodes to attach
 */
ve.dm.BranchNode = function VeDmBranchNode( element, children ) {
	// Mixin constructor
	ve.BranchNode.call( this );
 
	// Parent constructor
	ve.dm.BranchNode.super.call( this, element );
 
	// Properties
	this.slugPositions = {};
 
	// TODO: children is only ever used in tests
	if ( Array.isArray( children ) && children.length ) {
		this.splice.apply( this, [ 0, 0 ].concat( children ) );
	}
};
 
/**
 * @event splice
 * @see #method-splice
 * @param {number} index
 * @param {number} howmany
 * @param {ve.dm.BranchNode} [childModel]
 */
 
/**
 * @event update
 */
 
/* Inheritance */
 
OO.inheritClass( ve.dm.BranchNode, ve.dm.Node );
 
OO.mixinClass( ve.dm.BranchNode, ve.BranchNode );
 
/* Methods */
 
/**
 * Add a child node to the end of the list.
 *
 * @param {ve.dm.BranchNode} childModel Item to add
 * @return {number} New number of children
 * @fires splice
 * @fires update
 */
ve.dm.BranchNode.prototype.push = function ( childModel ) {
	this.splice( this.children.length, 0, childModel );
	return this.children.length;
};
 
/**
 * Remove a child node from the end of the list.
 *
 * @return {ve.dm.BranchNode} Removed childModel
 * @fires splice
 * @fires update
 */
ve.dm.BranchNode.prototype.pop = function () {
	Eif ( this.children.length ) {
		var childModel = this.children[ this.children.length - 1 ];
		this.splice( this.children.length - 1, 1 );
		return childModel;
	}
};
 
/**
 * Add a child node to the beginning of the list.
 *
 * @param {ve.dm.BranchNode} childModel Item to add
 * @return {number} New number of children
 * @fires splice
 * @fires update
 */
ve.dm.BranchNode.prototype.unshift = function ( childModel ) {
	this.splice( 0, 0, childModel );
	return this.children.length;
};
 
/**
 * Remove a child node from the beginning of the list.
 *
 * @return {ve.dm.BranchNode} Removed childModel
 * @fires splice
 * @fires update
 */
ve.dm.BranchNode.prototype.shift = function () {
	Eif ( this.children.length ) {
		var childModel = this.children[ 0 ];
		this.splice( 0, 1 );
		return childModel;
	}
};
 
/**
 * Add and/or remove child nodes at an offset.
 *
 * @param {number} index Index to remove and or insert nodes at
 * @param {number} howmany Number of nodes to remove
 * @param {...ve.dm.BranchNode} [nodes] Variadic list of nodes to insert
 * @fires splice
 * @return {ve.dm.BranchNode[]} Removed nodes
 */
ve.dm.BranchNode.prototype.splice = function () {
	var args = Array.prototype.slice.call( arguments ),
		diff = 0;
 
	var i, length;
	var removals = this.children.splice.apply( this.children, args );
	for ( i = 0, length = removals.length; i < length; i++ ) {
		removals[ i ].detach();
		diff -= removals[ i ].getOuterLength();
	}
 
	if ( args.length >= 3 ) {
		length = args.length;
		for ( i = 2; i < length; i++ ) {
			args[ i ].attach( this );
			diff += args[ i ].getOuterLength();
		}
	}
 
	this.adjustLength( diff, true );
	this.setupBlockSlugs();
	this.emit.apply( this, [ 'splice' ].concat( args ) );
 
	return removals;
};
 
/**
 * Setup a sparse array of booleans indicating where to place slugs
 *
 * TODO: The function name is misleading: in ContentBranchNodes it sets up inline slugs
 */
ve.dm.BranchNode.prototype.setupBlockSlugs = function () {
	var isBlock = this.canHaveChildrenNotContent();
 
	this.slugPositions = {};
 
	if ( isBlock && !this.isAllowedChildNodeType( 'paragraph' ) ) {
		// Don't put slugs in nodes which can't contain paragraphs
		return;
	}
 
	// Consider every position between two child nodes, before first child and after last child.
	// Skip over metadata children. Add slugs in appropriate places.
 
	// Support: Firefox
	// Note that this inserts a slug at position 0 if this content branch has no items or only
	// internal items, keeping the node from becoming invisible/unfocusable. In Firefox, backspace
	// after Ctrl+A leaves the document completely empty, so this ensures DocumentNode gets a slug.
 
	var len = this.children.length;
	var i = -1; // from -1 to len-1
	var j = 0; // from 0 to len
	while ( i < len ) {
		// If the next node is a meta item, find the first non-meta node after it, and consider that
		// one instead when deciding to insert a slug. Meta nodes themselves don't have slugs.
		while ( j < len && this.children[ j ].isMetaData() ) {
			j++;
		}
 
		// Can have slug at the beginning, or after every node which allows it (except internal nodes)
		var canHaveSlugAfter = i === -1 || ( this.children[ i ].canHaveSlugAfter() &&
			!this.children[ i ].isInternal() );
		// Can have slug at the end, or before every node which allows it
		var canHaveSlugBefore = j === len || this.children[ j ].canHaveSlugBefore();
 
		if ( canHaveSlugAfter && canHaveSlugBefore ) {
			var suppressSlugTypeAfter = this.children[ j ] && this.children[ j ].suppressSlugType();
			var suppressSlugTypeBefore = this.children[ i ] && this.children[ i ].suppressSlugType();
			// Slugs are suppressed if they have the same string type, e.g. for adjacent floated images
			if ( !( typeof suppressSlugTypeAfter === 'string' && suppressSlugTypeAfter === suppressSlugTypeBefore ) ) {
				this.slugPositions[ j ] = true;
			}
		}
 
		i = j;
		j++;
	}
};
 
/**
 * Check in the branch node has a slug at a particular offset
 *
 * @param {number} offset Offset to check for a slug at
 * @return {boolean} There is a slug at the offset
 */
ve.dm.BranchNode.prototype.hasSlugAtOffset = function ( offset ) {
	var startOffset = this.getOffset() + ( this.isWrapped() ? 1 : 0 );
 
	if ( offset === startOffset ) {
		return !!this.slugPositions[ 0 ];
	}
	for ( var i = 0; i < this.children.length; i++ ) {
		startOffset += this.children[ i ].getOuterLength();
		if ( offset === startOffset ) {
			return !!this.slugPositions[ i + 1 ];
		}
	}
	return false;
};