mediawiki-extensions-Visual.../modules/ve/dm/ve.dm.DocumentSynchronizer.js

119 lines
3.9 KiB
JavaScript

/**
* Creates an ve.dm.DocumentSynchronizer object.
*
* This object is a utility for collecting actions to be performed on the model tree
* in multiple steps and then processing those actions in a single step.
*
* @class
* @constructor
*/
ve.dm.DocumentSynchronizer = function( model ) {
// Properties
this.model = model;
this.actions = [];
};
/* Methods */
ve.dm.DocumentSynchronizer.prototype.getModel = function() {
return this.model;
};
/**
* Adds an action to the synchronizer.
*
* @method
* @param {String} type Type of action, can be: "insert", "delete", "rebuild", "resize" or "update"
* @param {ve.dm.Node} node Node this action is related to
* @param {Integer|null} offset Offset of node, improves performance if this has already been calculated.
* Only used for insert and rebuild actions
* @param {Integer} adjustment Node length adjustment, if any
*/
ve.dm.DocumentSynchronizer.prototype.pushAction = function( type, node, offset, adjustment ) {
this.actions.push( {
'type': type,
'node': node,
'offset': offset,
'adjustment': adjustment || 0
} );
};
/**
* Applies queued actions to the model tree.
*
* @method
*/
ve.dm.DocumentSynchronizer.prototype.synchronize = function() {
// TODO: Normalize the actions list to clean up nested actions
// Perform all actions
var adjustment = 0,
action,
offset,
parent;
for ( var i = 0, len = this.actions.length; i < len; i++ ) {
action = this.actions[i];
offset = action.offset === null ? null : ( action.offset + adjustment );
switch ( action.type ) {
case 'insert':
// Compute the offset if it wasn't provided
if ( offset === null ) {
offset = this.model.getOffsetFromNode( action.node );
}
// Insert the new node at the given offset
var target = this.model.getNodeFromOffset( offset + 1 );
if ( target === this.model ) {
// Insert at the beginning of the document
this.model.splice( 0, 0, action.node );
} else if ( target === null ) {
// Insert at the end of the document
this.model.splice( this.model.getElementLength(), 0, action.node );
} else {
// Insert before the element currently at the offset
parent = target.getParent();
parent.splice( parent.indexOf( target ), 0, action.node );
}
// Adjust proceeding offsets positively by the length of the node being inserted
adjustment += action.node.getElementLength();
break;
case 'delete':
// Replace original node with new node
parent = action.node.getParent();
parent.splice( parent.indexOf( action.node ), 1 );
// Adjust proceeding offsets negatively by the length of the node being deleted
adjustment -= action.node.getElementLength();
break;
case 'rebuild':
// Compute the offset if it wasn't provided
if ( offset === null ) {
offset = this.model.getOffsetFromNode( action.node );
}
// Replace original node with new node
var newNodes = ve.dm.DocumentNode.createNodesFromData( this.model.getData(
new ve.Range( offset, action.node.getElementLength() + action.adjustment )
) );
parent = action.node.getParent();
ve.batchedSplice( parent, parent.indexOf( action.node ), 1, newNodes );
// Adjust proceeding offsets by the difference between the original and new nodes
var newNodesLength = 0;
for ( var j = 0, jlen = newNodes.length; j < jlen; j++ ) {
newNodesLength += newNodes[j].getElementLength();
}
adjustment += newNodesLength - action.node.getElementLength();
break;
case 'resize':
// Adjust node length - causes update events to be emitted
action.node.adjustContentLength( action.adjustment );
// Adjust proceeding offsets by the amount the node is being lengthened or shortened
adjustment += action.adjustment;
break;
case 'update':
// Emit update events
action.node.emit( 'update' );
break;
}
}
// We've processed the queue, clear it
this.actions = [];
};