Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js |
— | — | @@ -103,6 +103,9 @@ |
104 | 104 | } else { |
105 | 105 | // Return to the parent node |
106 | 106 | currentNode = currentNode.getParent(); |
| 107 | + if ( currentNode == null ) { |
| 108 | + throw 'createNodesFromData() received unbalanced data: found closing without matching opening at index ' + i; |
| 109 | + } |
107 | 110 | } |
108 | 111 | } else { |
109 | 112 | // It's content, let's start tracking the length |
Index: trunk/extensions/VisualEditor/modules/es/es.TransactionProcessor.js |
— | — | @@ -164,8 +164,8 @@ |
165 | 165 | } |
166 | 166 | } |
167 | 167 | if ( max > 0 ) { |
168 | | - for ( i = 0; i < max - 1; i++ ) { |
169 | | - node = node.getParent(); |
| 168 | + for ( i = 0; i < max; i++ ) { |
| 169 | + node = node.getParent() || node; |
170 | 170 | } |
171 | 171 | } |
172 | 172 | return node; |
— | — | @@ -258,25 +258,28 @@ |
259 | 259 | es.TransactionProcessor.prototype.insert = function( op ) { |
260 | 260 | var node, |
261 | 261 | index, |
262 | | - offset; |
| 262 | + offset, |
| 263 | + scope; |
263 | 264 | |
264 | | - if ( es.DocumentModel.isStructuralOffset( this.model.data, this.cursor ) && this.cursor != this.model.data.length ) { |
265 | | - // FIXME: This fails when inserting something like </list><list> between 2 list items |
266 | | - // @see test #30 in es.TransactionProcessor.test.js |
| 265 | + node = this.model.getNodeFromOffset( this.cursor ); |
| 266 | + scope = this.getScope( node, op.data ); |
| 267 | + // We can take a shortcut if we're inserting an enclosed piece of structural data at a structural offset |
| 268 | + // that isn't at the end of the document. Check for scope == node to ensure the inserted data doesn't try |
| 269 | + // to close its containing element |
| 270 | + if ( es.DocumentModel.isStructuralOffset( this.model.data, this.cursor ) && this.cursor != this.model.data.length |
| 271 | + && scope == node |
| 272 | + ) { |
| 273 | + // We're inserting an enclosed element into something else, so we don't have to rebuild |
| 274 | + // the parent node. Just build a node from the inserted data and stick it in |
267 | 275 | es.insertIntoArray( this.model.data, this.cursor, op.data ); |
268 | 276 | this.applyAnnotations( this.cursor + op.data.length ); |
269 | | - node = this.model.getNodeFromOffset( this.cursor ); |
270 | 277 | offset = this.model.getOffsetFromNode( node ); |
271 | 278 | index = node.getIndexFromOffset( this.cursor - offset ); |
272 | 279 | this.rebuildNodes( op.data, null, node, index ); |
273 | 280 | } else { |
274 | | - node = this.model.getNodeFromOffset( this.cursor ); |
275 | | - if ( node.getParent() === this.model ) { |
276 | | - offset = this.model.getOffsetFromNode( node ); |
277 | | - } else { |
278 | | - node = this.getScope( node, op.data ); |
279 | | - offset = this.model.getOffsetFromNode( node ); |
280 | | - } |
| 281 | + // Rebuild scope, which is the node that encloses everything we might have to rebuild |
| 282 | + node = scope; |
| 283 | + offset = this.model.getOffsetFromNode( node ); |
281 | 284 | if ( es.DocumentModel.containsElementData( op.data ) ) { |
282 | 285 | // Perform insert on linear data model |
283 | 286 | es.insertIntoArray( this.model.data, this.cursor, op.data ); |