| Index: trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js |
| — | — | @@ -549,7 +549,7 @@ |
| 550 | 550 | ); |
| 551 | 551 | } ); |
| 552 | 552 | |
| 553 | | -test( 'es.DocumentModel.commit, es.DocumentModel.rollback', /*18*/ 15, function() { |
| | 553 | +test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 18, function() { |
| 554 | 554 | var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj ); |
| 555 | 555 | |
| 556 | 556 | // FIXME: These tests shouldn't use prepareFoo() because those functions |
| — | — | @@ -759,7 +759,6 @@ |
| 760 | 760 | 'commit keeps model tree up to date with paragraph split (paragraph 2)' |
| 761 | 761 | ); |
| 762 | 762 | |
| 763 | | - /* FIXME broken |
| 764 | 763 | // Test 16 |
| 765 | 764 | documentModel.rollback( paragraphBreak ); |
| 766 | 765 | deepEqual( |
| — | — | @@ -791,5 +790,4 @@ |
| 792 | 791 | 'table', |
| 793 | 792 | 'rollback keeps model tree up to date with paragraph split (table follows the paragraph)' |
| 794 | 793 | ); |
| 795 | | - */ |
| 796 | 794 | } ); |
| Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js |
| — | — | @@ -108,6 +108,7 @@ |
| 109 | 109 | ); |
| 110 | 110 | } else { |
| 111 | 111 | // Perform insert on linear data model |
| | 112 | + // TODO this is duplicated from above |
| 112 | 113 | es.insertIntoArray( this.data, this.cursor, op.data ); |
| 113 | 114 | annotate.call( this, this.cursor + op.data.length ); |
| 114 | 115 | // Update model tree |
| — | — | @@ -119,23 +120,55 @@ |
| 120 | 121 | } |
| 121 | 122 | |
| 122 | 123 | function remove( op ) { |
| 123 | | - var elementLeft = es.DocumentModel.isElementData( this.data, this.cursor ), |
| 124 | | - elementRight = es.DocumentModel.isElementData( this.cursor + op.data.length ); |
| 125 | | - if ( elementLeft && elementRight ) { |
| 126 | | - // TODO: Support tree updates when removing whole elements |
| | 124 | + if ( es.DocumentModel.containsElementData( op.data ) ) { |
| | 125 | + // Figure out which nodes are covered by the removal |
| | 126 | + var ranges = this.tree.selectNodes( new es.Range( this.cursor, this.cursor + op.data.length ) ); |
| | 127 | + var oldNodes = [], newData = [], firstKeptNode = true, lastElement; |
| | 128 | + for ( var i = 0; i < ranges.length; i++ ) { |
| | 129 | + oldNodes.push( ranges[i].node ); |
| | 130 | + if ( ranges[i].globalRange !== undefined ) { |
| | 131 | + // We have to keep part of this node |
| | 132 | + if ( firstKeptNode ) { |
| | 133 | + // This is the first node we're keeping |
| | 134 | + // Keep its opening as well |
| | 135 | + newData.push( ranges[i].node.getElement() ); |
| | 136 | + firstKeptNode = false; |
| | 137 | + } |
| | 138 | + |
| | 139 | + // Compute the start and end offset of this node |
| | 140 | + // We could do that with getOffsetFromNode() but |
| | 141 | + // we already have all the numbers we need so why would we |
| | 142 | + var startOffset = ranges[i].globalRange.start - ranges[i].range.start, |
| | 143 | + endOffset = startOffset + ranges[i].node.getContentLength(), |
| | 144 | + // Get this node's data |
| | 145 | + nodeData = this.data.slice( startOffset, endOffset ); |
| | 146 | + // Remove data covered by the range from nodeData |
| | 147 | + nodeData.splice( ranges[i].range.start, ranges[i].range.end - ranges[i].range.start ); |
| | 148 | + // What remains in nodeData is the data we need to keep |
| | 149 | + // Append it to newData |
| | 150 | + newData = newData.concat( nodeData ); |
| | 151 | + |
| | 152 | + lastElement = ranges[i].node.getElementType(); |
| | 153 | + } |
| | 154 | + } |
| | 155 | + if ( lastElement !== undefined ) { |
| | 156 | + // Keep the closing of the last element that was partially kept |
| | 157 | + newData.push( { 'type': '/' + lastElement } ); |
| | 158 | + } |
| | 159 | + |
| | 160 | + // Perform the rebuild. This updates the model tree |
| | 161 | + rebuild( newData, oldNodes ); |
| 127 | 162 | } else { |
| 128 | | - if ( es.DocumentModel.containsElementData( op.data ) ) { |
| 129 | | - // TODO: Support tree updates when removing partial elements |
| 130 | | - } else { |
| 131 | | - // Get the node we are removing content from |
| 132 | | - var node = this.tree.getNodeFromOffset( this.cursor ); |
| 133 | | - // Remove content from linear data model |
| 134 | | - this.data.splice( this.cursor, op.data.length ); |
| 135 | | - // Update model tree |
| 136 | | - node.adjustContentLength( -op.data.length, true ); |
| 137 | | - node.emit( 'update', this.cursor - this.tree.getOffsetFromNode( node ) ); |
| 138 | | - } |
| | 163 | + // We're removing content only. Take a shortcut |
| | 164 | + // Get the node we are removing content from |
| | 165 | + var node = this.tree.getNodeFromOffset( this.cursor ); |
| | 166 | + // Update model tree |
| | 167 | + node.adjustContentLength( -op.data.length, true ); |
| | 168 | + node.emit( 'update', this.cursor - this.tree.getOffsetFromNode( node ) ); |
| 139 | 169 | } |
| | 170 | + |
| | 171 | + // Update the linear model |
| | 172 | + this.data.splice( this.cursor, op.data.length ); |
| 140 | 173 | } |
| 141 | 174 | |
| 142 | 175 | function attribute( op, invert ) { |