https://www.mediawiki.org/wiki/Special:Code/MediaWiki/103516
Revision: 103516
Author: tparscal
Date: 2011-11-17 22:42:18 +0000 (Thu, 17 Nov 2011)
Log Message:
-----------
Renamed es.Transaction to es.TransactionModel
Modified Paths:
--------------
trunk/extensions/VisualEditor/demo/index.html
trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
trunk/extensions/VisualEditor/tests/es/index.html
Added Paths:
-----------
trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
trunk/extensions/VisualEditor/modules/es/models/es.TransactionModel.js
trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
Removed Paths:
-------------
trunk/extensions/VisualEditor/modules/es/es.Transaction.js
trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
Modified: trunk/extensions/VisualEditor/demo/index.html
===================================================================
--- trunk/extensions/VisualEditor/demo/index.html 2011-11-17 22:16:22 UTC
(rev 103515)
+++ trunk/extensions/VisualEditor/demo/index.html 2011-11-17 22:42:18 UTC
(rev 103516)
@@ -58,7 +58,6 @@
<script src="../modules/es/es.Html.js"></script>
<script src="../modules/es/es.Position.js"></script>
<script src="../modules/es/es.Range.js"></script>
- <script src="../modules/es/es.Transaction.js"></script>
<script src="../modules/es/es.TransactionProcessor.js"></script>
<!-- Serializers -->
@@ -90,6 +89,7 @@
<script src="../modules/es/models/es.TableRowModel.js"></script>
<script
src="../modules/es/models/es.TableCellModel.js"></script>
<script src="../modules/es/models/es.HeadingModel.js"></script>
+ <script
src="../modules/es/models/es.TransactionModel.js"></script>
<!-- Views -->
<script src="../modules/es/views/es.SurfaceView.js"></script>
Deleted: trunk/extensions/VisualEditor/modules/es/es.Transaction.js
===================================================================
--- trunk/extensions/VisualEditor/modules/es/es.Transaction.js 2011-11-17
22:16:22 UTC (rev 103515)
+++ trunk/extensions/VisualEditor/modules/es/es.Transaction.js 2011-11-17
22:42:18 UTC (rev 103516)
@@ -1,137 +0,0 @@
-/**
- * Creates an es.Transaction object.
- *
- * @class
- * @constructor
- * @param {Object[]} operations List of operations
- */
-es.Transaction = function( operations ) {
- this.operations = es.isArray( operations ) ? operations : [];
-};
-
-/* Methods */
-
-/**
- * Gets a list of all operations.
- *
- * @method
- * @returns {Object[]} List of operations
- */
-es.Transaction.prototype.getOperations = function() {
- return this.operations;
-};
-
-/**
- * Merges consecutive operations of the same type.
- *
- * @method
- */
-es.Transaction.prototype.optimize = function() {
- for ( var i = 0; i < this.operations.length - 1; i++ ) {
- var a = this.operations[i];
- var b = this.operations[i + 1];
- if ( a.type === b.type ) {
- switch ( a.type ) {
- case 'retain':
- a.length += b.length;
- this.operations.splice( i + 1, 1 );
- i--;
- break;
- case 'insert':
- case 'remove':
- a.data = a.data.concat( b.data );
- this.operations.splice( i + 1, 1 );
- i--;
- break;
- }
- }
- }
-};
-
-/**
- * Adds a retain operation.
- *
- * @method
- * @param {Integer} length Length of content data to retain
- */
-es.Transaction.prototype.pushRetain = function( length ) {
- this.operations.push( {
- 'type': 'retain',
- 'length': length
- } );
-};
-
-/**
- * Adds an insertion operation.
- *
- * @method
- * @param {Array} data Data to retain
- */
-es.Transaction.prototype.pushInsert = function( data ) {
- this.operations.push( {
- 'type': 'insert',
- 'data': data
- } );
-};
-
-/**
- * Adds a removal operation.
- *
- * @method
- * @param {Array} data Data to remove
- */
-es.Transaction.prototype.pushRemove = function( data ) {
- this.operations.push( {
- 'type': 'remove',
- 'data': data
- } );
-};
-
-/**
- * Adds an element attribute change operation.
- *
- * @method
- * @param {String} method Method to use, either "set" or "clear"
- * @param {String} key Name of attribute to change
- * @param {Mixed} value Value to set attribute to, or value of attribute being
cleared
- */
-es.Transaction.prototype.pushChangeElementAttribute = function( method, key,
value ) {
- this.operations.push( {
- 'type': 'attribute',
- 'method': method,
- 'key': key,
- 'value': value
- } );
-};
-
-/**
- * Adds a start annotating operation.
- *
- * @method
- * @param {String} method Method to use, either "set" or "clear"
- * @param {Object} annotation Annotation object to start setting or clearing
from content data
- */
-es.Transaction.prototype.pushStartAnnotating = function( method, annotation ) {
- this.operations.push( {
- 'type': 'annotate',
- 'method': method,
- 'bias': 'start',
- 'annotation': annotation
- } );
-};
-
-/**
- * Adds a stop annotating operation.
- *
- * @method
- * @param {String} method Method to use, either "set" or "clear"
- * @param {Object} annotation Annotation object to stop setting or clearing
from content data
- */
-es.Transaction.prototype.pushStopAnnotating = function( method, annotation ) {
- this.operations.push( {
- 'type': 'annotate',
- 'method': method,
- 'bias': 'stop',
- 'annotation': annotation
- } );
-};
Deleted: trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
===================================================================
--- trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
2011-11-17 22:16:22 UTC (rev 103515)
+++ trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -1,341 +0,0 @@
-/**
- * Creates an es.TransactionProcessor object.
- *
- * @class
- * @constructor
- */
-es.TransactionProcessor = function( model, transaction ) {
- this.model = model;
- this.transaction = transaction;
- this.cursor = 0;
- this.set = [];
- this.clear = [];
-};
-
-/* Static Members */
-
-es.TransactionProcessor.operationMap = {
- // Retain
- 'retain': {
- 'commit': function( op ) {
- this.retain( op );
- },
- 'rollback': function( op ) {
- this.retain( op );
- }
- },
- // Insert
- 'insert': {
- 'commit': function( op ) {
- this.insert( op );
- },
- 'rollback': function( op ) {
- this.remove( op );
- }
- },
- // Remove
- 'remove': {
- 'commit': function( op ) {
- this.remove( op );
- },
- 'rollback': function( op ) {
- this.insert( op );
- }
- },
- // Change element attributes
- 'attribute': {
- 'commit': function( op ) {
- this.attribute( op, false );
- },
- 'rollback': function( op ) {
- this.attribute( op, true );
- }
- },
- // Change content annotations
- 'annotate': {
- 'commit': function( op ) {
- this.mark( op, false );
- },
- 'rollback': function( op ) {
- this.mark( op, true );
- }
- }
-};
-
-/* Static Methods */
-
-es.TransactionProcessor.commit = function( doc, transaction ) {
- var tp = new es.TransactionProcessor( doc, transaction );
- tp.process( 'commit' );
-};
-
-es.TransactionProcessor.rollback = function( doc, transaction ) {
- var tp = new es.TransactionProcessor( doc, transaction );
- tp.process( 'rollback' );
-};
-
-/* Methods */
-
-es.TransactionProcessor.prototype.process = function( method ) {
- var operations = this.transaction.getOperations();
- for ( var i = 0, length = operations.length; i < length; i++ ) {
- var operation = operations[i];
- if ( operation.type in es.TransactionProcessor.operationMap ) {
-
es.TransactionProcessor.operationMap[operation.type][method].call( this,
operation );
- } else {
- throw 'Invalid operation error. Operation type is not
supported: ' + operation.type;
- }
- }
-};
-
-es.TransactionProcessor.prototype.rebuildNodes = function( newData, oldNodes,
parent, index ) {
- var remove = 0;
- if ( oldNodes ) {
- if ( oldNodes[0] === oldNodes[0].getRoot() ) {
- parent = oldNodes[0];
- index = 0;
- remove = parent.getChildren().length;
- } else {
- parent = oldNodes[0].getParent();
- index = parent.indexOf( oldNodes[0] );
- remove = oldNodes.length;
- }
- }
- // Try to perform this in a single operation if possible, this reduces
the number of UI updates
- // TODO: Introduce a global for max argument length - 1024 is also
assumed in es.insertIntoArray
- var newNodes = es.DocumentModel.createNodesFromData( newData );
- if ( newNodes.length < 1024 ) {
- parent.splice.apply( parent, [index, remove].concat( newNodes )
);
- } else {
- parent.splice.apply( parent, [index, remove] );
- // Safe to call with arbitrary length of newNodes
- es.insertIntoArray( parent, index, newNodes );
- }
-};
-
-/**
- * Get the parent node that would be affected by inserting given data into
it's child.
- *
- * This is used when inserting data that closes and reopens one or more parent
nodes into a child
- * node, which requires rebuilding at a higher level.
- *
- * @method
- * @param {es.DocumentNode} node Child node to start from
- * @param {Array} data Data to inspect for closings
- * @returns {es.DocumentNode} Lowest level parent node being affected
- */
-es.TransactionProcessor.prototype.getScope = function( node, data ) {
- var i,
- length,
- level = 0,
- max = 0;
- for ( i = 0, length = data.length; i < length; i++ ) {
- if ( typeof data[i].type === 'string' ) {
- level += data[i].type.charAt( 0 ) === '/' ? 1 : -1;
- max = Math.max( max, level );
- }
- }
- if ( max > 0 ) {
- for ( i = 0; i < max - 1; i++ ) {
- node = node.getParent();
- }
- }
- return node;
-};
-
-es.TransactionProcessor.prototype.applyAnnotations = function( to ) {
- var i,
- j,
- length,
- annotation;
- // Handle annotations
- if ( this.set.length ) {
- for ( i = 0, length = this.set.length; i < length; i++ ) {
- annotation = this.set[i];
- // Auto-build annotation hash
- if ( annotation.hash === undefined ) {
- annotation.hash =
es.DocumentModel.getAnnotationHash( annotation );
- }
- for ( j = this.cursor; j < to; j++ ) {
- // Auto-convert to array
- if ( es.isArray( this.model.data[j] ) ) {
- this.model.data[j].push( annotation );
- } else {
- this.model.data[j] =
[this.model.data[j], annotation];
- }
- }
- }
- }
- if ( this.clear.length ) {
- for ( i = 0, length = this.clear.length; i < length; i++ ) {
- annotation = this.clear[i];
- // Auto-build annotation hash
- if ( annotation.hash === undefined ) {
- annotation.hash =
es.DocumentModel.getAnnotationHash( annotation );
- }
- for ( j = this.cursor; j < to; j++ ) {
- var index =
es.DocumentModel.getIndexOfAnnotation( this.model.data[j], annotation );
- if ( index !== -1 ) {
- this.model.data[j].splice( index, 1 );
- }
- // Auto-convert to string
- if ( this.model.data[j].length === 1 ) {
- this.model.data[j] =
this.model.data[j][0];
- }
- }
- }
- }
-};
-
-es.TransactionProcessor.prototype.retain = function( op ) {
- this.applyAnnotations( this.cursor + op.length );
- this.cursor += op.length;
-};
-
-es.TransactionProcessor.prototype.insert = function( op ) {
- var node,
- index,
- offset;
- if ( es.DocumentModel.isStructuralOffset( this.model.data, this.cursor
) ) {
- es.insertIntoArray( this.model.data, this.cursor, op.data );
- this.applyAnnotations( this.cursor + op.data.length );
- node = this.model.getNodeFromOffset( this.cursor );
- offset = this.model.getOffsetFromNode( node );
- index = node.getIndexFromOffset( this.cursor - offset );
- this.rebuildNodes( op.data, null, node, index );
- } else {
- node = this.model.getNodeFromOffset( this.cursor );
- if ( node.getParent() === this.model ) {
- offset = this.model.getOffsetFromNode( node );
- index = this.model.getIndexFromOffset( this.cursor -
offset );
- } else {
- node = this.getScope( node, op.data );
- offset = this.model.getOffsetFromNode( node );
- index = node.getIndexFromOffset( this.cursor - offset );
- }
- if ( es.DocumentModel.containsElementData( op.data ) ) {
- // Perform insert on linear data model
- es.insertIntoArray( this.model.data, this.cursor,
op.data );
- this.applyAnnotations( this.cursor + op.data.length );
- // Synchronize model tree
- if ( offset === -1 ) {
- throw 'Invalid offset error. Node is not in
model tree';
- }
- this.rebuildNodes(
- this.model.data.slice( offset, offset +
node.getElementLength() + op.data.length ),
- [node]
- );
- } else {
- // Perform insert on linear data model
- // TODO this is duplicated from above
- es.insertIntoArray( this.model.data, this.cursor,
op.data );
- this.applyAnnotations( this.cursor + op.data.length );
- // Update model tree
- node.adjustContentLength( op.data.length, true );
- node.emit( 'update', this.cursor - offset );
- }
- }
- this.cursor += op.data.length;
-};
-
-es.TransactionProcessor.prototype.remove = function( op ) {
- if ( es.DocumentModel.containsElementData( op.data ) ) {
- // Figure out which nodes are covered by the removal
- var ranges = this.model.selectNodes( new es.Range( this.cursor,
this.cursor + op.data.length ) );
- var oldNodes = [], newData = [], firstKeptNode = true,
lastElement;
- for ( var i = 0; i < ranges.length; i++ ) {
- oldNodes.push( ranges[i].node );
- if ( ranges[i].range !== undefined ) {
- // We have to keep part of this node
- if ( firstKeptNode ) {
- // This is the first node we're keeping
- // Keep its opening as well
- newData.push(
ranges[i].node.getElement() );
- firstKeptNode = false;
- }
- // Compute the start and end offset of this node
- // We could do that with getOffsetFromNode() but
- // we already have all the numbers we need so
why would we
- var startOffset =
ranges[i].globalRange.start - ranges[i].range.start,
- endOffset = startOffset +
ranges[i].node.getContentLength(),
- // Get this node's data
- nodeData = this.model.data.slice(
startOffset, endOffset );
- // Remove data covered by the range from
nodeData
- nodeData.splice( ranges[i].range.start,
ranges[i].range.end - ranges[i].range.start );
- // What remains in nodeData is the data we need
to keep
- // Append it to newData
- newData = newData.concat( nodeData );
-
- lastElement = ranges[i].node.getElementType();
- }
- }
- if ( lastElement !== undefined ) {
- // Keep the closing of the last element that was
partially kept
- newData.push( { 'type': '/' + lastElement } );
- }
- // Update the linear model
- this.model.data.splice( this.cursor, op.data.length );
- // Perform the rebuild. This updates the model tree
- this.rebuildNodes( newData, oldNodes );
- } else {
- // We're removing content only. Take a shortcut
- // Get the node we are removing content from
- var node = this.model.getNodeFromOffset( this.cursor );
- // Update model tree
- node.adjustContentLength( -op.data.length, true );
- // Update the linear model
- this.model.data.splice( this.cursor, op.data.length );
- // Emit an update so things sync up
- var offset = this.model.getOffsetFromNode( node );
- node.emit( 'update', this.cursor - offset );
- }
-};
-
-es.TransactionProcessor.prototype.attribute = function( op, invert ) {
- var element = this.model.data[this.cursor];
- if ( element.type === undefined ) {
- throw 'Invalid element error. Can not set attributes on
non-element data.';
- }
- if ( ( op.method === 'set' && !invert ) || ( op.method === 'clear' &&
invert ) ) {
- // Automatically initialize attributes object
- if ( !element.attributes ) {
- element.attributes = {};
- }
- element.attributes[op.key] = op.value;
- } else if ( ( op.method === 'clear' && !invert ) || ( op.method ===
'set' && invert ) ) {
- if ( element.attributes ) {
- delete element.attributes[op.key];
- }
- // Automatically clean up attributes object
- var empty = true;
- for ( var key in element.attributes ) {
- empty = false;
- break;
- }
- if ( empty ) {
- delete element.attributes;
- }
- } else {
- throw 'Invalid method error. Can not operate attributes this
way: ' + method;
- }
-};
-
-es.TransactionProcessor.prototype.mark = function( op, invert ) {
- var target;
- if ( ( op.method === 'set' && !invert ) || ( op.method === 'clear' &&
invert ) ) {
- target = this.set;
- } else if ( ( op.method === 'clear' && !invert ) || ( op.method ===
'set' && invert ) ) {
- target = this.clear;
- } else {
- throw 'Invalid method error. Can not operate attributes this
way: ' + method;
- }
- if ( op.bias === 'start' ) {
- target.push( op.annotation );
- } else if ( op.bias === 'stop' ) {
- var index = es.DocumentModel.getIndexOfAnnotation( target,
op.annotation );
- if ( index === -1 ) {
- throw 'Annotation stack error. Annotation is missing.';
- }
- target.splice( index, 1 );
- }
-};
Added: trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
===================================================================
--- trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
(rev 0)
+++ trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -0,0 +1,341 @@
+/**
+ * Creates an es.TransactionProcessor object.
+ *
+ * @class
+ * @constructor
+ */
+es.TransactionProcessor = function( model, transaction ) {
+ this.model = model;
+ this.transaction = transaction;
+ this.cursor = 0;
+ this.set = [];
+ this.clear = [];
+};
+
+/* Static Members */
+
+es.TransactionProcessor.operationMap = {
+ // Retain
+ 'retain': {
+ 'commit': function( op ) {
+ this.retain( op );
+ },
+ 'rollback': function( op ) {
+ this.retain( op );
+ }
+ },
+ // Insert
+ 'insert': {
+ 'commit': function( op ) {
+ this.insert( op );
+ },
+ 'rollback': function( op ) {
+ this.remove( op );
+ }
+ },
+ // Remove
+ 'remove': {
+ 'commit': function( op ) {
+ this.remove( op );
+ },
+ 'rollback': function( op ) {
+ this.insert( op );
+ }
+ },
+ // Change element attributes
+ 'attribute': {
+ 'commit': function( op ) {
+ this.attribute( op, false );
+ },
+ 'rollback': function( op ) {
+ this.attribute( op, true );
+ }
+ },
+ // Change content annotations
+ 'annotate': {
+ 'commit': function( op ) {
+ this.mark( op, false );
+ },
+ 'rollback': function( op ) {
+ this.mark( op, true );
+ }
+ }
+};
+
+/* Static Methods */
+
+es.TransactionProcessor.commit = function( doc, transaction ) {
+ var tp = new es.TransactionProcessor( doc, transaction );
+ tp.process( 'commit' );
+};
+
+es.TransactionProcessor.rollback = function( doc, transaction ) {
+ var tp = new es.TransactionProcessor( doc, transaction );
+ tp.process( 'rollback' );
+};
+
+/* Methods */
+
+es.TransactionProcessor.prototype.process = function( method ) {
+ var operations = this.transaction.getOperations();
+ for ( var i = 0, length = operations.length; i < length; i++ ) {
+ var operation = operations[i];
+ if ( operation.type in es.TransactionProcessor.operationMap ) {
+
es.TransactionProcessor.operationMap[operation.type][method].call( this,
operation );
+ } else {
+ throw 'Invalid operation error. Operation type is not
supported: ' + operation.type;
+ }
+ }
+};
+
+es.TransactionProcessor.prototype.rebuildNodes = function( newData, oldNodes,
parent, index ) {
+ var remove = 0;
+ if ( oldNodes ) {
+ if ( oldNodes[0] === oldNodes[0].getRoot() ) {
+ parent = oldNodes[0];
+ index = 0;
+ remove = parent.getChildren().length;
+ } else {
+ parent = oldNodes[0].getParent();
+ index = parent.indexOf( oldNodes[0] );
+ remove = oldNodes.length;
+ }
+ }
+ // Try to perform this in a single operation if possible, this reduces
the number of UI updates
+ // TODO: Introduce a global for max argument length - 1024 is also
assumed in es.insertIntoArray
+ var newNodes = es.DocumentModel.createNodesFromData( newData );
+ if ( newNodes.length < 1024 ) {
+ parent.splice.apply( parent, [index, remove].concat( newNodes )
);
+ } else {
+ parent.splice.apply( parent, [index, remove] );
+ // Safe to call with arbitrary length of newNodes
+ es.insertIntoArray( parent, index, newNodes );
+ }
+};
+
+/**
+ * Get the parent node that would be affected by inserting given data into
it's child.
+ *
+ * This is used when inserting data that closes and reopens one or more parent
nodes into a child
+ * node, which requires rebuilding at a higher level.
+ *
+ * @method
+ * @param {es.DocumentNode} node Child node to start from
+ * @param {Array} data Data to inspect for closings
+ * @returns {es.DocumentNode} Lowest level parent node being affected
+ */
+es.TransactionProcessor.prototype.getScope = function( node, data ) {
+ var i,
+ length,
+ level = 0,
+ max = 0;
+ for ( i = 0, length = data.length; i < length; i++ ) {
+ if ( typeof data[i].type === 'string' ) {
+ level += data[i].type.charAt( 0 ) === '/' ? 1 : -1;
+ max = Math.max( max, level );
+ }
+ }
+ if ( max > 0 ) {
+ for ( i = 0; i < max - 1; i++ ) {
+ node = node.getParent();
+ }
+ }
+ return node;
+};
+
+es.TransactionProcessor.prototype.applyAnnotations = function( to ) {
+ var i,
+ j,
+ length,
+ annotation;
+ // Handle annotations
+ if ( this.set.length ) {
+ for ( i = 0, length = this.set.length; i < length; i++ ) {
+ annotation = this.set[i];
+ // Auto-build annotation hash
+ if ( annotation.hash === undefined ) {
+ annotation.hash =
es.DocumentModel.getAnnotationHash( annotation );
+ }
+ for ( j = this.cursor; j < to; j++ ) {
+ // Auto-convert to array
+ if ( es.isArray( this.model.data[j] ) ) {
+ this.model.data[j].push( annotation );
+ } else {
+ this.model.data[j] =
[this.model.data[j], annotation];
+ }
+ }
+ }
+ }
+ if ( this.clear.length ) {
+ for ( i = 0, length = this.clear.length; i < length; i++ ) {
+ annotation = this.clear[i];
+ // Auto-build annotation hash
+ if ( annotation.hash === undefined ) {
+ annotation.hash =
es.DocumentModel.getAnnotationHash( annotation );
+ }
+ for ( j = this.cursor; j < to; j++ ) {
+ var index =
es.DocumentModel.getIndexOfAnnotation( this.model.data[j], annotation );
+ if ( index !== -1 ) {
+ this.model.data[j].splice( index, 1 );
+ }
+ // Auto-convert to string
+ if ( this.model.data[j].length === 1 ) {
+ this.model.data[j] =
this.model.data[j][0];
+ }
+ }
+ }
+ }
+};
+
+es.TransactionProcessor.prototype.retain = function( op ) {
+ this.applyAnnotations( this.cursor + op.length );
+ this.cursor += op.length;
+};
+
+es.TransactionProcessor.prototype.insert = function( op ) {
+ var node,
+ index,
+ offset;
+ if ( es.DocumentModel.isStructuralOffset( this.model.data, this.cursor
) ) {
+ es.insertIntoArray( this.model.data, this.cursor, op.data );
+ this.applyAnnotations( this.cursor + op.data.length );
+ node = this.model.getNodeFromOffset( this.cursor );
+ offset = this.model.getOffsetFromNode( node );
+ index = node.getIndexFromOffset( this.cursor - offset );
+ this.rebuildNodes( op.data, null, node, index );
+ } else {
+ node = this.model.getNodeFromOffset( this.cursor );
+ if ( node.getParent() === this.model ) {
+ offset = this.model.getOffsetFromNode( node );
+ index = this.model.getIndexFromOffset( this.cursor -
offset );
+ } else {
+ node = this.getScope( node, op.data );
+ offset = this.model.getOffsetFromNode( node );
+ index = node.getIndexFromOffset( this.cursor - offset );
+ }
+ if ( es.DocumentModel.containsElementData( op.data ) ) {
+ // Perform insert on linear data model
+ es.insertIntoArray( this.model.data, this.cursor,
op.data );
+ this.applyAnnotations( this.cursor + op.data.length );
+ // Synchronize model tree
+ if ( offset === -1 ) {
+ throw 'Invalid offset error. Node is not in
model tree';
+ }
+ this.rebuildNodes(
+ this.model.data.slice( offset, offset +
node.getElementLength() + op.data.length ),
+ [node]
+ );
+ } else {
+ // Perform insert on linear data model
+ // TODO this is duplicated from above
+ es.insertIntoArray( this.model.data, this.cursor,
op.data );
+ this.applyAnnotations( this.cursor + op.data.length );
+ // Update model tree
+ node.adjustContentLength( op.data.length, true );
+ node.emit( 'update', this.cursor - offset );
+ }
+ }
+ this.cursor += op.data.length;
+};
+
+es.TransactionProcessor.prototype.remove = function( op ) {
+ if ( es.DocumentModel.containsElementData( op.data ) ) {
+ // Figure out which nodes are covered by the removal
+ var ranges = this.model.selectNodes( new es.Range( this.cursor,
this.cursor + op.data.length ) );
+ var oldNodes = [], newData = [], firstKeptNode = true,
lastElement;
+ for ( var i = 0; i < ranges.length; i++ ) {
+ oldNodes.push( ranges[i].node );
+ if ( ranges[i].range !== undefined ) {
+ // We have to keep part of this node
+ if ( firstKeptNode ) {
+ // This is the first node we're keeping
+ // Keep its opening as well
+ newData.push(
ranges[i].node.getElement() );
+ firstKeptNode = false;
+ }
+ // Compute the start and end offset of this node
+ // We could do that with getOffsetFromNode() but
+ // we already have all the numbers we need so
why would we
+ var startOffset =
ranges[i].globalRange.start - ranges[i].range.start,
+ endOffset = startOffset +
ranges[i].node.getContentLength(),
+ // Get this node's data
+ nodeData = this.model.data.slice(
startOffset, endOffset );
+ // Remove data covered by the range from
nodeData
+ nodeData.splice( ranges[i].range.start,
ranges[i].range.end - ranges[i].range.start );
+ // What remains in nodeData is the data we need
to keep
+ // Append it to newData
+ newData = newData.concat( nodeData );
+
+ lastElement = ranges[i].node.getElementType();
+ }
+ }
+ if ( lastElement !== undefined ) {
+ // Keep the closing of the last element that was
partially kept
+ newData.push( { 'type': '/' + lastElement } );
+ }
+ // Update the linear model
+ this.model.data.splice( this.cursor, op.data.length );
+ // Perform the rebuild. This updates the model tree
+ this.rebuildNodes( newData, oldNodes );
+ } else {
+ // We're removing content only. Take a shortcut
+ // Get the node we are removing content from
+ var node = this.model.getNodeFromOffset( this.cursor );
+ // Update model tree
+ node.adjustContentLength( -op.data.length, true );
+ // Update the linear model
+ this.model.data.splice( this.cursor, op.data.length );
+ // Emit an update so things sync up
+ var offset = this.model.getOffsetFromNode( node );
+ node.emit( 'update', this.cursor - offset );
+ }
+};
+
+es.TransactionProcessor.prototype.attribute = function( op, invert ) {
+ var element = this.model.data[this.cursor];
+ if ( element.type === undefined ) {
+ throw 'Invalid element error. Can not set attributes on
non-element data.';
+ }
+ if ( ( op.method === 'set' && !invert ) || ( op.method === 'clear' &&
invert ) ) {
+ // Automatically initialize attributes object
+ if ( !element.attributes ) {
+ element.attributes = {};
+ }
+ element.attributes[op.key] = op.value;
+ } else if ( ( op.method === 'clear' && !invert ) || ( op.method ===
'set' && invert ) ) {
+ if ( element.attributes ) {
+ delete element.attributes[op.key];
+ }
+ // Automatically clean up attributes object
+ var empty = true;
+ for ( var key in element.attributes ) {
+ empty = false;
+ break;
+ }
+ if ( empty ) {
+ delete element.attributes;
+ }
+ } else {
+ throw 'Invalid method error. Can not operate attributes this
way: ' + method;
+ }
+};
+
+es.TransactionProcessor.prototype.mark = function( op, invert ) {
+ var target;
+ if ( ( op.method === 'set' && !invert ) || ( op.method === 'clear' &&
invert ) ) {
+ target = this.set;
+ } else if ( ( op.method === 'clear' && !invert ) || ( op.method ===
'set' && invert ) ) {
+ target = this.clear;
+ } else {
+ throw 'Invalid method error. Can not operate attributes this
way: ' + method;
+ }
+ if ( op.bias === 'start' ) {
+ target.push( op.annotation );
+ } else if ( op.bias === 'stop' ) {
+ var index = es.DocumentModel.getIndexOfAnnotation( target,
op.annotation );
+ if ( index === -1 ) {
+ throw 'Annotation stack error. Annotation is missing.';
+ }
+ target.splice( index, 1 );
+ }
+};
Modified: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
===================================================================
--- trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
2011-11-17 22:16:22 UTC (rev 103515)
+++ trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -665,7 +665,7 @@
* @method
* @param {Integer} offset
* @param {Array} data
- * @returns {es.Transaction}
+ * @returns {es.TransactionModel}
*/
es.DocumentModel.prototype.prepareInsertion = function( offset, data ) {
/**
@@ -719,7 +719,7 @@
return workingData || data;
}
- var tx = new es.Transaction(),
+ var tx = new es.TransactionModel(),
insertedData = data, // may be cloned and modified
isStructuralLoc,
wrappingElementType;
@@ -823,7 +823,7 @@
*
* @method
* @param {es.Range} range
- * @returns {es.Transaction}
+ * @returns {es.TransactionModel}
*/
es.DocumentModel.prototype.prepareRemoval = function( range ) {
@@ -857,7 +857,7 @@
return true;
}
- var tx = new es.Transaction(), selectedNodes, selectedNode, startNode,
endNode, i;
+ var tx = new es.TransactionModel(), selectedNodes, selectedNode,
startNode, endNode, i;
range.normalize();
if ( range.start === range.end ) {
// Empty range, nothing to do
@@ -920,10 +920,10 @@
* Generates a transaction which annotates content within a given range.
*
* @method
- * @returns {es.Transaction}
+ * @returns {es.TransactionModel}
*/
es.DocumentModel.prototype.prepareContentAnnotation = function( range, method,
annotation ) {
- var tx = new es.Transaction();
+ var tx = new es.TransactionModel();
range.normalize();
if ( annotation.hash === undefined ) {
annotation.hash = es.DocumentModel.getAnnotationHash(
annotation );
@@ -983,10 +983,10 @@
* Generates a transaction which changes attributes on an element at a given
offset.
*
* @method
- * @returns {es.Transaction}
+ * @returns {es.TransactionModel}
*/
es.DocumentModel.prototype.prepareElementAttributeChange = function( offset,
method, key, value ) {
- var tx = new es.Transaction();
+ var tx = new es.TransactionModel();
if ( offset ) {
tx.pushRetain( offset );
}
@@ -1008,7 +1008,7 @@
* Applies a transaction to the content data.
*
* @method
- * @param {es.Transaction}
+ * @param {es.TransactionModel}
*/
es.DocumentModel.prototype.commit = function( transaction ) {
es.TransactionProcessor.commit( this, transaction );
@@ -1018,7 +1018,7 @@
* Reverses a transaction's effects on the content data.
*
* @method
- * @param {es.Transaction}
+ * @param {es.TransactionModel}
*/
es.DocumentModel.prototype.rollback = function( transaction ) {
es.TransactionProcessor.rollback( this, transaction );
Copied: trunk/extensions/VisualEditor/modules/es/models/es.TransactionModel.js
(from rev 103498, trunk/extensions/VisualEditor/modules/es/es.Transaction.js)
===================================================================
--- trunk/extensions/VisualEditor/modules/es/models/es.TransactionModel.js
(rev 0)
+++ trunk/extensions/VisualEditor/modules/es/models/es.TransactionModel.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -0,0 +1,150 @@
+/**
+ * Creates an es.TransactionModel object.
+ *
+ * @class
+ * @constructor
+ * @param {Object[]} operations List of operations
+ */
+es.TransactionModel = function( operations ) {
+ this.operations = es.isArray( operations ) ? operations : [];
+ this.lengthDiff = 0;
+};
+
+/* Methods */
+
+/**
+ * Gets a list of all operations.
+ *
+ * @method
+ * @returns {Object[]} List of operations
+ */
+es.TransactionModel.prototype.getOperations = function() {
+ return this.operations;
+};
+
+/**
+ * Gets the difference in content length this transaction will cause if
applied.
+ *
+ * @method
+ * @returns {Integer} Difference in content length
+ */
+es.TransactionModel.prototype.getLengthDiff = function() {
+ return this.lengthDiff;
+};
+
+/**
+ * Merges consecutive operations of the same type.
+ *
+ * @method
+ */
+es.TransactionModel.prototype.optimize = function() {
+ for ( var i = 0; i < this.operations.length - 1; i++ ) {
+ var a = this.operations[i];
+ var b = this.operations[i + 1];
+ if ( a.type === b.type ) {
+ switch ( a.type ) {
+ case 'retain':
+ a.length += b.length;
+ this.operations.splice( i + 1, 1 );
+ i--;
+ break;
+ case 'insert':
+ case 'remove':
+ a.data = a.data.concat( b.data );
+ this.operations.splice( i + 1, 1 );
+ i--;
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Adds a retain operation.
+ *
+ * @method
+ * @param {Integer} length Length of content data to retain
+ */
+es.TransactionModel.prototype.pushRetain = function( length ) {
+ this.operations.push( {
+ 'type': 'retain',
+ 'length': length
+ } );
+};
+
+/**
+ * Adds an insertion operation.
+ *
+ * @method
+ * @param {Array} data Data to retain
+ */
+es.TransactionModel.prototype.pushInsert = function( data ) {
+ this.operations.push( {
+ 'type': 'insert',
+ 'data': data
+ } );
+ this.lengthDiff += data.length;
+};
+
+/**
+ * Adds a removal operation.
+ *
+ * @method
+ * @param {Array} data Data to remove
+ */
+es.TransactionModel.prototype.pushRemove = function( data ) {
+ this.operations.push( {
+ 'type': 'remove',
+ 'data': data
+ } );
+ this.lengthDiff -= data.length;
+};
+
+/**
+ * Adds an element attribute change operation.
+ *
+ * @method
+ * @param {String} method Method to use, either "set" or "clear"
+ * @param {String} key Name of attribute to change
+ * @param {Mixed} value Value to set attribute to, or value of attribute being
cleared
+ */
+es.TransactionModel.prototype.pushChangeElementAttribute = function( method,
key, value ) {
+ this.operations.push( {
+ 'type': 'attribute',
+ 'method': method,
+ 'key': key,
+ 'value': value
+ } );
+};
+
+/**
+ * Adds a start annotating operation.
+ *
+ * @method
+ * @param {String} method Method to use, either "set" or "clear"
+ * @param {Object} annotation Annotation object to start setting or clearing
from content data
+ */
+es.TransactionModel.prototype.pushStartAnnotating = function( method,
annotation ) {
+ this.operations.push( {
+ 'type': 'annotate',
+ 'method': method,
+ 'bias': 'start',
+ 'annotation': annotation
+ } );
+};
+
+/**
+ * Adds a stop annotating operation.
+ *
+ * @method
+ * @param {String} method Method to use, either "set" or "clear"
+ * @param {Object} annotation Annotation object to stop setting or clearing
from content data
+ */
+es.TransactionModel.prototype.pushStopAnnotating = function( method,
annotation ) {
+ this.operations.push( {
+ 'type': 'annotate',
+ 'method': method,
+ 'bias': 'stop',
+ 'annotation': annotation
+ } );
+};
Deleted: trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
===================================================================
--- trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
2011-11-17 22:16:22 UTC (rev 103515)
+++ trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -1,246 +0,0 @@
-module( 'es' );
-
-test( 'es.TransactionProcessor', 18, function() {
- var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj );
-
- // FIXME: These tests shouldn't use prepareFoo() because those functions
- // normalize the transactions they create and are tested separately.
- // We should be creating transactions directly and feeding those into
- // commit()/rollback() --Roan
- var elementAttributeChange =
documentModel.prepareElementAttributeChange(
- 0, 'set', 'test', 1
- );
-
- // Test 1
- es.TransactionProcessor.commit( documentModel, elementAttributeChange );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph', 'attributes': { 'test': 1 } },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'commit applies an element attribute change transaction to the
content'
- );
-
- // Test 2
- es.TransactionProcessor.rollback( documentModel, elementAttributeChange
);
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'rollback reverses the effect of an element attribute change
transaction on the content'
- );
-
- var contentAnnotation = documentModel.prepareContentAnnotation(
- new es.Range( 1, 4 ), 'set', { 'type': 'textStyle/bold' }
- );
-
- // Test 3
- es.TransactionProcessor.commit( documentModel, contentAnnotation );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- ['a', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- [
- 'c',
- { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' },
- { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }
- ],
- { 'type': '/paragraph' }
- ],
- 'commit applies a content annotation transaction to the content'
- );
-
- // Test 4
- es.TransactionProcessor.rollback( documentModel, contentAnnotation );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'rollback reverses the effect of a content annotation
transaction on the content'
- );
-
- var insertion = documentModel.prepareInsertion( 3, ['d'] );
-
- // Test 5
- es.TransactionProcessor.commit( documentModel, insertion );
- deepEqual(
- documentModel.getData( new es.Range( 0, 6 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- 'd',
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'commit applies an insertion transaction to the content'
- );
-
- // Test 6
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- [
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- 'd',
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
- ],
- 'commit keeps model tree up to date with insertions'
- );
-
- // Test 7
- es.TransactionProcessor.rollback( documentModel, insertion );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'rollback reverses the effect of an insertion transaction on
the content'
- );
-
- // Test 8
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- [
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
- ],
- 'rollback keeps model tree up to date with insertions'
- );
-
- var removal = documentModel.prepareRemoval( new es.Range( 2, 4 ) );
-
- // Test 9
- es.TransactionProcessor.commit( documentModel, removal );
- deepEqual(
- documentModel.getData( new es.Range( 0, 3 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- { 'type': '/paragraph' }
- ],
- 'commit applies a removal transaction to the content'
- );
-
- // Test 10
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- ['a'],
- 'commit keeps model tree up to date with removals'
- );
-
- // Test 11
- es.TransactionProcessor.rollback( documentModel, removal );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'rollback reverses the effect of a removal transaction on the
content'
- );
-
- // Test 12
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- [
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
- ],
- 'rollback keeps model tree up to date with removals'
- );
-
- var paragraphBreak = documentModel.prepareInsertion(
- 2, [{ 'type': '/paragraph' }, { 'type': 'paragraph' }]
- );
-
- // Test 13
- es.TransactionProcessor.commit( documentModel, paragraphBreak );
- deepEqual(
- documentModel.getData( new es.Range( 0, 7 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- { 'type': '/paragraph' },
- { 'type': 'paragraph' },
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'commit applies an insertion transaction that splits the
paragraph'
- );
-
- // Test 14
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- ['a'],
- 'commit keeps model tree up to date with paragraph split
(paragraph 1)'
- );
-
- // Test 15
- deepEqual(
- documentModel.getChildren()[1].getContent(),
- [
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
- ],
- 'commit keeps model tree up to date with paragraph split
(paragraph 2)'
- );
-
- // Test 16
- es.TransactionProcessor.rollback( documentModel, paragraphBreak );
- deepEqual(
- documentModel.getData( new es.Range( 0, 5 ) ),
- [
- { 'type': 'paragraph' },
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
- { 'type': '/paragraph' }
- ],
- 'rollback reverses the effect of a paragraph split on the
content'
- );
-
- // Test 17
- deepEqual(
- documentModel.getChildren()[0].getContent(),
- [
- 'a',
- ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
- ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
- ],
- 'rollback keeps model tree up to date with paragraph split
(paragraphs are merged back)'
- );
-
- // Test 18
- deepEqual(
- documentModel.getChildren()[1].getElementType(),
- 'table',
- 'rollback keeps model tree up to date with paragraph split
(table follows the paragraph)'
- );
-} );
Added: trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
===================================================================
--- trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
(rev 0)
+++ trunk/extensions/VisualEditor/tests/es/es.TransactionProcessor.test.js
2011-11-17 22:42:18 UTC (rev 103516)
@@ -0,0 +1,246 @@
+module( 'es' );
+
+test( 'es.TransactionProcessor', 18, function() {
+ var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj );
+
+ // FIXME: These tests shouldn't use prepareFoo() because those functions
+ // normalize the transactions they create and are tested separately.
+ // We should be creating transactions directly and feeding those into
+ // commit()/rollback() --Roan
+ var elementAttributeChange =
documentModel.prepareElementAttributeChange(
+ 0, 'set', 'test', 1
+ );
+
+ // Test 1
+ es.TransactionProcessor.commit( documentModel, elementAttributeChange );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph', 'attributes': { 'test': 1 } },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'commit applies an element attribute change transaction to the
content'
+ );
+
+ // Test 2
+ es.TransactionProcessor.rollback( documentModel, elementAttributeChange
);
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'rollback reverses the effect of an element attribute change
transaction on the content'
+ );
+
+ var contentAnnotation = documentModel.prepareContentAnnotation(
+ new es.Range( 1, 4 ), 'set', { 'type': 'textStyle/bold' }
+ );
+
+ // Test 3
+ es.TransactionProcessor.commit( documentModel, contentAnnotation );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ ['a', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ [
+ 'c',
+ { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' },
+ { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }
+ ],
+ { 'type': '/paragraph' }
+ ],
+ 'commit applies a content annotation transaction to the content'
+ );
+
+ // Test 4
+ es.TransactionProcessor.rollback( documentModel, contentAnnotation );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'rollback reverses the effect of a content annotation
transaction on the content'
+ );
+
+ var insertion = documentModel.prepareInsertion( 3, ['d'] );
+
+ // Test 5
+ es.TransactionProcessor.commit( documentModel, insertion );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 6 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ 'd',
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'commit applies an insertion transaction to the content'
+ );
+
+ // Test 6
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ [
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ 'd',
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
+ ],
+ 'commit keeps model tree up to date with insertions'
+ );
+
+ // Test 7
+ es.TransactionProcessor.rollback( documentModel, insertion );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'rollback reverses the effect of an insertion transaction on
the content'
+ );
+
+ // Test 8
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ [
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
+ ],
+ 'rollback keeps model tree up to date with insertions'
+ );
+
+ var removal = documentModel.prepareRemoval( new es.Range( 2, 4 ) );
+
+ // Test 9
+ es.TransactionProcessor.commit( documentModel, removal );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 3 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ { 'type': '/paragraph' }
+ ],
+ 'commit applies a removal transaction to the content'
+ );
+
+ // Test 10
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ ['a'],
+ 'commit keeps model tree up to date with removals'
+ );
+
+ // Test 11
+ es.TransactionProcessor.rollback( documentModel, removal );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'rollback reverses the effect of a removal transaction on the
content'
+ );
+
+ // Test 12
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ [
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
+ ],
+ 'rollback keeps model tree up to date with removals'
+ );
+
+ var paragraphBreak = documentModel.prepareInsertion(
+ 2, [{ 'type': '/paragraph' }, { 'type': 'paragraph' }]
+ );
+
+ // Test 13
+ es.TransactionProcessor.commit( documentModel, paragraphBreak );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 7 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ { 'type': '/paragraph' },
+ { 'type': 'paragraph' },
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'commit applies an insertion transaction that splits the
paragraph'
+ );
+
+ // Test 14
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ ['a'],
+ 'commit keeps model tree up to date with paragraph split
(paragraph 1)'
+ );
+
+ // Test 15
+ deepEqual(
+ documentModel.getChildren()[1].getContent(),
+ [
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
+ ],
+ 'commit keeps model tree up to date with paragraph split
(paragraph 2)'
+ );
+
+ // Test 16
+ es.TransactionProcessor.rollback( documentModel, paragraphBreak );
+ deepEqual(
+ documentModel.getData( new es.Range( 0, 5 ) ),
+ [
+ { 'type': 'paragraph' },
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }],
+ { 'type': '/paragraph' }
+ ],
+ 'rollback reverses the effect of a paragraph split on the
content'
+ );
+
+ // Test 17
+ deepEqual(
+ documentModel.getChildren()[0].getContent(),
+ [
+ 'a',
+ ['b', { 'type': 'textStyle/bold', 'hash':
'#textStyle/bold' }],
+ ['c', { 'type': 'textStyle/italic', 'hash':
'#textStyle/italic' }]
+ ],
+ 'rollback keeps model tree up to date with paragraph split
(paragraphs are merged back)'
+ );
+
+ // Test 18
+ deepEqual(
+ documentModel.getChildren()[1].getElementType(),
+ 'table',
+ 'rollback keeps model tree up to date with paragraph split
(table follows the paragraph)'
+ );
+} );
Modified: trunk/extensions/VisualEditor/tests/es/index.html
===================================================================
--- trunk/extensions/VisualEditor/tests/es/index.html 2011-11-17 22:16:22 UTC
(rev 103515)
+++ trunk/extensions/VisualEditor/tests/es/index.html 2011-11-17 22:42:18 UTC
(rev 103516)
@@ -18,7 +18,6 @@
<script src="../../modules/qunit/qunit.js"></script>
<script src="../../modules/es/es.js"></script>
<script src="../../modules/es/es.Range.js"></script>
- <script src="../../modules/es/es.Transaction.js"></script>
<script
src="../../modules/es/es.TransactionProcessor.js"></script>
<!-- Bases -->
@@ -38,6 +37,7 @@
<script
src="../../modules/es/models/es.TableCellModel.js"></script>
<script src="../../modules/es/models/es.TableModel.js"></script>
<script
src="../../modules/es/models/es.TableRowModel.js"></script>
+ <script
src="../../modules/es/models/es.TransactionModel.js"></script>
<!-- Tests -->
<script src="es.testData.js"></script>
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs