mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-16 10:59:56 +00:00
a0178fd3bb
Change-Id: Ie0b742029f95e1475d58bb38b790bae3110259b0
159 lines
4.6 KiB
JavaScript
159 lines
4.6 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.
|
|
*
|
|
* NOTE: An 'update' event is emitted for every node that is touched by the synchronizer in any
|
|
* way, but only once for every node. Other events are emitted as we go.
|
|
*
|
|
* 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
|
|
*/
|
|
ve.dm.DocumentSynchronizer = function( doc ) {
|
|
// Properties
|
|
this.document = doc;
|
|
this.actions = [];
|
|
this.eventQueue = [];
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
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.
|
|
* @param {ve.Range} range Range that was annotated
|
|
*/
|
|
ve.dm.DocumentSynchronizer.prototype.pushAnnotation = function( range ) {
|
|
this.actions.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.
|
|
* @param {ve.dm.Node} node Node whose attribute changed
|
|
* @param {String} key Key of the attribute that changed
|
|
* @param from Old value of the attribute
|
|
* @param to New value of the attribute
|
|
*/
|
|
ve.dm.DocumentSynchronizer.prototype.pushAttributeChange = function( node, key, from, to ) {
|
|
this.actions.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.
|
|
* @param {ve.dm.TextNode} node Node to resize
|
|
* @param {Integer} adjustment Length adjustment to apply to the node
|
|
*/
|
|
ve.dm.DocumentSynchronizer.prototype.pushResize = function( node, adjustment ) {
|
|
this.actions.push( {
|
|
'type': 'resize',
|
|
'node': node,
|
|
'adjustment': adjustment
|
|
} );
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.pushRebuild = function( oldRange, newRange ) {
|
|
this.actions.push( {
|
|
'type': 'rebuild',
|
|
'oldRange': oldRange,
|
|
'newRange': newRange
|
|
} );
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.annotation = function( action ) {
|
|
// Queue events for all leaf nodes covered by the range
|
|
// TODO test me
|
|
var i, selection = this.document.selectNodes( action.range, 'leaves' );
|
|
for ( i = 0; i < selection.length; i++ ) {
|
|
this.queueEvent( selection[i].node, 'annotation' );
|
|
this.queueEvent( selection[i].node, 'update' );
|
|
}
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.attributeChange = function( action ) {
|
|
this.queueEvent( action.node, 'attributeChange', action.key, action.from, action.to );
|
|
this.queueEvent( action.node, 'update' );
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.resize = function( action ) {
|
|
action.node.adjustLength( action.adjustment );
|
|
this.queueEvent( action.node, 'update' );
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.rebuild = function( action ) {
|
|
// Find the nodes contained by oldRange
|
|
var selection = this.document.selectNodes( action.oldRange, 'siblings' );
|
|
if ( selection.length === 0 ) {
|
|
// WTF? Nothing to rebuild, I guess. Whatever.
|
|
return;
|
|
}
|
|
|
|
// TODO index of firstNode in parent should be in the selectNodes result
|
|
var firstNode = selection[0].node,
|
|
parent = firstNode.getParent(),
|
|
index = parent.indexOf( firstNode );
|
|
|
|
this.document.rebuildNodes( parent, index, selection.length, action.oldRange.from,
|
|
action.newRange.getLength()
|
|
);
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.queueEvent = function( node, event ) {
|
|
// Check if this is already queued
|
|
var args = Array.prototype.slice.call( arguments, 1 );
|
|
var hash = $.toJSON( args );
|
|
if ( !node.syncEventQueue ) {
|
|
node.syncEventQueue = {};
|
|
}
|
|
if ( !node.syncEventQueue[hash] ) {
|
|
node.syncEventQueue[hash] = true;
|
|
this.eventQueue.push( { 'node': node, 'args': args } );
|
|
}
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.emitEvents = function() {
|
|
var i, event;
|
|
for ( i = 0; i < this.eventQueue.length; i++ ) {
|
|
event = this.eventQueue[i];
|
|
event.node.emit.apply( event.node, event.args );
|
|
delete event.node.syncEventQueue;
|
|
}
|
|
this.eventQueue = [];
|
|
};
|
|
|
|
ve.dm.DocumentSynchronizer.prototype.synchronize = function() {
|
|
var action, i;
|
|
// Execute the actions in the queue
|
|
for ( i = 0; i < this.actions.length; i++ ) {
|
|
action = this.actions[i];
|
|
if ( action.type in this ) {
|
|
this[action.type]( action );
|
|
} else {
|
|
throw 'Invalid action type ' + action.type;
|
|
}
|
|
}
|
|
|
|
this.emitEvents();
|
|
this.actions = [];
|
|
};
|