Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js |
— | — | @@ -499,12 +499,44 @@ |
500 | 500 | ); |
501 | 501 | } ); |
502 | 502 | |
503 | | -test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 4, function() { |
| 503 | +test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 6, function() { |
504 | 504 | var documentModel = es.DocumentModel.newFromPlainObject( obj ); |
505 | 505 | |
| 506 | + var contentAnnotation = documentModel.prepareContentAnnotation( |
| 507 | + new es.Range( 1, 4 ), 'set', { 'type': 'bold' } |
| 508 | + ); |
| 509 | + |
| 510 | + // Test 1 |
| 511 | + documentModel.commit( contentAnnotation ); |
| 512 | + deepEqual( |
| 513 | + documentModel.getData( new es.Range( 0, 5 ) ), |
| 514 | + [ |
| 515 | + { 'type': 'paragraph' }, |
| 516 | + ['a', { 'type': 'bold', 'hash': '#bold' }], |
| 517 | + ['b', { 'type': 'bold', 'hash': '#bold' }], |
| 518 | + ['c', { 'type': 'italic', 'hash': '#italic' }, { 'type': 'bold', 'hash': '#bold' }], |
| 519 | + { 'type': '/paragraph' } |
| 520 | + ], |
| 521 | + 'commit applies a content annotation transaction to the content' |
| 522 | + ); |
| 523 | + |
| 524 | + // Test 2 |
| 525 | + documentModel.rollback( contentAnnotation ); |
| 526 | + deepEqual( |
| 527 | + documentModel.getData( new es.Range( 0, 5 ) ), |
| 528 | + [ |
| 529 | + { 'type': 'paragraph' }, |
| 530 | + 'a', |
| 531 | + ['b', { 'type': 'bold', 'hash': '#bold' }], |
| 532 | + ['c', { 'type': 'italic', 'hash': '#italic' }], |
| 533 | + { 'type': '/paragraph' } |
| 534 | + ], |
| 535 | + 'rollback reverses the effect of a content annotation transaction on the content' |
| 536 | + ); |
| 537 | + |
506 | 538 | var insertion = documentModel.prepareInsertion( 4, ['d'] ); |
507 | 539 | |
508 | | - // Test 1 |
| 540 | + // Test 3 |
509 | 541 | documentModel.commit( insertion ); |
510 | 542 | deepEqual( |
511 | 543 | documentModel.getData( new es.Range( 0, 6 ) ), |
— | — | @@ -519,7 +551,7 @@ |
520 | 552 | 'commit applies an insertion transaction to the content' |
521 | 553 | ); |
522 | 554 | |
523 | | - // Test 2 |
| 555 | + // Test 4 |
524 | 556 | documentModel.rollback( insertion ); |
525 | 557 | deepEqual( |
526 | 558 | documentModel.getData( new es.Range( 0, 5 ) ), |
— | — | @@ -535,7 +567,7 @@ |
536 | 568 | |
537 | 569 | var removal = documentModel.prepareRemoval( new es.Range( 2, 4 ) ); |
538 | 570 | |
539 | | - // Test 3 |
| 571 | + // Test 5 |
540 | 572 | documentModel.commit( removal ); |
541 | 573 | deepEqual( |
542 | 574 | documentModel.getData( new es.Range( 0, 3 ) ), |
— | — | @@ -547,7 +579,7 @@ |
548 | 580 | 'commit applies a removal transaction to the content' |
549 | 581 | ); |
550 | 582 | |
551 | | - // Test 4 |
| 583 | + // Test 6 |
552 | 584 | documentModel.rollback( removal ); |
553 | 585 | deepEqual( |
554 | 586 | documentModel.getData( new es.Range( 0, 5 ) ), |
Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -126,45 +126,54 @@ |
127 | 127 | if ( this.set.length ) { |
128 | 128 | for ( i = 0, length = this.set.length; i < length; i++ ) { |
129 | 129 | annotation = this.set[i]; |
| 130 | + // Auto-build annotation hash |
| 131 | + if ( annotation.hash === undefined ) { |
| 132 | + annotation.hash = es.DocumentModel.getAnnotationHash( annotation ); |
| 133 | + } |
130 | 134 | for ( j = this.cursor; j < to; j++ ) { |
| 135 | + // Auto-convert to array |
131 | 136 | if ( $.isArray( this.data[j] ) ) { |
132 | 137 | this.data[j].push( annotation ); |
133 | 138 | } else { |
134 | 139 | this.data[j] = [this.data[j], annotation]; |
135 | 140 | } |
136 | 141 | } |
137 | | - // Rebuild annotation hash |
138 | | - annotation.hash = es.DocumentModel.getAnnotationHash( annotation ); |
139 | 142 | } |
140 | 143 | } |
141 | 144 | if ( this.clear.length ) { |
142 | 145 | for ( i = 0, length = this.clear.length; i < length; i++ ) { |
143 | 146 | annotation = this.clear[i]; |
| 147 | + // Auto-build annotation hash |
| 148 | + if ( annotation.hash === undefined ) { |
| 149 | + annotation.hash = es.DocumentModel.getAnnotationHash( annotation ); |
| 150 | + } |
144 | 151 | for ( j = this.cursor; j < to; j++ ) { |
145 | 152 | var index = es.DocumentModel.getIndexOfAnnotation( this.data[j], annotation ); |
146 | 153 | if ( index !== -1 ) { |
147 | 154 | this.data[j].splice( index, 1 ); |
148 | 155 | } |
| 156 | + // Auto-convert to string |
| 157 | + if ( this.data[j].length === 1 ) { |
| 158 | + this.data[j] = this.data[j][0]; |
| 159 | + } |
149 | 160 | } |
150 | | - // Rebuild annotation hash |
151 | | - annotation.hash = es.DocumentModel.getAnnotationHash( annotation ); |
152 | 161 | } |
153 | 162 | } |
154 | 163 | } |
155 | 164 | |
156 | 165 | function mark( op, invert ) { |
157 | 166 | var target; |
158 | | - if ( op.method === 'set' || ( op.method === 'clear' && invert ) ) { |
| 167 | + if ( ( op.method === 'set' && !invert ) || ( op.method === 'clear' && invert ) ) { |
159 | 168 | target = this.set; |
160 | | - } else if ( op.method === 'clear' || ( op.method === 'set' && invert ) ) { |
| 169 | + } else if ( ( op.method === 'clear' && !invert ) || ( op.method === 'set' && invert ) ) { |
161 | 170 | target = this.clear; |
162 | 171 | } else { |
163 | 172 | throw 'Invalid method error. Can not operate attributes this way: ' + method; |
164 | 173 | } |
165 | 174 | if ( op.bias === 'start' ) { |
166 | 175 | target.push( op.annotation ); |
167 | | - } else if ( op.bias === 'end' ) { |
168 | | - var index = es.DocumentModel.getIndexOfAnnotation( target[i], op.annotation ); |
| 176 | + } else if ( op.bias === 'stop' ) { |
| 177 | + var index = es.DocumentModel.getIndexOfAnnotation( target, op.annotation ); |
169 | 178 | if ( index === -1 ) { |
170 | 179 | throw 'Annotation stack error. Annotation is missing.'; |
171 | 180 | } |
— | — | @@ -191,19 +200,19 @@ |
192 | 201 | // Change element attributes |
193 | 202 | 'attribute': { |
194 | 203 | 'commit': function( op ) { |
195 | | - attribute( op, false ); |
| 204 | + attribute.call( this, op, false ); |
196 | 205 | }, |
197 | 206 | 'rollback': function( op ) { |
198 | | - attribute( op, true ); |
| 207 | + attribute.call( this, op, true ); |
199 | 208 | } |
200 | 209 | }, |
201 | 210 | // Change content annotations |
202 | 211 | 'annotate': { |
203 | 212 | 'commit': function( op ) { |
204 | | - mark( op, false ); |
| 213 | + mark.call( this, op, false ); |
205 | 214 | }, |
206 | 215 | 'rollback': function( op ) { |
207 | | - mark( op, true ); |
| 216 | + mark.call( this, op, true ); |
208 | 217 | } |
209 | 218 | } |
210 | 219 | }; |
— | — | @@ -254,14 +263,18 @@ |
255 | 264 | return hash; |
256 | 265 | }; |
257 | 266 | |
258 | | -es.DocumentModel.getIndexOfAnnotation = function( character, annotation ) { |
| 267 | +es.DocumentModel.getIndexOfAnnotation = function( annotations, annotation ) { |
259 | 268 | if ( annotation === undefined || annotation.type === undefined ) { |
260 | 269 | throw 'Invalid annotation error. Can not find non-annotation data in character.'; |
261 | 270 | } |
262 | | - if ( $.isArray( character ) ) { |
| 271 | + if ( $.isArray( annotations ) ) { |
263 | 272 | // Find the index of a comparable annotation (checking for same value, not reference) |
264 | | - for ( var i = 1; i < character.length; i++ ) { |
265 | | - if ( character[i].hash === annotation.hash ) { |
| 273 | + for ( var i = 0; i < annotations.length; i++ ) { |
| 274 | + // Skip over character data - used when this is called on a content data item |
| 275 | + if ( typeof annotations[i] === 'string' ) { |
| 276 | + continue; |
| 277 | + } |
| 278 | + if ( annotations[i].hash === annotation.hash ) { |
266 | 279 | return i; |
267 | 280 | } |
268 | 281 | } |