All files / src/ce ve.ce.RangeState.js

96.07% Statements 49/51
98% Branches 49/50
100% Functions 2/2
96.07% Lines 49/51

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                                1x       4531x         4531x               4531x         4531x         4531x         4531x         4531x         4531x         4531x                 4531x   4531x         1x                     1x 4531x 4531x     4531x         3288x     1243x       4531x   3352x 3352x   1179x 1179x     4531x   4531x 3651x   880x 880x 28x   852x   852x             4531x     4531x 1243x 1243x 1243x 3288x 128x 128x 128x   3160x 3160x 3160x       4531x                 4531x 3212x     1319x                   4531x    
/*!
 * VisualEditor Content Editable Range State class
 *
 * @copyright See AUTHORS.txt
 */
 
/**
 * ContentEditable range state (a snapshot of CE selection/content state)
 *
 * @class
 *
 * @constructor
 * @param {ve.ce.RangeState|null} old Previous range state
 * @param {ve.ce.BranchNode} root Surface root
 * @param {boolean} selectionOnly The caller promises the content has not changed from old
 */
ve.ce.RangeState = function VeCeRangeState( old, root, selectionOnly ) {
	/**
	 * @property {boolean} branchNodeChanged Whether the CE branch node changed
	 */
	this.branchNodeChanged = false;
 
	/**
	 * @property {boolean} selectionChanged Whether the DOM range changed
	 */
	this.selectionChanged = false;
 
	/**
	 * @property {boolean} contentChanged Whether the content changed
	 *
	 * This is only set to true if both the old and new states have the
	 * same current branch node, whose content has changed
	 */
	this.contentChanged = false;
 
	/**
	 * @property {ve.Range|null} veRange The current selection range
	 */
	this.veRange = null;
 
	/**
	 * @property {ve.ce.BranchNode|null} node The current branch node
	 */
	this.node = null;
 
	/**
	 * @property {string|null} text Plain text of current branch node
	 */
	this.text = null;
 
	/**
	 * @property {string|null} DOM Hash of current branch node
	 */
	this.hash = null;
 
	/**
	 * @property {ve.ce.TextState|null} Current branch node's annotated content
	 */
	this.textState = null;
 
	/**
	 * @property {boolean|null} focusIsAfterAnnotationBoundary Focus lies after annotation tag
	 */
	this.focusIsAfterAnnotationBoundary = null;
 
	/**
	 * Saved selection for future comparisons. (But it is not properly frozen, because the
	 * nodes are live and mutable, and therefore the offsets may come to point to places that
	 * are misleadingly different from when the selection was saved).
	 *
	 * @property {ve.SelectionState} misleadingSelection Saved selection (but with live nodes)
	 */
	this.misleadingSelection = null;
 
	this.saveState( old, root, selectionOnly );
};
 
/* Inheritance */
 
OO.initClass( ve.ce.RangeState );
 
/* Methods */
 
/**
 * Saves a snapshot of the current range state
 *
 * @param {ve.ce.RangeState|null} old Previous range state
 * @param {ve.ce.BranchNode} root Surface root
 * @param {boolean} selectionOnly The caller promises the content has not changed from old
 */
ve.ce.RangeState.prototype.saveState = function ( old, root, selectionOnly ) {
	const oldSelection = old ? old.misleadingSelection : ve.SelectionState.static.newNullSelection(),
		nativeSelection = root.getElementDocument().getSelection();
 
	let selection;
	if (
		nativeSelection.rangeCount &&
		OO.ui.contains( root.$element[ 0 ], nativeSelection.focusNode, true )
	) {
		// Freeze selection out of live object.
		selection = new ve.SelectionState( nativeSelection );
	} else {
		// Use a blank selection if the selection is outside the document
		selection = ve.SelectionState.static.newNullSelection();
	}
 
	// Get new range information
	if ( selection.equalsSelection( oldSelection ) ) {
		// No change; use old values for speed
		this.selectionChanged = false;
		this.veRange = old && old.veRange;
	} else {
		this.selectionChanged = true;
		this.veRange = ve.ce.veRangeFromSelection( selection );
	}
 
	const focusNodeChanged = oldSelection.focusNode !== selection.focusNode;
 
	if ( !focusNodeChanged ) {
		this.node = old && old.node;
	} else {
		const $node = $( selection.focusNode ).closest( '.ve-ce-branchNode' );
		if ( $node.length === 0 ) {
			this.node = null;
		} else {
			this.node = $node.data( 'view' );
			// Check this node belongs to our document
			Iif ( this.node && this.node.root !== root.root ) {
				this.node = null;
				this.veRange = null;
			}
		}
	}
 
	this.branchNodeChanged = ( old && old.node ) !== this.node;
 
	// Compute text/hash/textState, for change comparison
	if ( !this.node ) {
		this.text = null;
		this.hash = null;
		this.textState = null;
	} else if ( selectionOnly && !focusNodeChanged ) {
		this.text = old.text;
		this.hash = old.hash;
		this.textState = old.textState;
	} else {
		this.text = ve.ce.getDomText( this.node.$element[ 0 ] );
		this.hash = ve.ce.getDomHash( this.node.$element[ 0 ] );
		this.textState = new ve.ce.TextState( this.node.$element[ 0 ] );
	}
 
	// Only set contentChanged if we're still in the same branch node
	this.contentChanged =
		!selectionOnly &&
		!this.branchNodeChanged && (
			( old && old.hash ) !== this.hash ||
			( old && old.text ) !== this.text ||
			( !this.textState && old && old.textState ) ||
			( !!this.textState && !this.textState.isEqual( old && old.textState ) )
		);
 
	if ( old && !this.selectionChanged && !this.contentChanged ) {
		this.focusIsAfterAnnotationBoundary = old.focusIsAfterAnnotationBoundary;
	} else {
		// Will be null if there is no selection
		this.focusIsAfterAnnotationBoundary = selection.focusNode &&
			ve.ce.isAfterAnnotationBoundary(
				selection.focusNode,
				selection.focusOffset
			);
	}
 
	// Save selection for future comparisons. (But it is not properly frozen, because the nodes
	// are live and mutable, and therefore the offsets may come to point to places that are
	// misleadingly different from when the selection was saved).
	this.misleadingSelection = selection;
};