%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/dm/ |
| Current File : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/dm/ve.dm.DocumentSynchronizer.js |
/*!
* VisualEditor DataModel DocumentSynchronizer class.
*
* @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
*/
/**
* DataModel document synchronizer.
*
* This object is a utility for collecting actions to be performed on the model tree in multiple
* steps as the linear model is modified by a transaction processor and then processing those queued
* actions when the transaction is done being processed.
*
* IMPORTANT NOTE: It is assumed that:
*
* - The linear model has already been updated for the pushed actions
* - Actions are pushed in increasing offset order
* - Actions are non-overlapping
*
* @class
* @constructor
* @param {ve.dm.Document} doc Document to synchronize
* @param {ve.dm.Transaction} transaction The transaction being synchronized for
* @param {boolean} isStaging Transaction is being applied in staging mode
*/
ve.dm.DocumentSynchronizer = function VeDmDocumentSynchronizer( doc, transaction, isStaging ) {
// Properties
this.document = doc;
this.actionQueue = [];
this.eventQueue = [];
this.adjustment = 0;
this.transaction = transaction;
this.isStaging = isStaging;
};
/* Static Properties */
/**
* Synchronization methods.
*
* Each method is specific to a type of action. Methods are called in the context of a document
* synchronizer, so they work similar to normal methods on the object.
*
* @static
* @property
*/
ve.dm.DocumentSynchronizer.synchronizers = {};
/* Static Methods */
/**
* Synchronize an annotation action.
*
* This method is called within the context of a document synchronizer instance.
*
* @static
* @method
* @param {Object} action
*/
ve.dm.DocumentSynchronizer.synchronizers.annotation = function ( action ) {
// Queue events for all leaf nodes covered by the range
var i,
adjustedRange = action.range.translate( this.adjustment ),
selection = this.document.selectNodes( adjustedRange, 'leaves' );
for ( i = 0; i < selection.length; i++ ) {
// No tree synchronization needed
// Queue events
this.queueEvent( selection[ i ].node, 'annotation' );
this.queueEvent( selection[ i ].node, 'update', this.isStaging );
}
};
/**
* Synchronize an attribute change action.
*
* This method is called within the context of a document synchronizer instance.
*
* @static
* @method
* @param {Object} action
*/
ve.dm.DocumentSynchronizer.synchronizers.attributeChange = function ( action ) {
// No tree synchronization needed
// Queue events
this.queueEvent( action.node, 'attributeChange', action.key, action.from, action.to );
this.queueEvent( action.node, 'update', this.isStaging );
};
/**
* Synchronize a resize action.
*
* This method is called within the context of a document synchronizer instance.
*
* @static
* @method
* @param {Object} action
*/
ve.dm.DocumentSynchronizer.synchronizers.resize = function ( action ) {
var node = action.node,
parent = node.getParent();
if ( parent && node.getType() === 'text' && node.getLength() + action.adjustment === 0 ) {
// Auto-prune empty text nodes
parent.splice( parent.indexOf( node ), 1 );
} else {
// Apply length change to tree
// No update event needed, adjustLength causes an update event on its own
// FIXME however, any queued update event will still be emitted, resulting in a duplicate
node.adjustLength( action.adjustment );
}
// Update adjustment
this.adjustment += action.adjustment;
};
/**
* Synchronize a text node insertion.
*
* This method is called within the context of a document synchronizer instance.
*
* @static
* @method
* @param {Object} action
*/
ve.dm.DocumentSynchronizer.synchronizers.insertTextNode = function ( action ) {
var textNode = new ve.dm.TextNode();
textNode.setLength( action.length );
action.parentNode.splice( action.index, 0, textNode );
this.adjustment += action.length;
};
/**
* Synchronize a rebuild action.
*
* This method is called within the context of a document synchronizer instance.
*
* @static
* @method
* @param {Object} action
*/
ve.dm.DocumentSynchronizer.synchronizers.rebuild = function ( action ) {
var firstNode, parent, index, numNodes,
// Find the nodes contained by oldRange
adjustedOldRange = action.oldRange.translate( this.adjustment ),
selection = this.document.selectNodes( adjustedOldRange, 'siblings' );
// If the document is empty, selection[0].node will be the document (so no parent)
// but we won't get indexInNode either. Detect this and use index=0 in that case.
if ( 'indexInNode' in selection[ 0 ] || !selection[ 0 ].node.getParent() ) {
// Insertion
parent = selection[ 0 ].node;
index = selection[ 0 ].indexInNode || 0;
numNodes = 0;
} else {
// Rebuild
firstNode = selection[ 0 ].node;
parent = firstNode.getParent();
index = selection[ 0 ].index;
numNodes = selection.length;
}
// Perform rebuild in tree
this.document.rebuildNodes( parent, index, numNodes, adjustedOldRange.from,
action.newRange.getLength()
);
// Update adjustment
this.adjustment += action.newRange.getLength() - adjustedOldRange.getLength();
};
/* Methods */
/**
* Get the document being synchronized.
*
* @method
* @return {ve.dm.Document} Document being synchronized
*/
ve.dm.DocumentSynchronizer.prototype.getDocument = function () {
return this.document;
};
/**
* Add an annotation action to the queue.
*
* This finds all leaf nodes covered wholly or partially by the given range, and emits annotation
* events for all of them.
*
* @method
* @param {ve.Range} range Range that was annotated
*/
ve.dm.DocumentSynchronizer.prototype.pushAnnotation = function ( range ) {
this.actionQueue.push( {
type: 'annotation',
range: range
} );
};
/**
* Add an attribute change to the queue.
*
* This emits an attributeChange event for the given node with the provided metadata.
*
* @method
* @param {ve.dm.Node} node Node whose attribute changed
* @param {string} key Key of the attribute that changed
* @param {Mixed} from Old value of the attribute
* @param {Mixed} to New value of the attribute
*/
ve.dm.DocumentSynchronizer.prototype.pushAttributeChange = function ( node, key, from, to ) {
this.actionQueue.push( {
type: 'attributeChange',
node: node,
key: key,
from: from,
to: to
} );
};
/**
* Add a resize action to the queue.
*
* This changes the length of a text node.
*
* @method
* @param {ve.dm.TextNode} node Node to resize
* @param {number} adjustment Length adjustment to apply to the node
*/
ve.dm.DocumentSynchronizer.prototype.pushResize = function ( node, adjustment ) {
this.actionQueue.push( {
type: 'resize',
node: node,
adjustment: adjustment
} );
};
/**
* Add a text node insertion action to the queue.
*
* This inserts a new text node.
*
* @param {ve.dm.Node} parentNode Node to insert text node into
* @param {number} index Index in parentNode to insert text node at
* @param {number} length Length of new text node
*/
ve.dm.DocumentSynchronizer.prototype.pushInsertTextNode = function ( parentNode, index, length ) {
this.actionQueue.push( {
type: 'insertTextNode',
parentNode: parentNode,
index: index,
length: length
} );
};
/**
* Add a rebuild action to the queue.
*
* When a range of data has been changed arbitrarily this can be used to drop the nodes that
* represented the original range and replace them with new nodes that represent the new range.
*
* @method
* @param {ve.Range} oldRange Range of old nodes to be dropped
* @param {ve.Range} newRange Range for new nodes to be built from
*/
ve.dm.DocumentSynchronizer.prototype.pushRebuild = function ( oldRange, newRange ) {
this.actionQueue.push( {
type: 'rebuild',
oldRange: oldRange,
newRange: newRange
} );
};
/**
* Queue an event to be emitted on a node.
*
* This method is called by methods defined in {ve.dm.DocumentSynchronizer.synchronizers}.
*
* Duplicate events will be ignored only if all arguments match exactly. Hashes of each event that
* has been queued are stored in the nodes they will eventually be fired on.
*
* @method
* @param {ve.dm.Node} node
* @param {string} event Event name
* @param {...Mixed} [args] Additional arguments to be passed to the event when fired
*/
ve.dm.DocumentSynchronizer.prototype.queueEvent = function ( node ) {
// Check if this is already queued
var
args = Array.prototype.slice.call( arguments, 1 ),
hash = OO.getHash( args );
if ( !node.queuedEventHashes ) {
node.queuedEventHashes = {};
}
if ( !node.queuedEventHashes[ hash ] ) {
node.queuedEventHashes[ hash ] = true;
this.eventQueue.push( {
node: node,
args: args.concat( this.transaction )
} );
}
};
/**
* Synchronize node tree using queued actions.
*
* This method uses the static methods defined in {ve.dm.DocumentSynchronizer.synchronizers} and
* calls them in the context of {this}.
*
* After synchronization is complete all queued events will be emitted. Hashes of queued events that
* have been stored on nodes are removed from the nodes after the events have all been emitted.
*
* This method also clears both action and event queues.
*
* @method
*/
ve.dm.DocumentSynchronizer.prototype.synchronize = function () {
var action,
event,
i;
// Execute the actions in the queue
for ( i = 0; i < this.actionQueue.length; i++ ) {
action = this.actionQueue[ i ];
if ( Object.prototype.hasOwnProperty.call( ve.dm.DocumentSynchronizer.synchronizers, action.type ) ) {
ve.dm.DocumentSynchronizer.synchronizers[ action.type ].call( this, action );
} else {
throw new Error( 'Invalid action type ' + action.type );
}
}
// Emit events in the event queue
for ( i = 0; i < this.eventQueue.length; i++ ) {
event = this.eventQueue[ i ];
event.node.emit.apply( event.node, event.args );
delete event.node.queuedEventHashes;
}
// Clear queues
this.actionQueue = [];
this.eventQueue = [];
};