Jforrester has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/350965 )

Change subject: [WIP] Allow document subscribers to be informed to changes to 
nodes
......................................................................

[WIP] Allow document subscribers to be informed to changes to nodes

Bug: T162761
Change-Id: I6b397e792bddc002e78fd7d2b06d509f3da2569d
---
M src/dm/ve.dm.Document.js
M tests/dm/ve.dm.Document.test.js
2 files changed, 163 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor 
refs/changes/65/350965/1

diff --git a/src/dm/ve.dm.Document.js b/src/dm/ve.dm.Document.js
index 196ba05..de6151a 100644
--- a/src/dm/ve.dm.Document.js
+++ b/src/dm/ve.dm.Document.js
@@ -52,6 +52,10 @@
        this.completeHistory = [];
        this.storeLengthAtHistoryLength = [ 0 ];
        this.nodesByType = {};
+       this.changeListeners = {};
+
+       // FIXME: DEBUG CODE
+       this.registerListener( 'heading', function() { window.alert( arguments[ 
0 ] + ': ' + arguments[ 1 ] ); } );
 
        if ( data instanceof ve.dm.ElementLinearData ) {
                // Pre-split ElementLinearData
@@ -384,10 +388,12 @@
        if ( transaction.hasBeenApplied() ) {
                throw new Error( 'Cannot commit a transaction that has already 
been committed' );
        }
+       this.alertListenersOnRemoves( transaction );
        this.emit( 'precommit', transaction );
        new ve.dm.TransactionProcessor( this, transaction, isStaging 
).process();
        this.completeHistory.push( transaction );
        this.storeLengthAtHistoryLength[ this.completeHistory.length ] = 
this.store.getLength();
+       this.alertListenersOnInserts( transaction );
        this.emit( 'transact', transaction );
 };
 
@@ -1664,3 +1670,150 @@
 ve.dm.Document.prototype.getDir = function () {
        return this.dir;
 };
+
+/**
+ * Register a function to be executed on changes to this document of nodes of 
the given type.
+ *
+ * @param {string} nodeType Node type
+ * @param {Function} listener Node type
+ */
+ve.dm.Document.prototype.registerListener = function ( nodeType, listener ) {
+       if ( !this.changeListeners.hasOwnProperty( nodeType ) ) {
+               this.changeListeners[ nodeType ] = [];
+       }
+       this.changeListeners[ nodeType ].push( listener );
+};
+
+/**
+ * De-register a function from execution on changes to this document of nodes 
of the given type.
+ *
+ * @param {string} nodeType Node type
+ * @param {Function} listener Node type
+ */
+ve.dm.Document.prototype.deregisterListener = function ( nodeType, listener ) {
+       this.changeListeners[ nodeType ].splice( this.changeListeners[ nodeType 
].indexOf( listener ) );
+};
+
+/**
+ * Execute listener functions on changes to this document of nodes of the 
given type.
+ *
+ * @param {ve.dm.Transaction} tx Transaction that was applied to the document
+ */
+ve.dm.Document.prototype.alertListenersOnRemoves = function ( tx ) {
+       var operations, operation, i, ilen, j, jlen, type, k, klen,
+               listeners = [];
+
+       if ( !Object.keys( this.changeListeners ).length ) {
+               return;
+       }
+
+       operations = tx.getOperations();
+       for ( i = 0, ilen = operations.length; i < ilen; i++ ) {
+               operation = operations[ i ];
+               switch ( operation.type ) {
+                       case 'retain':
+                       case 'retainMetadata':
+                               // No-op
+                               continue;
+                       case 'annotate':
+                               // FIXME: Implement this.
+                               continue;
+                       case 'attribute':
+                               // We only care about this in the 
after-transact case.
+                               continue;
+                       case 'replace':
+                               for ( j = 0, jlen = operation.remove.length; j 
< jlen; j++ ) {
+                                       type = operation.remove[ j ].type;
+                                       if ( !type || 
!this.changeListeners.hasOwnProperty( type ) ) {
+                                               // Do nothing for text node 
changes, or if no-one's registered for this type
+                                               continue;
+                                       }
+                                       for ( k = 0, klen = 
this.changeListeners[ type ].length; k < klen; k++ ) {
+                                               listeners.push( [ 
this.changeListeners[ type ][ k ], 'remove', operation.remove[ j ] ] );
+                                       }
+                               }
+                               break;
+                       case 'replaceMetadata':
+                               // FIXME: Implement this.
+                               continue;
+                       default:
+                               throw new Error( 'Unrecognised operation type: 
' + operation );
+
+               }
+
+       }
+
+       if ( !listeners.length ) {
+               return;
+       }
+
+       for ( i = 0, ilen = listeners.length; i < ilen; i++ ) {
+               listeners[ i ][ 0 ]( listeners[ i ][ 1 ], listeners[ i ][ 2 ] );
+       }
+};
+
+/**
+ * Execute listener functions on changes to this document of nodes of the 
given type.
+ *
+ * @param {ve.dm.Transaction} tx Transaction that was applied to the document
+ */
+ve.dm.Document.prototype.alertListenersOnInserts = function ( tx ) {
+       var operations, operation, i, ilen, j, jlen, type, k, klen,
+               listeners = [];
+
+       if ( !Object.keys( this.changeListeners ).length ) {
+               return;
+       }
+
+       operations = tx.getOperations();
+       for ( i = 0, ilen = operations.length; i < ilen; i++ ) {
+               operation = operations[ i ];
+               switch ( operation.type ) {
+                       case 'retain':
+                       case 'retainMetadata':
+                               // No-op
+                               continue;
+                       case 'annotate':
+                               // FIXME: Implement this.
+                               continue;
+                       case 'attribute':
+                               // FIXME: We don't have a reference to the 
object to pass on to listeners
+                               type = 'heading';
+                               if ( !type || 
!this.changeListeners.hasOwnProperty( type ) ) {
+                                       // Do nothing for text node changes, or 
if no-one's registered for this type
+                                       continue;
+                               }
+                               for ( k = 0, klen = this.changeListeners[ type 
].length; k < klen; k++ ) {
+                                       listeners.push( [ this.changeListeners[ 
type ][ k ], operation.type, { from: operation.from, to: operation.to } ] );
+                               }
+                               break;
+                       case 'replace':
+                               for ( j = 0, jlen = operation.insert.length; j 
< jlen; j++ ) {
+                                       type = operation.insert[ j ].type;
+                                       if ( !type || 
!this.changeListeners.hasOwnProperty( type ) ) {
+                                               // Do nothing for text node 
changes, or if no-one's registered for this type
+                                               continue;
+                                       }
+                                       for ( k = 0, klen = 
this.changeListeners[ type ].length; k < klen; k++ ) {
+                                               listeners.push( [ 
this.changeListeners[ type ][ k ], 'insert', operation.insert[ j ] ] );
+                                       }
+                               }
+                               break;
+                       case 'replaceMetadata':
+                               // FIXME: Implement this.
+                               continue;
+                       default:
+                               throw new Error( 'Unrecognised operation type: 
' + operation );
+
+               }
+
+       }
+
+       if ( !listeners.length ) {
+               return;
+       }
+
+       for ( i = 0, ilen = listeners.length; i < ilen; i++ ) {
+               listeners[ i ][ 0 ]( listeners[ i ][ 1 ], listeners[ i ][ 2 ] );
+       }
+};
diff --git a/tests/dm/ve.dm.Document.test.js b/tests/dm/ve.dm.Document.test.js
index e7153a2..cf57d44 100644
--- a/tests/dm/ve.dm.Document.test.js
+++ b/tests/dm/ve.dm.Document.test.js
@@ -1269,3 +1269,13 @@
                }
        }
 } );
+
+QUnit.test( 'listening to transactions', function () {
+       var testDocument = ve.dm.example.createExampleDocument(),
+               txBuilder = new ve.dm.TransactionBuilder();
+       txBuilder.pushRetain( 1 );
+       txBuilder.pushReplace( testDocument, 1, 0, [ 'H', 'e', 'l', 'l', 'o' ] 
);
+       // FIXME: WRITEME
+       testDocument.commit( txBuilder.getTransaction() );
+       // FIXME: WRITEME
+} );

-- 
To view, visit https://gerrit.wikimedia.org/r/350965
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6b397e792bddc002e78fd7d2b06d509f3da2569d
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Jforrester <jforres...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to