%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 = []; };