mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 18:39:52 +00:00
Merge branch 'dmrewrite' of ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor into dmrewrite
This commit is contained in:
commit
469a55bde4
|
@ -115,6 +115,10 @@ ve.dm.DocumentFragment = function( data, parentDocument ) {
|
|||
children = stack.pop();
|
||||
currentStack = parentStack;
|
||||
parentStack = stack[stack.length - 2];
|
||||
if ( !parentStack ) {
|
||||
// This can only happen if we got unbalanced data
|
||||
throw 'Unbalanced input passed to DocumentFragment';
|
||||
}
|
||||
// Attach the children to the node
|
||||
ve.batchSplice( currentNode, 0, 0, children );
|
||||
}
|
||||
|
|
|
@ -89,12 +89,20 @@ ve.dm.DocumentSynchronizer.synchronizers = {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO index of firstNode in parent should be in the selectNodes result
|
||||
var firstNode = selection[0].node,
|
||||
var firstNode, parent, index, numNodes;
|
||||
if ( 'indexInNode' in selection[0] ) {
|
||||
// Insertion
|
||||
parent = selection[0].node;
|
||||
index = selection[0].indexInNode;
|
||||
numNodes = 0;
|
||||
} else {
|
||||
// Rebuild
|
||||
firstNode = selection[0].node,
|
||||
parent = firstNode.getParent(),
|
||||
index = parent.indexOf( firstNode );
|
||||
|
||||
this.document.rebuildNodes( parent, index, selection.length, action.oldRange.from,
|
||||
index = selection[0].index;
|
||||
numNodes = selection.length;
|
||||
}
|
||||
this.document.rebuildNodes( parent, index, numNodes, action.oldRange.from,
|
||||
action.newRange.getLength()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -232,6 +232,77 @@ ve.dm.TransactionProcessor.prototype.replace = function( op ) {
|
|||
this.cursor += replacement.length;
|
||||
} else {
|
||||
// Structural replacement
|
||||
// TODO implement
|
||||
// TODO generalize for insert/remove
|
||||
|
||||
// It's possible that multiple replace operations are needed before the
|
||||
// model is back in a consistent state. This loop applies the current
|
||||
// replace operation to the linear model, then keeps applying subsequent
|
||||
// operations until the model is consistent. We keep track of the changes
|
||||
// and queue a single rebuild after the loop finishes.
|
||||
var operation = op,
|
||||
removeLevel = 0,
|
||||
replaceLevel = 0,
|
||||
startOffset = this.cursor,
|
||||
adjustment = 0,
|
||||
i,
|
||||
type;
|
||||
|
||||
while ( true ) {
|
||||
if ( operation.type == 'replace' ) {
|
||||
var opRemove = this.reversed ? operation.replacement : operation.remove,
|
||||
opReplacement = this.reversed ? operation.remove : operation.replacement;
|
||||
// Update the linear model for this replacement
|
||||
ve.batchSplice( this.document.data, this.cursor, opRemove.length, opReplacement );
|
||||
this.cursor += opReplacement.length;
|
||||
adjustment += opReplacement.length - opRemove.length;
|
||||
|
||||
// Walk through the remove and replacement data
|
||||
// and keep track of the element depth change (level)
|
||||
// for each of these two separately. The model is
|
||||
// only consistent if both levels are zero.
|
||||
for ( i = 0; i < opRemove.length; i++ ) {
|
||||
type = opRemove[i].type;
|
||||
if ( type === undefined ) {
|
||||
// This is content, ignore
|
||||
} else if ( type.charAt( 0 ) === '/' ) {
|
||||
// Closing element
|
||||
removeLevel--;
|
||||
} else {
|
||||
// Opening element
|
||||
removeLevel++;
|
||||
}
|
||||
}
|
||||
for ( i = 0; i < opReplacement.length; i++ ) {
|
||||
type = opReplacement[i].type;
|
||||
if ( type === undefined ) {
|
||||
// This is content, ignore
|
||||
} else if ( type.charAt( 0 ) === '/' ) {
|
||||
// Closing element
|
||||
replaceLevel--;
|
||||
} else {
|
||||
// Opening element
|
||||
replaceLevel++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We know that other operations won't cause adjustments, so we
|
||||
// don't have to update adjustment
|
||||
this.executeOperation( operation );
|
||||
}
|
||||
|
||||
if ( removeLevel === 0 && replaceLevel === 0 ) {
|
||||
// The model is back in a consistent state, so we're done
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the next operation
|
||||
operation = this.nextOperation();
|
||||
if ( !operation ) {
|
||||
throw 'Unbalanced set of replace operations found';
|
||||
}
|
||||
}
|
||||
// Queue a rebuild for the replaced node
|
||||
this.synchronizer.pushRebuild( new ve.Range( startOffset, this.cursor - adjustment ),
|
||||
new ve.Range( startOffset, this.cursor ) );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,7 +34,11 @@ 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
|
||||
* 'index': Index of the node in its parent, missing if node has no parent
|
||||
* 'indexInNode': If range is a zero-length range between two children of node,
|
||||
* this is set to the index of the child following range (or to
|
||||
* node.children.length+1 if range is between the last child and
|
||||
* the end). Missing in all other cases
|
||||
* '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
|
||||
|
@ -51,7 +55,7 @@ ve.Document.prototype.selectNodes = function( range, mode ) {
|
|||
// Index of the child in node we're visiting
|
||||
'index': 0,
|
||||
// First offset inside node
|
||||
'startOffset': 1
|
||||
'startOffset': 0
|
||||
} ],
|
||||
node,
|
||||
prevNode,
|
||||
|
@ -106,15 +110,18 @@ ve.Document.prototype.selectNodes = function( range, mode ) {
|
|||
|
||||
if ( start == end && ( startBetween || endBetween ) && node.isWrapped() ) {
|
||||
// Empty range in the parent, outside of any child
|
||||
parentFrame = stack[stack.length - 2];
|
||||
return [ {
|
||||
retval = [ {
|
||||
'node': currentFrame.node,
|
||||
'indexInNode': currentFrame.index + ( endBetween ? 1 : 0 ),
|
||||
'range': new ve.Range( start, end ),
|
||||
'index': parentFrame.index,
|
||||
'nodeRange': new ve.Range( parentFrame.startOffset,
|
||||
parentFrame.startOffset + currentFrame.node.getLength()
|
||||
'nodeRange': new ve.Range( currentFrame.startOffset,
|
||||
currentFrame.startOffset + currentFrame.node.getLength()
|
||||
)
|
||||
} ];
|
||||
parentFrame = stack[stack.length - 2];
|
||||
if ( parentFrame ) {
|
||||
retval[0].index = parentFrame.index;
|
||||
}
|
||||
} else if ( startBetween ) {
|
||||
// start is between the previous sibling and node
|
||||
// so the selection covers all of node and possibly more
|
||||
|
|
|
@ -2,10 +2,21 @@ module( 've.dm.DocumentFragment' );
|
|||
|
||||
/* Tests */
|
||||
|
||||
test( 'constructor', 114, function() {
|
||||
test( 'constructor', 115, function() {
|
||||
var fragment = new ve.dm.DocumentFragment( ve.dm.example.data );
|
||||
// Test count: ( ( 4 tests x 21 branch nodes ) + ( 3 tests x 10 leaf nodes ) ) = 114
|
||||
ve.example.nodeTreeEqual( fragment.getDocumentNode(), ve.dm.example.tree );
|
||||
|
||||
raises(
|
||||
function() {
|
||||
fragment = new ve.dm.DocumentFragment( [
|
||||
{ 'type': '/paragraph' },
|
||||
{ 'type': 'paragraph' }
|
||||
] );
|
||||
},
|
||||
/^Unbalanced input passed to DocumentFragment$/,
|
||||
'unbalanced input causes exception'
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'getData', 1, function() {
|
||||
|
|
|
@ -154,6 +154,20 @@ ve.example.getSelectNodesCases = function( doc ) {
|
|||
'nodeRange': new ve.Range( 1, 4 )
|
||||
}
|
||||
]
|
||||
},
|
||||
// Zero-length range between two children of the document
|
||||
{
|
||||
'actual': doc.selectNodes( new ve.Range( 5, 5 ), 'leaves' ),
|
||||
'expected': [
|
||||
// document
|
||||
{
|
||||
'node': documentNode,
|
||||
'range': new ve.Range( 5, 5 ),
|
||||
// no 'index' because documentNode has no parent
|
||||
'indexInNode': 1,
|
||||
'nodeRange': new ve.Range( 0, 53 )
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
@ -196,6 +210,7 @@ ve.example.nodeSelectionEqual = function( a, b ) {
|
|||
strictEqual( 'range' in a[i], 'range' in b[i], 'range existence match' );
|
||||
}
|
||||
deepEqual( a[i].index, b[i].index, 'index match' );
|
||||
deepEqual( a[i].indexInNode, b[i].indexInNode, 'indexInNode match' );
|
||||
deepEqual( a[i].nodeRange, b[i].nodeRange, 'nodeRange match' );
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue