diff --git a/modules/ve2/ce/ve.ce.BranchNode.js b/modules/ve2/ce/ve.ce.BranchNode.js index c42143087b..6c4771db90 100644 --- a/modules/ve2/ce/ve.ce.BranchNode.js +++ b/modules/ve2/ce/ve.ce.BranchNode.js @@ -21,8 +21,10 @@ ve.ce.BranchNode = function( type, model, $element ) { // Events this.model.addListenerMethod( this, 'splice', 'onSplice' ); - this.$.addClass("ve-ce-branch"); + // DOM Changes + this.$.addClass( 've-ce-branchNode' ); + // Initialization if ( model.getChildren().length ) { this.onSplice.apply( this, [0, 0].concat( model.getChildren() ) ); } diff --git a/modules/ve2/ce/ve.ce.LeafNode.js b/modules/ve2/ce/ve.ce.LeafNode.js index 22bc5f6520..3035e29d50 100644 --- a/modules/ve2/ce/ve.ce.LeafNode.js +++ b/modules/ve2/ce/ve.ce.LeafNode.js @@ -15,7 +15,10 @@ ve.ce.LeafNode = function( type, model, $element ) { ve.LeafNode.call( this ); ve.ce.Node.call( this, type, model, $element ); - this.$.addClass("ve-ce-leaf"); + // DOM Changes + if ( model.isWrapped() ) { + this.$.addClass( 've-ce-leafNode' ); + } }; /* Inheritance */ diff --git a/modules/ve2/ve.Document.js b/modules/ve2/ve.Document.js index 0a1cd66e7b..6ca70bc2a7 100644 --- a/modules/ve2/ve.Document.js +++ b/modules/ve2/ve.Document.js @@ -34,6 +34,8 @@ ve.Document.prototype.getDocumentNode = function() { * @returns {Array} List of objects describing nodes in the selection and the ranges therein * 'node': Reference to a ve.dm.Node * 'range': ve.Range, missing if the entire node is covered + * 'index': Index of the node in its parent + * 'nodeRange': Range covering the inside of the entire node * @throws 'Invalid start offset' if range.start is out of range * @throws 'Invalid end offset' if range.end is out of range */ @@ -42,13 +44,18 @@ ve.Document.prototype.selectNodes = function( range, mode ) { retval = [], start = range.start, end = range.end, - stack = [ { 'node': doc, 'index': 0 } ], + stack = [ { + 'node': doc, + 'index': 0, + 'startOffset': 0 + } ], node, prevNode, nextNode, left, right, currentFrame = stack[0], + parentFrame, startInside, endInside, startBetween, @@ -68,9 +75,15 @@ ve.Document.prototype.selectNodes = function( range, mode ) { } if ( !doc.children || doc.children.length === 0 ) { - return []; + // Document has no children. This is weird + return [ { + 'node': doc, + 'range': new ve.Range( start, end ), + 'index': 0, + 'parentRange': new ve.Range( 0, doc.getLength() ) + } ]; } - // TODO we could find the start more efficiently using the offset map + // TODO maybe we could find the start more efficiently using the offset map left = doc.children[0].isWrapped() ? 1 : 0; while ( end >= left ) { @@ -89,9 +102,14 @@ ve.Document.prototype.selectNodes = function( range, mode ) { if ( start == end && ( startBetween || endBetween ) ) { // Empty range in the parent, outside of any child + parentFrame = stack[stack.length - 2]; return [ { 'node': currentFrame.node, - 'range': new ve.Range( start, end ) + 'range': new ve.Range( start, end ), + 'index': parentFrame.index, + 'nodeRange': new ve.Range( parentFrame.startOffset, + parentFrame.startOffset + currentFrame.node.getLength() + ) } ]; } else if ( startBetween ) { // start is between the previous sibling and node @@ -99,7 +117,11 @@ ve.Document.prototype.selectNodes = function( range, mode ) { if ( mode == 'leaves' && node.children && node.children.length ) { // Descend into node - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); if ( node.children[0].isWrapped() ) { left++; @@ -108,14 +130,22 @@ ve.Document.prototype.selectNodes = function( range, mode ) { continue; } else { // All of node is covered - // TODO should this have a range or not? - retval.push( { 'node': node } ); + retval.push( { + 'node': node, + // no 'range' because the entire node is covered + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) + } ); startFound = true; } } else if ( startInside && endInside ) { if ( node.children && node.children.length ) { // Descend into node - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); // If the first child of node has an opening, skip over it if ( node.children[0].isWrapped() ) { @@ -126,14 +156,20 @@ ve.Document.prototype.selectNodes = function( range, mode ) { // node is a leaf node and the range is entirely inside it return [ { 'node': node, - 'range': new ve.Range( left, right ) + 'range': new ve.Range( left, right ), + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) } ]; } } else if ( startInside ) { if ( mode == 'leaves' && node.children && node.children.length ) { // node is a branch node and the start is inside it // Descend into it - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); if ( node.children[0].isWrapped() ) { left++; @@ -144,7 +180,9 @@ ve.Document.prototype.selectNodes = function( range, mode ) { // Add to retval and keep going retval.push( { 'node': node, - 'range': new ve.Range( start, right ) + 'range': new ve.Range( start, right ), + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) } ); startFound = true; } @@ -156,7 +194,11 @@ ve.Document.prototype.selectNodes = function( range, mode ) { if ( mode == 'leaves' && node.children && node.children.length ) { // Descend into node - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); if ( node.children[0].isWrapped() ) { left++; @@ -164,15 +206,23 @@ ve.Document.prototype.selectNodes = function( range, mode ) { continue; } else { // All of node is covered - // TODO should this have a range or not? - retval.push( { 'node': node } ); + retval.push( { + 'node': node, + // no 'range' because the entire node is covered + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) + } ); return retval; } } else if ( endInside ) { if ( mode == 'leaves' && node.children && node.children.length ) { // node is a branch node and the end is inside it // Descend into it - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); if ( node.children[0].isWrapped() ) { left++; @@ -183,7 +233,9 @@ ve.Document.prototype.selectNodes = function( range, mode ) { // Add to retval and return retval.push( { 'node': node, - 'range': new ve.Range( left, end ) + 'range': new ve.Range( left, end ), + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) } ); return retval; } @@ -195,7 +247,11 @@ ve.Document.prototype.selectNodes = function( range, mode ) { if ( mode == 'leaves' && node.children && node.children.length ) { // Descend into node - currentFrame = { 'node': node, 'index': 0 }; + currentFrame = { + 'node': node, + 'index': 0, + 'startOffset': left + }; stack.push( currentFrame ); if ( node.children[0].isWrapped() ) { left++; @@ -203,8 +259,12 @@ ve.Document.prototype.selectNodes = function( range, mode ) { continue; } else { // All of node is covered - // TODO should this have a range or not? - retval.push( { 'node': node } ); + retval.push( { + 'node': node, + // no 'range' because the entire node is covered + 'index': currentFrame.index, + 'nodeRange': new ve.Range( left, right ) + } ); } } diff --git a/tests/ve2/ce/ve.ce.BranchNode.test.js b/tests/ve2/ce/ve.ce.BranchNode.test.js index d4b56c3995..360477aaf4 100644 --- a/tests/ve2/ce/ve.ce.BranchNode.test.js +++ b/tests/ve2/ce/ve.ce.BranchNode.test.js @@ -37,7 +37,7 @@ test( 'canHaveGrandchildren', 1, function() { test( 'updateDomWrapper', 3, function() { var node = new ve.ce.BranchNodeStub( new ve.dm.BranchNodeStub( [], { 'type': 'a' } ) ); // Add classes and content to the node - node.$.addClass( 'test' ).text( 'hello' ); + node.$.attr( 'class', 'test' ).text( 'hello' ); // Modify attribute node.getModel().attributes.type = 'b'; node.updateDomWrapper( 'type' ); diff --git a/tests/ve2/ce/ve.ce.LeafNode.test.js b/tests/ve2/ce/ve.ce.LeafNode.test.js index ba87f7a828..ae1e78e77d 100644 --- a/tests/ve2/ce/ve.ce.LeafNode.test.js +++ b/tests/ve2/ce/ve.ce.LeafNode.test.js @@ -7,12 +7,6 @@ ve.ce.LeafNodeStub = function( model ) { ve.ce.LeafNode.call( this, 'leaf-stub', model ); }; -ve.ce.LeafNodeStub.rules = { - 'canHaveChildren': false, - 'canHaveGrandchildren': false, - 'isWrapped': true -}; - ve.extendClass( ve.ce.LeafNodeStub, ve.ce.LeafNode ); ve.ce.factory.register( 'leaf-stub', ve.ce.LeafNodeStub ); diff --git a/tests/ve2/ve.example.js b/tests/ve2/ve.example.js index 8f7b704f95..b4688f2a58 100644 --- a/tests/ve2/ve.example.js +++ b/tests/ve2/ve.example.js @@ -12,58 +12,115 @@ ve.example.getSelectNodesCases = function( doc ) { 'actual': doc.selectNodes( new ve.Range( 0, 3 ), 'leaves' ), 'expected': [ // heading/text - partial leaf results have ranges with global offsets - { 'node': lookup( documentNode, 0, 0 ), 'range': new ve.Range( 1, 3 ) } + { + 'node': lookup( documentNode, 0, 0 ), + 'range': new ve.Range( 1, 3 ), + 'index': 0, + 'nodeRange': new ve.Range( 1, 4 ) + } ] }, { 'actual': doc.selectNodes( new ve.Range( 0, 10 ), 'leaves' ), 'expected': [ // heading/text - full coverage leaf nodes do not have ranges - { 'node': lookup( documentNode, 0, 0 ) }, + { + 'node': lookup( documentNode, 0, 0 ), + 'index': 0, + 'nodeRange': new ve.Range( 1, 4 ) + }, // table/tableRow/tableCell/paragraph/text - leaf nodes from different levels - { 'node': lookup( documentNode, 1, 0, 0, 0, 0 ) } + { + 'node': lookup( documentNode, 1, 0, 0, 0, 0 ), + 'index': 0, + 'nodeRange': new ve.Range( 9, 10 ) + } ] }, { 'actual': doc.selectNodes( new ve.Range( 28, 41 ), 'leaves' ), 'expected': [ // table/tableRow/tableCell/list/listItem/paragraph/text - { 'node': lookup( documentNode, 1, 0, 0, 2, 0, 0, 0 ) }, + { + 'node': lookup( documentNode, 1, 0, 0, 2, 0, 0, 0 ), + 'index': 0, + 'nodeRange': new ve.Range( 28, 29 ) + }, // preformatted/text - { 'node': lookup( documentNode, 2, 0 ) }, + { + 'node': lookup( documentNode, 2, 0 ), + 'index': 0, + 'nodeRange': new ve.Range( 36, 37 ) + }, // preformatted/image - leaf nodes that are not text nodes - { 'node': lookup( documentNode, 2, 1 ) }, + { + 'node': lookup( documentNode, 2, 1 ), + 'index': 1, + 'nodeRange': new ve.Range( 38, 38 ) + }, // preformatted/text - { 'node': lookup( documentNode, 2, 2 ) } + { + 'node': lookup( documentNode, 2, 2 ), + 'index': 2, + 'nodeRange': new ve.Range( 39, 40 ) + } ] }, { 'actual': doc.selectNodes( new ve.Range( 2, 15 ), 'siblings' ), 'expected': [ // heading - { 'node': lookup( documentNode, 0 ), 'range': new ve.Range( 2, 4 ) }, + { + 'node': lookup( documentNode, 0 ), + 'range': new ve.Range( 2, 4 ), + 'index': 0, + 'nodeRange': new ve.Range( 1, 4 ) + }, // table - { 'node': lookup( documentNode, 1 ), 'range': new ve.Range( 6, 15 ) } + { + 'node': lookup( documentNode, 1 ), + 'range': new ve.Range( 6, 15 ), + 'index': 1, + 'nodeRange': new ve.Range( 6, 34 ) + } ] }, { 'actual': doc.selectNodes( new ve.Range( 2, 49 ), 'siblings' ), 'expected': [ // heading - { 'node': lookup( documentNode, 0 ), 'range': new ve.Range( 2, 4 ) }, + { + 'node': lookup( documentNode, 0 ), + 'range': new ve.Range( 2, 4 ), + 'index': 0, + 'nodeRange': new ve.Range( 1, 4 ) + }, // table - { 'node': lookup( documentNode, 1 ) }, + { + 'node': lookup( documentNode, 1 ), + 'index': 1, + 'nodeRange': new ve.Range( 6, 34 ) + }, // preformatted - { 'node': lookup( documentNode, 2 ) }, + { + 'node': lookup( documentNode, 2 ), + 'index': 2, + 'nodeRange': new ve.Range( 36, 40 ) + }, // definitionList - { 'node': lookup( documentNode, 3 ), 'range': new ve.Range( 42, 49 ) } + { + 'node': lookup( documentNode, 3 ), + 'range': new ve.Range( 42, 49 ), + 'index': 3, + 'nodeRange': new ve.Range( 42, 52 ) + } ] } ]; }; /** - * Asserts that two node trees are equavilant. + * Asserts that two node trees are equivalent. * * This will perform 4 assertions on each branch node and 3 assertions on each leaf node. * @@ -82,7 +139,7 @@ ve.example.nodeTreeEqual = function( a, b ) { }; /** - * Asserts that two node selections are equavilant. + * Asserts that two node selections are equivalent. * * This will perform 1 assertion to check the number of results in the selection and then 2 * assertions on each result @@ -98,6 +155,8 @@ ve.example.nodeSelectionEqual = function( a, b ) { } else { strictEqual( 'range' in a[i], 'range' in b[i], 'range existence match' ); } + deepEqual( a[i].index, b[i].index, 'index match' ); + deepEqual( a[i].nodeRange, b[i].nodeRange, 'nodeRange match' ); } };