jenkins-bot has submitted this change and it was merged.
Change subject: Store DM nodes in InternalList
......................................................................
Store DM nodes in InternalList
Also keep items in the order they appear in the document
and grouped by group and key.
Additions and removals are triggered by the new root/unroot events.
Change-Id: Ia3e90ccfdab88f352b89992b90554e5f03ff9952
---
M modules/ve/ce/nodes/ve.ce.DocumentNode.js
M modules/ve/dm/nodes/ve.dm.DocumentNode.js
M modules/ve/dm/nodes/ve.dm.MWReferenceNode.js
M modules/ve/dm/ve.dm.BranchNode.js
M modules/ve/dm/ve.dm.Document.js
M modules/ve/dm/ve.dm.InternalList.js
M modules/ve/test/dm/ve.dm.InternalList.test.js
M modules/ve/test/dm/ve.dm.Node.test.js
M modules/ve/test/dm/ve.dm.example.js
M modules/ve/test/ve.Node.test.js
M modules/ve/test/ve.qunit.js
M modules/ve/ve.Node.js
12 files changed, 304 insertions(+), 50 deletions(-)
Approvals:
Catrope: Looks good to me, approved
jenkins-bot: Verified
diff --git a/modules/ve/ce/nodes/ve.ce.DocumentNode.js
b/modules/ve/ce/nodes/ve.ce.DocumentNode.js
index 07adfa3..4c204cf 100644
--- a/modules/ve/ce/nodes/ve.ce.DocumentNode.js
+++ b/modules/ve/ce/nodes/ve.ce.DocumentNode.js
@@ -12,7 +12,7 @@
* @extends ve.ce.BranchNode
* @constructor
* @param {ve.dm.DocumentNode} model Model to observe
- * @param {ve.ui.Surface} surface Surface document is part of
+ * @param {ve.ce.Surface} surface Surface document is part of
* @param {Object} [config] Config options
*/
ve.ce.DocumentNode = function VeCeDocumentNode( model, surface, config ) {
@@ -22,6 +22,9 @@
// Properties
this.surface = surface;
+ // Set root
+ this.setRoot( this );
+
// DOM Changes
this.$.addClass( 've-ce-documentNode' );
this.$.attr( { 'contentEditable': 'true', 'spellcheck': 'true' } );
diff --git a/modules/ve/dm/nodes/ve.dm.DocumentNode.js
b/modules/ve/dm/nodes/ve.dm.DocumentNode.js
index 367cf75..43b54d4 100644
--- a/modules/ve/dm/nodes/ve.dm.DocumentNode.js
+++ b/modules/ve/dm/nodes/ve.dm.DocumentNode.js
@@ -16,6 +16,9 @@
ve.dm.DocumentNode = function VeDmDocumentNode( children ) {
// Parent constructor
ve.dm.BranchNode.call( this, children );
+
+ // Properties
+ this.root = this;
};
/* Inheritance */
diff --git a/modules/ve/dm/nodes/ve.dm.MWReferenceNode.js
b/modules/ve/dm/nodes/ve.dm.MWReferenceNode.js
index bda162c..fe02097 100644
--- a/modules/ve/dm/nodes/ve.dm.MWReferenceNode.js
+++ b/modules/ve/dm/nodes/ve.dm.MWReferenceNode.js
@@ -17,6 +17,12 @@
ve.dm.MWReferenceNode = function VeDmMWReferenceNode( length, element ) {
// Parent constructor
ve.dm.LeafNode.call( this, 0, element );
+
+ // Event handlers
+ this.connect( this, {
+ 'root': 'onRoot',
+ 'unroot': 'onUnroot'
+ } );
};
/* Inheritance */
@@ -41,16 +47,19 @@
// TODO: this should use mw.attrs.name once available from
Parsoid
name = $( domElements[0] ).children( 'a' ).attr( 'href' ),
// TODO: should also store and use mw.attrs.group once
available from Parsoid
- key = name !== null ? name : ve.getHash( body );
+ listGroup = this.name, // + '/' + mw.attrs.group
+ listKey = name !== null ? name : ve.getHash( body );
- listIndex = converter.internalList.queueItemHtml( key, body );
+ listIndex = converter.internalList.queueItemHtml( listGroup, listKey,
body );
dataElement = {
'type': this.name,
'attributes': {
'mw': mw,
'about': about,
- 'listIndex': listIndex
+ 'listIndex': listIndex,
+ 'listGroup': listGroup,
+ 'listKey': listKey
}
};
return dataElement;
@@ -91,7 +100,29 @@
* @returns {ve.dm.InternalItemNode} Item node
*/
ve.dm.MWReferenceNode.prototype.getInternalItem = function () {
- return this.doc.getInternalList().getItemNode( this.getAttribute(
'listIndex' ) );
+ return this.getDocument().getInternalList().getItemNode(
this.getAttribute( 'listIndex' ) );
+};
+
+/**
+ * Handle the node being attached to the root
+ * @method
+ */
+ve.dm.MWReferenceNode.prototype.onRoot = function () {
+ if ( this.getRoot() === this.getDocument().getDocumentNode() ) {
+ this.getDocument().getInternalList().addNode(
+ this.element.attributes.listGroup,
this.element.attributes.listKey, this
+ );
+ }
+};
+
+/**
+ * Handle the node being detatched from the root
+ * @method
+ */
+ve.dm.MWReferenceNode.prototype.onUnroot = function () {
+ this.getDocument().getInternalList().removeNode(
+ this.element.attributes.listGroup,
this.element.attributes.listKey, this
+ );
};
/* Registration */
diff --git a/modules/ve/dm/ve.dm.BranchNode.js
b/modules/ve/dm/ve.dm.BranchNode.js
index 8ef3fc9..a892bef 100644
--- a/modules/ve/dm/ve.dm.BranchNode.js
+++ b/modules/ve/dm/ve.dm.BranchNode.js
@@ -126,18 +126,18 @@
args = Array.prototype.slice.call( arguments ),
diff = 0;
+ removals = this.children.splice.apply( this.children, args );
+ for ( i = 0, length = removals.length; i < length; i++ ) {
+ removals[i].detach();
+ diff -= removals[i].getOuterLength();
+ }
+
if ( args.length >= 3 ) {
length = args.length;
for ( i = 2; i < length; i++ ) {
args[i].attach( this );
diff += args[i].getOuterLength();
}
- }
-
- removals = this.children.splice.apply( this.children, args );
- for ( i = 0, length = removals.length; i < length; i++ ) {
- removals[i].detach();
- diff -= removals[i].getOuterLength();
}
this.adjustLength( diff, true );
diff --git a/modules/ve/dm/ve.dm.Document.js b/modules/ve/dm/ve.dm.Document.js
index b36d3ed..9443022 100644
--- a/modules/ve/dm/ve.dm.Document.js
+++ b/modules/ve/dm/ve.dm.Document.js
@@ -32,7 +32,7 @@
*/
var i, node, children, meta,
doc = parentDocument || this,
- root = doc.getDocumentNode(),
+ root = this.getDocumentNode(),
textLength = 0,
inTextNode = false,
// Stack of stacks, each containing a
@@ -40,8 +40,8 @@
currentStack = stack[1],
parentStack = stack[0],
currentNode = this.documentNode;
- this.documentNode.setDocument( doc );
this.documentNode.setRoot( root );
+ this.documentNode.setDocument( doc );
this.internalList = internalList ? internalList.clone( this ) : new
ve.dm.InternalList( this );
// Properties
@@ -75,8 +75,7 @@
if ( !inTextNode ) {
// Create a lengthless text node
node = new ve.dm.TextNode();
- // Set the root pointer now, to prevent
cascading updates
- node.setRoot( root );
+ node.setDocument( doc );
// Put the node on the current inner stack
currentStack.push( node );
currentNode = node;
@@ -120,8 +119,7 @@
node = ve.dm.nodeFactory.create(
this.data.getData( i ).type, [],
this.data.getData( i )
);
- // Set the root pointer now, to prevent
cascading updates
- node.setRoot( root );
+ node.setDocument( doc );
// Put the childless node on the current inner
stack
currentStack.push( node );
if ( ve.dm.nodeFactory.canNodeHaveChildren(
node.getType() ) ) {
@@ -162,9 +160,16 @@
currentNode.setLength( textLength );
// Don't bother updating currentNode et al, we don't use them
below
}
+
+ // State variable that allows nodes to know that they are being
+ // appended in order. Used by ve.dm.InternalList.
+ this.buildingNodeTree = true;
+
// The end state is stack = [ [this.documentNode] [ array, of, its,
children ] ]
// so attach all nodes in stack[1] to the root node
ve.batchSplice( this.documentNode, 0, 0, stack[1] );
+
+ this.buildingNodeTree = false;
};
/* Inheritance */
diff --git a/modules/ve/dm/ve.dm.InternalList.js
b/modules/ve/dm/ve.dm.InternalList.js
index 6e332a0..d5dfaed 100644
--- a/modules/ve/dm/ve.dm.InternalList.js
+++ b/modules/ve/dm/ve.dm.InternalList.js
@@ -21,11 +21,12 @@
// Properties
this.document = doc;
this.store = new ve.dm.IndexValueStore();
- this.itemsHtml = [];
+ this.itemHtmlQueue = [];
this.listNode = null;
+ this.nodes = {};
// Event handlers
- //this.document.connect( this, { 'transact', 'onTransact' } );
+ // this.getDocument().connect( this, { 'transact': 'onTransact' } );
};
/* Inheritance */
@@ -37,18 +38,19 @@
/**
* Queues up an item's html for parsing later.
*
- * If an item with the specified key already exists it will be ignored.
+ * If an item with the specified group and key already exists it will be
ignored.
*
* @method
+ * @param {string} groupName Item group
* @param {string} key Item key
* @param {string} html Item contents
* @returns {number} Index of the item in the index-value store, and also the
list
*/
-ve.dm.InternalList.prototype.queueItemHtml = function ( key, html ) {
- var index = this.getStore().indexOfHash( key );
+ve.dm.InternalList.prototype.queueItemHtml = function ( groupName, key, html )
{
+ var index = this.getStore().indexOfHash( groupName + '/' + key );
if ( index === null ) {
- index = this.getStore().index( html, key );
- this.itemsHtml.push( index );
+ index = this.getStore().index( html, groupName + '/' + key );
+ this.itemHtmlQueue.push( index );
}
return index;
};
@@ -58,8 +60,8 @@
* @method
* @returns {Object} Name-indexed object containing HTMLElements
*/
-ve.dm.InternalList.prototype.getItemsHtml = function () {
- return this.getStore().values( this.itemsHtml );
+ve.dm.InternalList.prototype.getItemHtmlQueue = function () {
+ return this.getStore().values( this.itemHtmlQueue );
};
/**
@@ -124,12 +126,12 @@
*/
ve.dm.InternalList.prototype.convertToData = function ( converter ) {
var i, length, itemData,
- itemsHtml = this.getItemsHtml(), list = [];
+ itemHtmlQueue = this.getItemHtmlQueue(), list = [];
- if ( itemsHtml.length ) {
+ if ( itemHtmlQueue.length ) {
list.push( { 'type': 'internalList' } );
- for ( i = 0, length = itemsHtml.length; i < length; i++ ) {
- itemData = converter.getDataFromDomRecursion( $(
'<div>' ).html( itemsHtml[i] )[0] );
+ for ( i = 0, length = itemHtmlQueue.length; i < length; i++ ) {
+ itemData = converter.getDataFromDomRecursion( $(
'<div>' ).html( itemHtmlQueue[i] )[0] );
list = list.concat(
[{ 'type': 'internalItem' }],
itemData,
@@ -139,8 +141,94 @@
list.push( { 'type': '/internalList' } );
}
// After conversion we no longer need the HTML
- this.itemsHtml = [];
+ this.itemHtmlQueue = [];
return list;
+};
+
+/**
+ * Add a node.
+ * @method
+ * @param {string} groupName Item group
+ * @param {string} key Item name
+ * @param {ve.dm.Node} node Item node
+ */
+ve.dm.InternalList.prototype.addNode = function ( groupName, key, node ) {
+ var i, len, start, keyNodes, group = this.nodes[groupName];
+ // The group may not exist yet
+ if ( group === undefined ) {
+ group = this.nodes[groupName] = {
+ keyNodes: {},
+ keyOrder: []
+ };
+ }
+ keyNodes = group.keyNodes[key];
+ // The key may not exist yet
+ if ( keyNodes === undefined ) {
+ keyNodes = group.keyNodes[key] = [];
+ }
+
+ if ( ve.indexOf( key, group.keyOrder ) === -1 ) {
+ group.keyOrder.push( key );
+ }
+ if ( node.getDocument().buildingNodeTree ) {
+ // If the document is building the original node tree
+ // then every item is being added in order, so we don't
+ // need to worry about sorting.
+ keyNodes.push( node );
+ } else {
+ // TODO: We could use binary search insertion sort
+ start = node.getRange().start;
+ for ( i = 0, len = keyNodes.length; i < len; i++ ) {
+ if ( start < keyNodes[i].getRange().start ) {
+ break;
+ }
+ }
+ // 'i' is now the insertion point, so add the node here
+ keyNodes.splice( i, 0, node );
+
+ this.sortGroupKeys( group );
+ }
+};
+
+/**
+ * Remove a node.
+ * @method
+ * @param {string} groupName Item group
+ * @param {string} key Item name
+ * @param {ve.dm.Node} node Item node
+ */
+ve.dm.InternalList.prototype.removeNode = function ( groupName, key, node ) {
+ var i, len, j,
+ group = this.nodes[groupName],
+ keyNodes = group.keyNodes[key];
+ for ( i = 0, len = keyNodes.length; i < len; i++ ) {
+ if ( keyNodes[i] === node ) {
+ keyNodes.splice( i, 1 );
+ if ( keyNodes.length === 0 ) {
+ delete group.keyNodes[key];
+ j = ve.indexOf( key, group.keyOrder );
+ group.keyOrder.splice( j, 1 );
+ } else {
+ // If the key has been emptied then removing it
from the keyOrder
+ // array is sufficient to keep it ordered,
otherwise we need to
+ // re-sort the keys, as we don't know the
position of the new
+ // 0th item in the key
+ this.sortGroupKeys( group );
+ }
+ break;
+ }
+ }
+};
+
+/**
+ * Sort the keyOrder array within a group object.
+ * @param {Object} group Group object
+ */
+ve.dm.InternalList.prototype.sortGroupKeys = function ( group ) {
+ // Sort keyOrder
+ group.keyOrder.sort( function ( key1, key2 ) {
+ return group.keyNodes[key1][0].getRange().start -
group.keyNodes[key2][0].getRange().start;
+ } );
};
/**
@@ -165,11 +253,11 @@
*/
ve.dm.InternalList.prototype.merge = function ( other ) {
var i, len, index, storeMapping = this.getStore().merge(
other.getStore() ), mapping = {};
- for ( i = 0, len = other.itemsHtml.length; i < len; i++ ) {
- other.itemsHtml[i] = storeMapping[other.itemsHtml[i]];
- index = ve.indexOf( other.itemsHtml[i], this.itemsHtml );
+ for ( i = 0, len = other.itemHtmlQueue.length; i < len; i++ ) {
+ other.itemHtmlQueue[i] = storeMapping[other.itemHtmlQueue[i]];
+ index = ve.indexOf( other.itemHtmlQueue[i], this.itemHtmlQueue
);
if ( index === -1 ) {
- index = this.itemsHtml.push( other.itemsHtml[i] ) - 1;
+ index = this.itemHtmlQueue.push( other.itemHtmlQueue[i]
) - 1;
}
mapping[i] = index;
}
diff --git a/modules/ve/test/dm/ve.dm.InternalList.test.js
b/modules/ve/test/dm/ve.dm.InternalList.test.js
index 683195f..e655fcc 100644
--- a/modules/ve/test/dm/ve.dm.InternalList.test.js
+++ b/modules/ve/test/dm/ve.dm.InternalList.test.js
@@ -15,13 +15,13 @@
assert.deepEqual( internalList.getDocument(), doc, 'Returns original
document' );
} );
-QUnit.test( 'queueItemHtml/getItemsHtml', 4, function ( assert ) {
+QUnit.test( 'queueItemHtml/getItemHtmlQueue', 4, function ( assert ) {
var doc = ve.dm.example.createExampleDocument(),
internalList = doc.getInternalList();
- assert.equal( internalList.queueItemHtml( 'foo', 'Bar' ), 0, 'First
queued item returns index 0' );
- assert.equal( internalList.queueItemHtml( 'foo', 'Baz' ), 0, 'Duplicate
key returns index 0' );
- assert.equal( internalList.queueItemHtml( 'bar', 'Baz' ), 1, 'Second
queued item returns index 1' );
- assert.deepEqual( internalList.getItemsHtml(), ['Bar', 'Baz'],
'getItemsHtml returns stored HTML items' );
+ assert.equal( internalList.queueItemHtml( 'reference', 'foo', 'Bar' ),
0, 'First queued item returns index 0' );
+ assert.equal( internalList.queueItemHtml( 'reference', 'foo', 'Baz' ),
0, 'Duplicate key returns index 0' );
+ assert.equal( internalList.queueItemHtml( 'reference', 'bar', 'Baz' ),
1, 'Second queued item returns index 1' );
+ assert.deepEqual( internalList.getItemHtmlQueue(), ['Bar', 'Baz'],
'getItemHtmlQueue returns stored HTML items' );
} );
QUnit.test( 'convertToData', 2, function ( assert ) {
@@ -49,10 +49,10 @@
ve.dm.converter.internalList = internalList;
ve.dm.converter.contextStack = [];
- internalList.queueItemHtml( 'foo', 'Bar' );
- internalList.queueItemHtml( 'bar', 'Baz' );
+ internalList.queueItemHtml( 'reference', 'foo', 'Bar' );
+ internalList.queueItemHtml( 'reference', 'bar', 'Baz' );
assert.deepEqual( internalList.convertToData( ve.dm.converter ),
expectedData, 'Data matches' );
- assert.deepEqual( internalList.getItemsHtml(), [], 'Items html is
emptied after conversion' );
+ assert.deepEqual( internalList.getItemHtmlQueue(), [], 'Items html is
emptied after conversion' );
} );
QUnit.test( 'clone', 3, function ( assert ) {
@@ -67,3 +67,75 @@
assert.equal( internalListClone2.getDocument(), doc2, 'Cloning with
document parameter' );
} );
+
+QUnit.test( 'addNode/removeNode', 4, function ( assert ) {
+ var doc = ve.dm.example.createExampleDocument(),
+ internalList = doc.getInternalList(),
+ expectedNodes = {
+ 'ref': {
+ 'keyNodes': {
+ 'ref-1': [
doc.documentNode.children[0], doc.documentNode.children[2] ],
+ 'ref-2': [ doc.documentNode.children[1]
],
+ 'ref-3': [ doc.documentNode.children[3]
]
+ },
+ 'keyOrder': [ 'ref-1', 'ref-2', 'ref-3' ]
+ }
+ };
+
+ internalList.addNode( 'ref', 'ref-1', doc.documentNode.children[0] );
+ internalList.addNode( 'ref', 'ref-2', doc.documentNode.children[1] );
+ internalList.addNode( 'ref', 'ref-1', doc.documentNode.children[2] );
+ internalList.addNode( 'ref', 'ref-3', doc.documentNode.children[3] );
+
+ assert.deepEqualWithNodeTree(
+ internalList.nodes,
+ expectedNodes,
+ 'Nodes added in order'
+ );
+
+ doc = ve.dm.example.createExampleDocument();
+ internalList = doc.getInternalList();
+
+ internalList.addNode( 'ref', 'ref-3', doc.documentNode.children[3] );
+ internalList.addNode( 'ref', 'ref-1', doc.documentNode.children[2] );
+ internalList.addNode( 'ref', 'ref-2', doc.documentNode.children[1] );
+ internalList.addNode( 'ref', 'ref-1', doc.documentNode.children[0] );
+
+ assert.deepEqualWithNodeTree(
+ internalList.nodes,
+ expectedNodes,
+ 'Nodes added in reverse order'
+ );
+
+ internalList.removeNode( 'ref', 'ref-1', doc.documentNode.children[0] );
+
+ assert.deepEqualWithNodeTree(
+ internalList.nodes,
+ {
+ 'ref': {
+ 'keyNodes': {
+ 'ref-2': [ doc.documentNode.children[1]
],
+ 'ref-1': [ doc.documentNode.children[2]
],
+ 'ref-3': [ doc.documentNode.children[3]
]
+ },
+ 'keyOrder': [ 'ref-2', 'ref-1', 'ref-3' ]
+ }
+ },
+ 'Keys re-ordered after one item of key removed'
+ );
+
+ internalList.removeNode( 'ref', 'ref-3', doc.documentNode.children[3] );
+ internalList.removeNode( 'ref', 'ref-1', doc.documentNode.children[2] );
+ internalList.removeNode( 'ref', 'ref-2', doc.documentNode.children[1] );
+
+ assert.deepEqualWithNodeTree(
+ internalList.nodes,
+ {
+ 'ref': {
+ 'keyNodes': {},
+ 'keyOrder': []
+ }
+ },
+ 'All nodes removed'
+ );
+} );
diff --git a/modules/ve/test/dm/ve.dm.Node.test.js
b/modules/ve/test/dm/ve.dm.Node.test.js
index 2df25b5..a12229e 100644
--- a/modules/ve/test/dm/ve.dm.Node.test.js
+++ b/modules/ve/test/dm/ve.dm.Node.test.js
@@ -86,7 +86,7 @@
node2 = new ve.dm.NodeStub();
node1.attach( node2 );
assert.strictEqual( node1.getParent(), node2 );
- assert.strictEqual( node1.getRoot(), node2 );
+ assert.strictEqual( node1.getRoot(), null );
} );
QUnit.test( 'detach', 2, function ( assert ) {
@@ -95,7 +95,7 @@
node1.attach( node2 );
node1.detach();
assert.strictEqual( node1.getParent(), null );
- assert.strictEqual( node1.getRoot(), node1 );
+ assert.strictEqual( node1.getRoot(), null );
} );
QUnit.test( 'canBeMergedWith', 4, function ( assert ) {
diff --git a/modules/ve/test/dm/ve.dm.example.js
b/modules/ve/test/dm/ve.dm.example.js
index 991e133..d3e8eae 100644
--- a/modules/ve/test/dm/ve.dm.example.js
+++ b/modules/ve/test/dm/ve.dm.example.js
@@ -1215,6 +1215,8 @@
'attributes': {
'about': '#mwt5',
'listIndex': 0,
+ 'listGroup': 'mwReference',
+ 'listKey': '#cite_note-bar-1',
'mw': { 'body': { 'html': 'Bar' } }
},
'htmlAttributes': [
@@ -1247,6 +1249,8 @@
'attributes': {
'about': '#mwt6',
'listIndex': 1,
+ 'listGroup': 'mwReference',
+ 'listKey': '#cite_note-quux-2',
'mw': { 'body': { 'html': 'Quux' } }
},
'htmlAttributes': [
@@ -1279,6 +1283,8 @@
'attributes': {
'about': '#mwt7',
'listIndex': 0,
+ 'listGroup': 'mwReference',
+ 'listKey': '#cite_note-bar-1',
'mw': { 'body': { 'html': '' } }
},
'htmlAttributes': [
@@ -1311,6 +1317,8 @@
'attributes': {
'about': '#mwt8',
'listIndex': 2,
+ 'listGroup': 'mwReference',
+ 'listKey': '#cite_note-3',
'mw': { 'body': { 'html': 'No name' } }
},
'htmlAttributes': [
diff --git a/modules/ve/test/ve.Node.test.js b/modules/ve/test/ve.Node.test.js
index 104a122..514e60a 100644
--- a/modules/ve/test/ve.Node.test.js
+++ b/modules/ve/test/ve.Node.test.js
@@ -32,5 +32,5 @@
QUnit.test( 'getRoot', 1, function ( assert ) {
var node = new ve.NodeStub();
- assert.strictEqual( node.getRoot(), node );
+ assert.strictEqual( node.getRoot(), null );
} );
diff --git a/modules/ve/test/ve.qunit.js b/modules/ve/test/ve.qunit.js
index aacba38..e1ef030 100644
--- a/modules/ve/test/ve.qunit.js
+++ b/modules/ve/test/ve.qunit.js
@@ -84,6 +84,20 @@
}
/**
+ * Callback for ve.copyArray/Object to convert nodes to a comparable summary
+ *
+ * @method
+ * @private
+ * @param {ve.dm.Node|Object} value Value in the object/array
+ * @returns {Object} Node summary if value is a node, otherwise just the value
+ */
+function convertNodes( value ) {
+ return value instanceof ve.dm.Node || value instanceof ve.ce.Node ?
+ getNodeTreeSummary( value ) :
+ value;
+}
+
+/**
* Assertion helpers for VisualEditor test suite.
* @class ve.QUnit.assert
*/
@@ -153,4 +167,17 @@
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
};
+/**
+ * Assert that two objects which may contain dom elements are equal.
+ * @method
+ * @static
+ */
+QUnit.assert.deepEqualWithNodeTree = function ( actual, expected, message ) {
+ // Recursively copy objects or arrays, converting any dom elements
found to comparable summaries
+ actual = ve.isArray( actual ) ? ve.copyArray( actual, convertNodes ) :
ve.copyObject( actual, convertNodes );
+ expected = ve.isArray( expected ) ? ve.copyArray( expected,
convertNodes ) : ve.copyObject( expected, convertNodes );
+
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+};
+
}( QUnit ) );
diff --git a/modules/ve/ve.Node.js b/modules/ve/ve.Node.js
index 7f5da4d..6cbab14 100644
--- a/modules/ve/ve.Node.js
+++ b/modules/ve/ve.Node.js
@@ -17,7 +17,7 @@
// Properties
this.type = this.constructor.static.name;
this.parent = null;
- this.root = this;
+ this.root = null;
this.doc = null;
};
@@ -29,6 +29,14 @@
/**
* @event detach
* @param {ve.Node} parent
+ */
+
+/**
+ * @event root
+ */
+
+/**
+ * @event unroot
*/
/* Abstract Methods */
@@ -132,9 +140,18 @@
*
* @method
* @param {ve.Node} root Node to use as root
+ * @emits root
+ * @emits unroot
*/
ve.Node.prototype.setRoot = function ( root ) {
- this.root = root;
+ if ( root !== this.root ) {
+ this.root = root;
+ if ( this.getRoot() ) {
+ this.emit( 'root' );
+ } else {
+ this.emit( 'unroot' );
+ }
+ }
};
/**
@@ -182,8 +199,8 @@
ve.Node.prototype.detach = function () {
var parent = this.parent;
this.parent = null;
- this.setRoot( this );
- this.setDocument();
+ this.setRoot( null );
+ this.setDocument( null );
this.emit( 'detach', parent );
};
--
To view, visit https://gerrit.wikimedia.org/r/66091
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia3e90ccfdab88f352b89992b90554e5f03ff9952
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits