https://www.mediawiki.org/wiki/Special:Code/MediaWiki/103498

Revision: 103498
Author:   catrope
Date:     2011-11-17 19:23:15 +0000 (Thu, 17 Nov 2011)
Log Message:
-----------
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
    trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js
    trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js

Modified: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
===================================================================
--- trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js 
2011-11-17 19:22:11 UTC (rev 103497)
+++ trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js 
2011-11-17 19:23:15 UTC (rev 103498)
@@ -827,6 +827,36 @@
  */
 
 es.DocumentModel.prototype.prepareRemoval = function( range ) {
+       // If a selection is painted across two paragraphs, and then the text 
is deleted, the two
+       // paragraphs can become one paragraph. However, if the selection 
crosses into a table, those
+       // cannot be merged. To make this simple, we follow rule #2 in the 
comment above for deciding
+       // whether two elements can be merged.
+       // So you can merge adjacent paragraphs, or list items. And you can't 
merge a paragraph into
+       // a table row. There may be other rules we will want in here later, 
for instance, special
+       // casing merging a listitem into a paragraph.
+       function canMerge( node1, node2 ) {
+               var n1 = node1, n2 = node2;
+               // Simultaneously walk upwards from node1 and node2
+               // until we reach the common ancestor.
+               while ( n1 !== n2 ) {
+                       if ( n1.getElementType() !== n2.getElementType() ) {
+                               // Not the same type
+                               return false;
+                       }
+                       n1 = n1.getParent();
+                       n2 = n2.getParent();
+                       if ( n1 === null || n2 === null ) {
+                               // Reached a root, so no common ancestor
+                               // or different depth
+                               return false;
+                       }
+               }
+               // We've reached the common ancestor using simultaneous 
traversal,
+               // so we know node1 and node2 have the same depth. We also 
haven't
+               // seen any nodes with mismatching types along the way, so 
we're good.
+               return true;
+       }
+       
        var tx = new es.Transaction(), selectedNodes, selectedNode, startNode, 
endNode, i;
        range.normalize();
        if ( range.start === range.end ) {
@@ -840,17 +870,7 @@
        startNode = selectedNodes[0].node;
        endNode = selectedNodes[selectedNodes.length - 1].node;
        
-       // If a selection is painted across two paragraphs, and then the text 
is deleted, the two
-       // paragraphs can become one paragraph. However, if the selection 
crosses into a table, those
-       // cannot be merged. To make this simple, we are follow a basic rule:
-       //     can merge = ( same type ) && ( same parent )
-       // So you can merge adjacent paragraphs, or listitems. And you can't 
merge a paragraph into
-       // a table row. There may be other rules we will want in here later, 
for instance, special
-       // casing merging a listitem into a paragraph.
-       if ( startNode && endNode &&
-                       startNode.getParent() === endNode.getParent() &&
-                       startNode.getElementType() === endNode.getElementType()
-       ) {
+       if ( startNode && endNode && canMerge( startNode, endNode ) ) {
                // This is the simple case. node1 and node2 are either the same 
node, or can be merged
                // So we can just remove all the data in the range and call it 
a day, no fancy
                // processing necessary

Modified: trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js
===================================================================
--- trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js        
2011-11-17 19:22:11 UTC (rev 103497)
+++ trunk/extensions/VisualEditor/tests/es/es.DocumentBranchNode.test.js        
2011-11-17 19:23:15 UTC (rev 103498)
@@ -948,7 +948,7 @@
                                        ' to ' + selectNodesTests[i].input.end 
+ ')'
                        );
                        if ( console && console.log && !compare( result, 
selectNodesTests[i].output ) ) {
-                               console.log("Test " + (i+1) + " FAILED");
+                               console.log( "Test " + (i+1) + " FAILED" );
                                console.log( result );
                                console.log( selectNodesTests[i].output );
                        }

Modified: trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js
===================================================================
--- trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js     
2011-11-17 19:22:11 UTC (rev 103497)
+++ trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js     
2011-11-17 19:23:15 UTC (rev 103498)
@@ -301,7 +301,7 @@
        );
 } );
 
-test( 'es.DocumentModel.prepareRemoval', 8, function() {
+test( 'es.DocumentModel.prepareRemoval', 11, function() {
        var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj );
 
        // Test 1
@@ -341,26 +341,9 @@
                ],
                'prepareRemoval removes entire elements'
        );
-       
+
        // Test 3
        deepEqual(
-               documentModel.prepareRemoval( new es.Range( 21, 23 ) 
).getOperations(),
-               [
-                       { 'type': 'retain', 'length': 21 },
-                       {
-                               'type': 'remove',
-                               'data': [
-                                       { 'type': '/listItem' },
-                                       { 'type': 'listItem', 'attributes': { 
'styles': ['number'] } }
-                               ]
-                       },
-                       { 'type': 'retain', 'length': 11 }
-               ],
-               'prepareRemoval merges two list items'
-       );
-
-       // Test 4
-       deepEqual(
                documentModel.prepareRemoval( new es.Range( 3, 9 ) 
).getOperations(),
                [
                        { 'type': 'retain', 'length': 3 },
@@ -375,7 +358,7 @@
                'prepareRemoval works across structural nodes'
        );
 
-       // Test 5
+       // Test 4
        deepEqual(
                documentModel.prepareRemoval( new es.Range( 3, 24 ) 
).getOperations(),
                [
@@ -410,7 +393,7 @@
                'prepareRemoval strips and drops correctly when working across 
structural nodes'
        );
        
-       // Test 6
+       // Test 5
        deepEqual(
                documentModel.prepareRemoval( new es.Range( 3, 25 ) 
).getOperations(),
                [
@@ -450,7 +433,7 @@
                'prepareRemoval strips and drops correctly when working across 
structural nodes (2)'
        );
        
-       // Test 7
+       // Test 6
        deepEqual(
                documentModel.prepareRemoval( new es.Range( 9, 17 ) 
).getOperations(),
                [
@@ -475,7 +458,7 @@
                'prepareRemoval will not merge items of unequal types'
        );
        
-       // Test 8
+       // Test 7
        deepEqual(
                documentModel.prepareRemoval( new es.Range( 9, 27 ) 
).getOperations(),
                [
@@ -509,6 +492,76 @@
                ],
                'prepareRemoval blanks a paragraph and a list'
        );
+       
+       // Test 8
+       deepEqual(
+               documentModel.prepareRemoval( new es.Range( 21, 23 ) 
).getOperations(),
+               [
+                       { 'type': 'retain', 'length': 21 },
+                       {
+                               'type': 'remove',
+                               'data': [
+                                       { 'type': '/listItem' },
+                                       { 'type': 'listItem', 'attributes': { 
'styles': ['number'] } }
+                               ]
+                       },
+                       { 'type': 'retain', 'length': 11 }
+               ],
+               'prepareRemoval merges two list items'
+       );
+       
+       // Test 9
+       deepEqual(
+               documentModel.prepareRemoval( new es.Range( 20, 24 ) 
).getOperations(),
+               [
+                       { 'type': 'retain', 'length': 20 },
+                       {
+                               'type': 'remove',
+                               'data': [
+                                       { 'type': '/paragraph' },
+                                       { 'type': '/listItem' },
+                                       { 'type': 'listItem', 'attributes': { 
'styles': ['number'] } },
+                                       { 'type': 'paragraph' }
+                               ]
+                       },
+                       { 'type': 'retain', 'length': 10 }
+               ],
+               'prepareRemoval merges two list items and the paragraphs inside 
them'
+       );
+       
+       // Test 10
+       deepEqual(
+               documentModel.prepareRemoval( new es.Range( 20, 23 ) 
).getOperations(),
+               [
+                       { 'type': 'retain', 'length': 34 }
+               ],
+               'prepareRemoval returns a null transaction when attempting an 
unbalanced merge'
+       );
+       
+       // Test 11
+       deepEqual(
+               documentModel.prepareRemoval( new es.Range( 15, 24 ) 
).getOperations(),
+               [
+                       { 'type': 'retain', 'length': 15 },
+                       {
+                               'type': 'remove',
+                               'data': [
+                                       { 'type': '/paragraph' },
+                                       { 'type': '/listItem' },
+                                       { 'type': 'listItem', 'attributes': { 
'styles': ['bullet', 'bullet'] } },
+                                       { 'type': 'paragraph' },
+                                       'f',
+                                       { 'type': '/paragraph' },
+                                       { 'type': '/listItem' },
+                                       { 'type': 'listItem', 'attributes': { 
'styles': ['number'] } },
+                                       { 'type': 'paragraph' }
+                               ]
+                       },
+                       { 'type': 'retain', 'length': 10 }
+               ],
+               'prepareRemoval merges two list items and the paragraphs inside 
them'
+       );
+       
 } );
 
 test( 'es.DocumentModel.prepareInsertion', 11, function() {


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to