jenkins-bot has submitted this change and it was merged.
Change subject: Add system to dm.Surface for staging changes
......................................................................
Add system to dm.Surface for staging changes
Pushing to the staging state stack causes all transactions
to be stored in a list at the top of the stack. Popping the stack
undoes those transactions and returns them. Applying
moves the transactions down the stack or into the undo stack.
Change-Id: I607d37591eb3e239a59047be0472627d48d267b1
---
M modules/ve/dm/ve.dm.Surface.js
M modules/ve/test/dm/ve.dm.Surface.test.js
2 files changed, 219 insertions(+), 14 deletions(-)
Approvals:
Catrope: Looks good to me, approved
jenkins-bot: Verified
diff --git a/modules/ve/dm/ve.dm.Surface.js b/modules/ve/dm/ve.dm.Surface.js
index fbec868..4ca1c66 100644
--- a/modules/ve/dm/ve.dm.Surface.js
+++ b/modules/ve/dm/ve.dm.Surface.js
@@ -24,6 +24,7 @@
this.selection = new ve.Range( 1 );
this.selectedNodes = {};
this.newTransactions = [];
+ this.stagingStack = [];
this.undoStack = [];
this.undoIndex = 0;
this.historyTrackingInterval = null;
@@ -156,6 +157,125 @@
};
/**
+ * If the surface in staging mode.
+ *
+ * @returns {boolean} The surface in staging mode
+ */
+ve.dm.Surface.prototype.isStaging = function () {
+ return this.stagingStack.length > 0;
+};
+
+/**
+ * Get the staging transactions at the current staging stack depth
+ *
+ * The array is returned by reference so it can be pushed to.
+ *
+ * @returns {ve.dm.Transaction[]|undefined} Staging transactions, or undefined
if not staging
+ */
+ve.dm.Surface.prototype.getStagingTransactions = function () {
+ return this.stagingStack[this.stagingStack.length - 1];
+};
+
+/**
+ * Push another level of staging to the staging stack
+ *
+ * @fires history
+ */
+ve.dm.Surface.prototype.pushStaging = function () {
+ // If we're starting staging stop history tracking
+ if ( !this.isStaging() ) {
+ // Set a breakpoint to make sure newTransactions is clear
+ this.breakpoint();
+ this.stopHistoryTracking();
+ this.emit( 'history' );
+ }
+ this.stagingStack.push( [] );
+};
+
+/**
+ * Pop a level of staging from the staging stack
+ *
+ * @fires history
+ * @returns {ve.dm.Transaction[]|undefined} Staging transactions, or undefined
if not staging
+ */
+ve.dm.Surface.prototype.popStaging = function () {
+ if ( !this.isStaging() ) {
+ return;
+ }
+
+ var i, transaction,
+ reverseTransactions = [],
+ transactions = this.stagingStack.pop();
+
+ // Not applying, so rollback transactions
+ for ( i = transactions.length - 1; i >= 0; i-- ) {
+ transaction = transactions[i].reversed();
+ reverseTransactions.push( transaction );
+ }
+ this.changeInternal( reverseTransactions, undefined, true );
+
+ if ( !this.isStaging() ) {
+ this.startHistoryTracking();
+ this.emit( 'history' );
+ }
+
+ return transactions;
+};
+
+/**
+ * Apply a level of staging from the staging stack
+ *
+ * @fires history
+ */
+ve.dm.Surface.prototype.applyStaging = function () {
+ if ( !this.isStaging() ) {
+ return;
+ }
+
+ var transactions = this.stagingStack.pop();
+
+ if ( this.isStaging() ) {
+ // Move transactions to the next item down in the staging stack
+ Array.prototype.push.apply( this.getStagingTransactions(),
transactions );
+ } else {
+ // Move transactions to the undo stack
+ this.newTransactions = transactions;
+ this.breakpoint();
+ }
+
+ if ( !this.isStaging() ) {
+ this.startHistoryTracking();
+ this.emit( 'history' );
+ }
+};
+
+/**
+ * Pop the staging stack until empty
+ *
+ * @returns {ve.dm.Transaction[]|undefined} Staging transactions, or undefined
if not staging
+ */
+ve.dm.Surface.prototype.popAllStaging = function () {
+ if ( !this.isStaging() ) {
+ return;
+ }
+
+ var transactions = [];
+ while ( this.isStaging() ) {
+ ve.batchSplice( transactions, 0, 0, this.popStaging() );
+ }
+ return transactions;
+};
+
+/**
+ * Apply the staging stack until empty
+ */
+ve.dm.Surface.prototype.applyAllStaging = function () {
+ while ( this.isStaging() ) {
+ this.applyStaging();
+ }
+};
+
+/**
* Get annotations that will be used upon insertion.
*
* @method
@@ -240,7 +360,7 @@
* @returns {boolean} Redo is allowed
*/
ve.dm.Surface.prototype.canRedo = function () {
- return this.undoIndex > 0 && this.enabled;
+ return this.undoIndex > 0 && this.enabled && !this.isStaging();
};
/**
@@ -250,7 +370,7 @@
* @returns {boolean} Undo is allowed
*/
ve.dm.Surface.prototype.canUndo = function () {
- return this.hasBeenModified() && this.enabled;
+ return this.hasBeenModified() && this.enabled && !this.isStaging();
};
/**
@@ -509,11 +629,15 @@
for ( i = 0, len = transactions.length; i < len; i++ ) {
if ( !transactions[i].isNoOp() ) {
if ( !skipUndoStack ) {
- this.truncateUndoStack();
- if ( !this.newTransactions.length ) {
- this.selectionBefore =
selectionBefore;
+ if ( this.isStaging() ) {
+
this.getStagingTransactions().push( transactions[i] );
+ } else {
+ this.truncateUndoStack();
+ if (
!this.newTransactions.length ) {
+ this.selectionBefore =
selectionBefore;
+ }
+ this.newTransactions.push(
transactions[i] );
}
- this.newTransactions.push(
transactions[i] );
}
// The .commit() call below indirectly invokes
setSelection()
this.documentModel.commit( transactions[i] );
diff --git a/modules/ve/test/dm/ve.dm.Surface.test.js
b/modules/ve/test/dm/ve.dm.Surface.test.js
index d7b2e13..a05096a 100644
--- a/modules/ve/test/dm/ve.dm.Surface.test.js
+++ b/modules/ve/test/dm/ve.dm.Surface.test.js
@@ -8,14 +8,15 @@
QUnit.module( 've.dm.Surface' );
ve.dm.SurfaceStub = function VeDmSurfaceStub( data ) {
+ var doc;
if ( data !== undefined ) {
- this.dm = new ve.dm.Document( data );
+ doc = new ve.dm.Document( data );
} else {
- this.dm = new ve.dm.Document( [{ 'type': 'paragraph' }, 'h',
'i', { 'type': '/paragraph' }] );
+ doc = new ve.dm.Document( [{ 'type': 'paragraph' }, 'h', 'i', {
'type': '/paragraph' }] );
}
// Inheritance
- ve.dm.Surface.call( this, this.dm );
+ ve.dm.Surface.call( this, doc );
};
OO.inheritClass( ve.dm.SurfaceStub, ve.dm.Surface );
@@ -33,14 +34,14 @@
} );
QUnit.test( 'change/setSelection events', 3, function ( assert ) {
- var tx, surface = new ve.dm.SurfaceStub(),
+ var surface = new ve.dm.SurfaceStub(),
+ doc = surface.getDocument(),
+ // docmentUpdate doesn't fire for no-op transactions, so make
sure there's something there
+ tx = ve.dm.Transaction.newFromInsertion( doc, 3, [ 'i' ] ),
events = {
'documentUpdate': 0,
'select': 0
};
-
- // docmentUpdate doesn't fire for no-op transactions, so make sure
there's something there
- tx = ve.dm.Transaction.newFromInsertion( surface.getDocument(), 3, [
'i' ] );
surface.on( 'documentUpdate', function () {
events.documentUpdate++;
@@ -58,7 +59,8 @@
QUnit.test( 'breakpoint', 7, function ( assert ) {
var surface = new ve.dm.SurfaceStub(),
- tx = new ve.dm.Transaction.newFromInsertion( surface.dm, 1,
['x'] ),
+ doc = surface.getDocument(),
+ tx = new ve.dm.Transaction.newFromInsertion( doc, 1, ['x'] ),
selection = new ve.Range( 1, 1 );
assert.equal( surface.breakpoint(), false, 'Returns false if no
transactions applied' );
@@ -80,6 +82,85 @@
assert.deepEqual( surface.newTransactions, [], 'New transactions match
after breakpoint' );
} );
+QUnit.test( 'staging', 23, function ( assert ) {
+ var tx1, tx2,
+ surface = new ve.dm.SurfaceStub(),
+ fragment = surface.getFragment( new ve.Range( 1, 3 ) ),
+ doc = surface.getDocument();
+
+ assert.equal( surface.isStaging(), false, 'isStaging false when not
staging' );
+ assert.equal( surface.getStagingTransactions(), undefined,
'getStagingTransactions undefined when not staging' );
+
+ surface.change( new ve.dm.Transaction.newFromInsertion( doc, 1, ['a'] )
);
+
+ surface.pushStaging();
+ assert.equal( surface.isStaging(), true, 'isStaging true after
pushStaging' );
+ assert.deepEqual( surface.getStagingTransactions(), [],
'getStagingTransactions empty array after pushStaging' );
+
+ tx1 = new ve.dm.Transaction.newFromInsertion( doc, 2, ['b'] );
+ surface.change( tx1 );
+
+ assert.equal( fragment.getText(), 'abhi', 'document contents match
after first transaction' );
+ assert.deepEqual( surface.getStagingTransactions(), [tx1],
'getStagingTransactions contains first transaction after change' );
+
+ surface.pushStaging();
+ assert.equal( surface.isStaging(), true, 'isStaging true after nested
pushStaging' );
+ assert.deepEqual( surface.getStagingTransactions(), [],
'getStagingTransactions empty array after nested pushStaging' );
+
+ tx2 = new ve.dm.Transaction.newFromInsertion( doc, 3, ['c'] );
+ surface.change( tx2 );
+
+ assert.equal( fragment.getText(), 'abchi', 'document contents match
after second transaction' );
+ assert.deepEqual( surface.getStagingTransactions(), [tx2],
'getStagingTransactions contains second transaction after change in nested
staging' );
+
+ assert.deepEqual( surface.popStaging(), [tx2], 'popStaging returns
second transaction list' );
+ assert.equal( surface.isStaging(), true, 'isStaging true after nested
popStaging' );
+ assert.equal( fragment.getText(), 'abhi', 'document contents match
after nested popStaging' );
+
+ assert.deepEqual( surface.popStaging(), [tx1], 'popStaging returns
first transaction list' );
+ assert.equal( surface.isStaging(), false, 'isStaging false after outer
popStaging' );
+ assert.equal( fragment.getText(), 'ahi', 'document contents match after
outer popStaging' );
+
+ surface.pushStaging();
+ tx1 = new ve.dm.Transaction.newFromInsertion( doc, 2, ['b'] );
+ surface.change( tx1 );
+
+ surface.pushStaging();
+ tx2 = new ve.dm.Transaction.newFromInsertion( doc, 3, ['c'] );
+ surface.change( tx2 );
+
+ assert.deepEqual( surface.popAllStaging(), [tx1, tx2], 'popAllStaging
returns full transaction list' );
+ assert.equal( fragment.getText(), 'ahi', 'document contents match after
outer clearStaging' );
+
+ surface.pushStaging();
+ tx1 = new ve.dm.Transaction.newFromInsertion( doc, 2, ['b'] );
+ surface.change( tx1 );
+
+ surface.pushStaging();
+ tx2 = new ve.dm.Transaction.newFromInsertion( doc, 3, ['c'] );
+ surface.change( tx2 );
+
+ surface.applyStaging();
+ assert.deepEqual( surface.getStagingTransactions(), [tx1, tx2],
'applyStaging merges transactions' );
+
+ surface.applyStaging();
+ assert.equal( surface.isStaging(), false, 'isStaging false after outer
applyStaging' );
+ assert.equal( fragment.getText(), 'abchi', 'document contents changed
after applyStaging' );
+
+ surface.pushStaging();
+ tx1 = new ve.dm.Transaction.newFromInsertion( doc, 4, ['d'] );
+ surface.change( tx1 );
+
+ surface.pushStaging();
+ tx2 = new ve.dm.Transaction.newFromInsertion( doc, 5, ['e'] );
+ surface.change( tx2 );
+
+ surface.applyAllStaging();
+ assert.equal( surface.isStaging(), false, 'isStaging false after outer
applyAllStaging' );
+ assert.equal( fragment.getText(), 'abcdehi', 'document contents changed
after applyAllStaging' );
+
+} );
+
// TODO: ve.dm.Surface#getHistory
// TODO: ve.dm.Surface#purgeHistory
// TODO: ve.dm.Surface#canRedo
--
To view, visit https://gerrit.wikimedia.org/r/125923
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I607d37591eb3e239a59047be0472627d48d267b1
Gerrit-PatchSet: 7
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits