2011-11-17 22:44:11 +00:00
|
|
|
/**
|
|
|
|
* Creates an es.HistoryModel object.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @constructor
|
|
|
|
* @extends {es.EventEmitter}
|
|
|
|
* @param {es.DocumentModel} doc Document being tracked and modified
|
|
|
|
*/
|
|
|
|
es.HistoryModel = function( doc ) {
|
|
|
|
// Inheritance
|
|
|
|
es.EventEmitter.call( this );
|
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.doc = doc;
|
2011-11-21 23:51:37 +00:00
|
|
|
this.currentState = new es.HistoryStateModel();
|
|
|
|
this.currentStateIndex = 0;
|
|
|
|
this.states = [this.currentState];
|
|
|
|
this.currentStateDiff = 0;
|
2011-11-17 22:44:11 +00:00
|
|
|
|
|
|
|
// Configuration
|
2011-11-21 23:51:37 +00:00
|
|
|
this.maxStateDiff = 24;
|
2011-11-17 22:44:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the index of the current state.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.getCurrentStateIndex = function() {
|
|
|
|
return this.currentStateIndex;
|
|
|
|
};
|
|
|
|
|
2011-11-21 23:51:37 +00:00
|
|
|
/**
|
|
|
|
* Gets the index of the current state.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.getCurrentStateSelection = function() {
|
|
|
|
return this.currentState.getSelection();
|
|
|
|
};
|
|
|
|
|
2011-11-17 22:44:11 +00:00
|
|
|
/**
|
|
|
|
* Gets the number of states available.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.getStateCount = function() {
|
|
|
|
return this.states.length;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a copy of the list of states.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Boolean} deep Whether to make a deep copy (can be slow)
|
|
|
|
* @returns {Array[]} List of states, each a list of transactions
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.getStates = function( deep ) {
|
|
|
|
return deep ? es.copyArray( this.states ) : this.states.slice( 0 );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a copy of the list of transactions, which are not yet part of a state.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Boolean} deep Whether to make a deep copy (can be slow)
|
|
|
|
* @returns {es.TransactionModel[]} List of transactions
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.getTransactions = function( deep ) {
|
|
|
|
return deep ? es.copyArray( this.transactions ) : this.transactions.slice( 0 );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Commits a transaction.
|
|
|
|
*
|
|
|
|
* Unless the accumulate option is used a state will be automatically pushed before committing
|
|
|
|
* if the transaction is of a different type as the previous one in the transaction buffer, or if
|
|
|
|
* the transaction would produce a content length difference beyond the configured maximum.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {es.TransactionModel} transaction Transaction to commit
|
2011-11-21 23:51:37 +00:00
|
|
|
* @param {es.Range} [selection] Selection to use after the transaction has been applied
|
|
|
|
* @param {Boolean} [accumulate] Prevent automatic state pushing
|
2011-11-17 22:44:11 +00:00
|
|
|
*/
|
2011-11-21 23:51:37 +00:00
|
|
|
es.HistoryModel.prototype.commit = function( transaction, selection, accumulate ) {
|
|
|
|
var transactionDiff = Math.abs( transaction.getLengthDiff() );
|
2011-11-17 22:44:11 +00:00
|
|
|
// Unless we should intentionally accumulate transactions or this is the first one for this
|
|
|
|
// state, automatically push state
|
2011-11-21 23:51:37 +00:00
|
|
|
var transactionCount = this.currentState.getTransactionCount();
|
|
|
|
if ( !accumulate && transactionCount ) {
|
2011-11-17 22:44:11 +00:00
|
|
|
if (
|
|
|
|
// If the transactions are of a different type
|
2011-11-21 23:51:37 +00:00
|
|
|
this.currentState.getTransactions()[transactionCount - 1].type !== transaction.type ||
|
2011-11-17 22:44:11 +00:00
|
|
|
// This transaction would make the state longer than the maximum length
|
2011-11-21 23:51:37 +00:00
|
|
|
this.currentStateDiff + transactionDiff > this.maxStateDiff
|
2011-11-17 22:44:11 +00:00
|
|
|
) {
|
|
|
|
this.pushState();
|
|
|
|
}
|
|
|
|
}
|
2011-11-21 23:51:37 +00:00
|
|
|
this.currentState.pushTransaction( transaction );
|
|
|
|
this.currentStateDiff += transactionDiff;
|
2011-11-17 22:44:11 +00:00
|
|
|
// Apply transaction to the document
|
|
|
|
this.doc.commit( transaction );
|
|
|
|
// Emit a do event with the transaction that was just committed
|
|
|
|
this.emit( 'do', transaction );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Moves transactions in the buffer into a new state.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.pushState = function() {
|
|
|
|
// If any transactions have been pushed since the last state push
|
2011-11-21 23:51:37 +00:00
|
|
|
if ( this.currentState.getTransactionCount() ) {
|
2011-11-17 22:44:11 +00:00
|
|
|
// If the current state is not the most recently added state
|
|
|
|
if ( this.currentStateIndex < this.states.length - 1 ) {
|
|
|
|
// Forget about states newer than the current one
|
|
|
|
this.states.splice(
|
|
|
|
this.currentStateIndex, this.states.length - this.currentStateIndex
|
|
|
|
);
|
|
|
|
}
|
2011-11-21 23:51:37 +00:00
|
|
|
// Create a new current state
|
|
|
|
this.currentState = new es.HistoryStateModel();
|
|
|
|
// Add the new current state to the stack
|
|
|
|
this.states.push( this.currentState );
|
|
|
|
// Reset the state diff counter
|
|
|
|
this.currentStateDiff = 0;
|
|
|
|
// Move the current state index to the end (should be equivilant of ++)
|
|
|
|
this.currentStateIndex = this.states.length - 1;
|
2011-11-17 22:44:11 +00:00
|
|
|
}
|
2011-11-21 23:51:37 +00:00
|
|
|
// Emit the completed
|
|
|
|
this.emit( 'pushState', this.states[this.states.length - 1] );
|
2011-11-17 22:44:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.undo = function( steps ) {
|
|
|
|
if ( steps === undefined ) {
|
|
|
|
steps = 1;
|
|
|
|
}
|
|
|
|
// Stop undo just before the first state
|
|
|
|
var previousStateIndex = this.currentStateIndex;
|
|
|
|
this.currentStateIndex = Math.max( -1, this.currentStateIndex - steps );
|
|
|
|
if ( previousStateIndex > this.currentStateIndex ) {
|
|
|
|
for ( var i = previousStateIndex; i > this.currentStateIndex; i-- ) {
|
|
|
|
// Apply transaction to the document
|
2011-11-21 23:51:37 +00:00
|
|
|
var transactions = this.states[i].getTransactions();
|
|
|
|
for ( var j = transactions.length - 1; j >= 0; j-- ) {
|
|
|
|
this.doc.rollback( transactions[j] );
|
2011-11-21 23:10:31 +00:00
|
|
|
}
|
2011-11-17 22:44:11 +00:00
|
|
|
// Emit an undo event with the state to be rolled back
|
|
|
|
this.emit( 'undo', this.states[i] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
es.HistoryModel.prototype.redo = function( steps ) {
|
|
|
|
if ( steps === undefined ) {
|
|
|
|
steps = 1;
|
|
|
|
}
|
|
|
|
// Stop redo at the last state
|
|
|
|
var previousStateIndex = this.currentStateIndex;
|
|
|
|
this.currentStateIndex = Math.min( this.states.length - 1, this.currentStateIndex + steps );
|
|
|
|
if ( previousStateIndex < this.currentStateIndex ) {
|
|
|
|
for ( var i = previousStateIndex + 1; i >= this.currentStateIndex; i++ ) {
|
|
|
|
// Apply transaction to the document
|
2011-11-21 23:51:37 +00:00
|
|
|
var transactions = this.states[i].getTransactions();
|
|
|
|
for ( var j = 0; j < transactions.length; j++ ) {
|
|
|
|
this.doc.commit( transactions[j] );
|
|
|
|
}
|
2011-11-17 22:44:11 +00:00
|
|
|
// Emit an undo event with the state to be rolled back
|
|
|
|
this.emit( 'redo', this.states[i] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2011-11-21 22:59:40 +00:00
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
es.extendClass( es.HistoryModel, es.EventEmitter );
|