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

89.8% Statements 44/49
94% Branches 47/50
100% Functions 2/2
89.8% Lines 44/49
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                                      3414×         3414×               3414×         3414×         3414×         3414×         3414×         3414×         3414×   3414×                             3414×       3414×         2918×     496×       3414×   2382× 2382×   1032× 1032×     3414×   3414× 2653×   761× 761× 17×   744×   744×             3414×     3414× 496× 496× 496× 2918×         2918× 2918× 2918×       3414×                 3414× 2281×     1133×                   3414×    
/*!
 * VisualEditor Content Editable Range State class
 *
 * @copyright 2011-2019 VisualEditor Team and others; see http://ve.mit-license.org
 */
 
/**
 * 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;
 
	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 ) {
	var $node, selection, focusNodeChanged,
		oldSelection = old ? old.misleadingSelection : ve.SelectionState.static.newNullSelection(),
		nativeSelection = root.getElementDocument().getSelection();
 
	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 );
	}
 
	focusNodeChanged = oldSelection.focusNode !== selection.focusNode;
 
	if ( !focusNodeChanged ) {
		this.node = old && old.node;
	} else {
		$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 Iif ( 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;
};