r103498 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r103497‎ | r103498 | r103499 >
Date:19:23, 17 November 2011
Author:catrope
Status:deferred
Tags:
Comment:
Improve the merging logic in prepareRemoval() to also allow merging nested nodes, e.g. by deleting </p></li><li><p>
Modified paths:
  • /trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js
@@ -948,7 +948,7 @@
949949 ' to ' + selectNodesTests[i].input.end + ')'
950950 );
951951 if ( console && console.log && !compare( result, selectNodesTests[i].output ) ) {
952 - console.log("Test " + (i+1) + " FAILED");
 952+ console.log( "Test " + (i+1) + " FAILED" );
953953 console.log( result );
954954 console.log( selectNodesTests[i].output );
955955 }
Index: trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js
@@ -301,7 +301,7 @@
302302 );
303303 } );
304304
305 -test( 'es.DocumentModel.prepareRemoval', 8, function() {
 305+test( 'es.DocumentModel.prepareRemoval', 11, function() {
306306 var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj );
307307
308308 // Test 1
@@ -341,26 +341,9 @@
342342 ],
343343 'prepareRemoval removes entire elements'
344344 );
345 -
 345+
346346 // Test 3
347347 deepEqual(
348 - documentModel.prepareRemoval( new es.Range( 21, 23 ) ).getOperations(),
349 - [
350 - { 'type': 'retain', 'length': 21 },
351 - {
352 - 'type': 'remove',
353 - 'data': [
354 - { 'type': '/listItem' },
355 - { 'type': 'listItem', 'attributes': { 'styles': ['number'] } }
356 - ]
357 - },
358 - { 'type': 'retain', 'length': 11 }
359 - ],
360 - 'prepareRemoval merges two list items'
361 - );
362 -
363 - // Test 4
364 - deepEqual(
365348 documentModel.prepareRemoval( new es.Range( 3, 9 ) ).getOperations(),
366349 [
367350 { 'type': 'retain', 'length': 3 },
@@ -375,7 +358,7 @@
376359 'prepareRemoval works across structural nodes'
377360 );
378361
379 - // Test 5
 362+ // Test 4
380363 deepEqual(
381364 documentModel.prepareRemoval( new es.Range( 3, 24 ) ).getOperations(),
382365 [
@@ -410,7 +393,7 @@
411394 'prepareRemoval strips and drops correctly when working across structural nodes'
412395 );
413396
414 - // Test 6
 397+ // Test 5
415398 deepEqual(
416399 documentModel.prepareRemoval( new es.Range( 3, 25 ) ).getOperations(),
417400 [
@@ -450,7 +433,7 @@
451434 'prepareRemoval strips and drops correctly when working across structural nodes (2)'
452435 );
453436
454 - // Test 7
 437+ // Test 6
455438 deepEqual(
456439 documentModel.prepareRemoval( new es.Range( 9, 17 ) ).getOperations(),
457440 [
@@ -475,7 +458,7 @@
476459 'prepareRemoval will not merge items of unequal types'
477460 );
478461
479 - // Test 8
 462+ // Test 7
480463 deepEqual(
481464 documentModel.prepareRemoval( new es.Range( 9, 27 ) ).getOperations(),
482465 [
@@ -509,6 +492,76 @@
510493 ],
511494 'prepareRemoval blanks a paragraph and a list'
512495 );
 496+
 497+ // Test 8
 498+ deepEqual(
 499+ documentModel.prepareRemoval( new es.Range( 21, 23 ) ).getOperations(),
 500+ [
 501+ { 'type': 'retain', 'length': 21 },
 502+ {
 503+ 'type': 'remove',
 504+ 'data': [
 505+ { 'type': '/listItem' },
 506+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } }
 507+ ]
 508+ },
 509+ { 'type': 'retain', 'length': 11 }
 510+ ],
 511+ 'prepareRemoval merges two list items'
 512+ );
 513+
 514+ // Test 9
 515+ deepEqual(
 516+ documentModel.prepareRemoval( new es.Range( 20, 24 ) ).getOperations(),
 517+ [
 518+ { 'type': 'retain', 'length': 20 },
 519+ {
 520+ 'type': 'remove',
 521+ 'data': [
 522+ { 'type': '/paragraph' },
 523+ { 'type': '/listItem' },
 524+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 525+ { 'type': 'paragraph' }
 526+ ]
 527+ },
 528+ { 'type': 'retain', 'length': 10 }
 529+ ],
 530+ 'prepareRemoval merges two list items and the paragraphs inside them'
 531+ );
 532+
 533+ // Test 10
 534+ deepEqual(
 535+ documentModel.prepareRemoval( new es.Range( 20, 23 ) ).getOperations(),
 536+ [
 537+ { 'type': 'retain', 'length': 34 }
 538+ ],
 539+ 'prepareRemoval returns a null transaction when attempting an unbalanced merge'
 540+ );
 541+
 542+ // Test 11
 543+ deepEqual(
 544+ documentModel.prepareRemoval( new es.Range( 15, 24 ) ).getOperations(),
 545+ [
 546+ { 'type': 'retain', 'length': 15 },
 547+ {
 548+ 'type': 'remove',
 549+ 'data': [
 550+ { 'type': '/paragraph' },
 551+ { 'type': '/listItem' },
 552+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 553+ { 'type': 'paragraph' },
 554+ 'f',
 555+ { 'type': '/paragraph' },
 556+ { 'type': '/listItem' },
 557+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 558+ { 'type': 'paragraph' }
 559+ ]
 560+ },
 561+ { 'type': 'retain', 'length': 10 }
 562+ ],
 563+ 'prepareRemoval merges two list items and the paragraphs inside them'
 564+ );
 565+
513566 } );
514567
515568 test( 'es.DocumentModel.prepareInsertion', 11, function() {
Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
@@ -827,6 +827,36 @@
828828 */
829829
830830 es.DocumentModel.prototype.prepareRemoval = function( range ) {
 831+ // If a selection is painted across two paragraphs, and then the text is deleted, the two
 832+ // paragraphs can become one paragraph. However, if the selection crosses into a table, those
 833+ // cannot be merged. To make this simple, we follow rule #2 in the comment above for deciding
 834+ // whether two elements can be merged.
 835+ // So you can merge adjacent paragraphs, or list items. And you can't merge a paragraph into
 836+ // a table row. There may be other rules we will want in here later, for instance, special
 837+ // casing merging a listitem into a paragraph.
 838+ function canMerge( node1, node2 ) {
 839+ var n1 = node1, n2 = node2;
 840+ // Simultaneously walk upwards from node1 and node2
 841+ // until we reach the common ancestor.
 842+ while ( n1 !== n2 ) {
 843+ if ( n1.getElementType() !== n2.getElementType() ) {
 844+ // Not the same type
 845+ return false;
 846+ }
 847+ n1 = n1.getParent();
 848+ n2 = n2.getParent();
 849+ if ( n1 === null || n2 === null ) {
 850+ // Reached a root, so no common ancestor
 851+ // or different depth
 852+ return false;
 853+ }
 854+ }
 855+ // We've reached the common ancestor using simultaneous traversal,
 856+ // so we know node1 and node2 have the same depth. We also haven't
 857+ // seen any nodes with mismatching types along the way, so we're good.
 858+ return true;
 859+ }
 860+
831861 var tx = new es.Transaction(), selectedNodes, selectedNode, startNode, endNode, i;
832862 range.normalize();
833863 if ( range.start === range.end ) {
@@ -840,17 +870,7 @@
841871 startNode = selectedNodes[0].node;
842872 endNode = selectedNodes[selectedNodes.length - 1].node;
843873
844 - // If a selection is painted across two paragraphs, and then the text is deleted, the two
845 - // paragraphs can become one paragraph. However, if the selection crosses into a table, those
846 - // cannot be merged. To make this simple, we are follow a basic rule:
847 - // can merge = ( same type ) && ( same parent )
848 - // So you can merge adjacent paragraphs, or listitems. And you can't merge a paragraph into
849 - // a table row. There may be other rules we will want in here later, for instance, special
850 - // casing merging a listitem into a paragraph.
851 - if ( startNode && endNode &&
852 - startNode.getParent() === endNode.getParent() &&
853 - startNode.getElementType() === endNode.getElementType()
854 - ) {
 874+ if ( startNode && endNode && canMerge( startNode, endNode ) ) {
855875 // This is the simple case. node1 and node2 are either the same node, or can be merged
856876 // So we can just remove all the data in the range and call it a day, no fancy
857877 // processing necessary

Status & tagging log