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 b340a70..b8c7bcf 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();
 };
 
 /**
@@ -507,11 +627,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/126904
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I607d37591eb3e239a59047be0472627d48d267b1
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: wmf/1.23wmf22
Gerrit-Owner: Catrope <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to