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