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

100% Statements 69/69
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 221 222 223 224 225 226 227                                      1x   11782x     11782x     11782x     11782x 40x                                   1x   1x                       1x 1x 1x                   1x   1x 1x 1x 1x                       1x 1x 1x                   1x   1x 1x 1x 1x                         1x       12812x 12812x   12812x 12812x 1033x 1033x     12812x 10738x 10738x 15771x 15771x       12812x 12812x 12812x   12812x               1x     13272x   13272x   13272x   3595x                     9677x 9677x 9677x 9677x     23678x 748x       23678x     23678x   23678x 1784x 1784x   1784x 1783x       23678x 23678x                   1x   469x   469x 167x   302x 579x 579x 288x     14x    
/*!
 * VisualEditor DataModel BranchNode class.
 *
 * @copyright 2011-2020 VisualEditor Team and others; see http://ve.mit-license.org
 */
 
/**
 * 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 () {
	var childModel;
	Eif ( this.children.length ) {
		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 () {
	var childModel;
	Eif ( this.children.length ) {
		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 i,
		length,
		removals,
		args = Array.prototype.slice.call( arguments ),
		diff = 0;
 
	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 i, j, len, canHaveSlugAfter, canHaveSlugBefore,
		suppressSlugTypeAfter, suppressSlugTypeBefore,
		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.
 
	len = this.children.length;
	i = -1; // from -1 to len-1
	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)
		canHaveSlugAfter = i === -1 || ( this.children[ i ].canHaveSlugAfter() &&
			!this.children[ i ].isInternal() );
		// Can have slug at the end, or before every node which allows it
		canHaveSlugBefore = j === len || this.children[ j ].canHaveSlugBefore();
 
		if ( canHaveSlugAfter && canHaveSlugBefore ) {
			suppressSlugTypeAfter = this.children[ j ] && this.children[ j ].suppressSlugType();
			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 i,
		startOffset = this.getOffset() + ( this.isWrapped() ? 1 : 0 );
 
	if ( offset === startOffset ) {
		return !!this.slugPositions[ 0 ];
	}
	for ( i = 0; i < this.children.length; i++ ) {
		startOffset += this.children[ i ].getOuterLength();
		if ( offset === startOffset ) {
			return !!this.slugPositions[ i + 1 ];
		}
	}
	return false;
};