mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-28 00:00:49 +00:00
Implement prepareWrap and add tests for it
This commit is contained in:
parent
9b66749575
commit
5054ed320e
Notes:
Roan Kattouw
2012-03-08 23:21:26 +00:00
|
@ -1145,6 +1145,87 @@ ve.dm.DocumentNode.prototype.prepareContentReplacement = function( range, data )
|
|||
*
|
||||
*/
|
||||
ve.dm.DocumentNode.prototype.prepareWrap = function( range, unwrapOuter, wrapOuter, unwrapEach, wrapEach ) {
|
||||
// Function to generate arrays of closing elements in reverse order
|
||||
function closingArray( openings ) {
|
||||
var closings = [], i, len = openings.length;
|
||||
for ( i = 0; i < len; i++ ) {
|
||||
closings[closings.length] = { 'type': '/' + openings[len - i - 1].type };
|
||||
}
|
||||
return closings;
|
||||
}
|
||||
|
||||
// TODO sanity checks:
|
||||
// * range.start > unwrapOuter.length
|
||||
// * unwraps actually match
|
||||
// * result is valid
|
||||
|
||||
var tx = new ve.dm.Transaction();
|
||||
range.normalize();
|
||||
if ( range.start > unwrapOuter.length ) {
|
||||
// Retain up to the first thing we're unwrapping
|
||||
// The outer unwrapping takes place *outside*
|
||||
// the range, so compensate for that
|
||||
tx.pushRetain ( range.start - unwrapOuter.length );
|
||||
}
|
||||
|
||||
// Replace the opening elements for the outer unwrap&wrap
|
||||
if ( wrapOuter.length > 0 || unwrapOuter.length > 0 ) {
|
||||
tx.pushReplace( unwrapOuter, wrapOuter );
|
||||
}
|
||||
|
||||
if ( wrapEach.length > 0 && unwrapEach.length > 0 ) {
|
||||
var closingUnwrapEach = closingArray( unwrapEach ),
|
||||
closingWrapEach = closingArray( wrapEach ),
|
||||
depth = 0,
|
||||
startOffset,
|
||||
i;
|
||||
// Visit each top-level child and wrap/unwrap it
|
||||
// TODO figure out if we should use the tree/node functions here
|
||||
// rather than iterating over offsets, it may or may not be faster
|
||||
for ( i = range.start; i < range.end; i++ ) {
|
||||
if ( this.data[offset].type === undefined ) {
|
||||
// This is a content offset, skip
|
||||
} else {
|
||||
// This is a structural offset
|
||||
if ( this.data[offset].type.charAt( 0 ) != '/' ) {
|
||||
// This is an opening element
|
||||
if ( depth == 0 ) {
|
||||
// We are at the start of a top-level element
|
||||
// Replace the opening elements
|
||||
tx.pushReplace( unwrapEach, wrapEach );
|
||||
// Store this offset for later
|
||||
startOffset = i;
|
||||
}
|
||||
depth++;
|
||||
} else {
|
||||
// This is a closing element
|
||||
depth--;
|
||||
if ( depth == 0 ) {
|
||||
// We are at the end of a top-level element
|
||||
// Retain the contents of what we're wrapping
|
||||
tx.pushRetain( i - startOffset + 1 - unwrapEach.length*2 );
|
||||
// Replace the closing elements
|
||||
tx.pushReplace( closingUnwrapEach, closingWrapEach );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no wrapEach/unwrapEach to be done, just retain
|
||||
// up to the end of the range
|
||||
tx.pushRetain( range.end - range.start );
|
||||
}
|
||||
|
||||
if ( wrapOuter.length > 0 || unwrapOuter.length > 0 ) {
|
||||
tx.pushReplace( closingArray( unwrapOuter ), closingArray( wrapOuter ) );
|
||||
}
|
||||
|
||||
// Retain up to the end of the document
|
||||
if ( range.end < this.data.length ) {
|
||||
tx.pushRetain( this.data.length - range.end - unwrapOuter.length );
|
||||
}
|
||||
|
||||
return tx;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -800,3 +800,19 @@ test( 've.dm.DocumentNode.prepareInsertion', 11, function() {
|
|||
'prepareInsertion throws exception for malformed input'
|
||||
);
|
||||
} );
|
||||
|
||||
test( 've.dm.DocumentNode.prepareWrap', 1, function() {
|
||||
var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
|
||||
|
||||
// Test 1
|
||||
deepEqual(
|
||||
documentModel.prepareWrap( new ve.Range( 1, 4 ), [ { 'type': 'paragraph' } ], [ { 'type': 'heading', 'level': 2 } ], [], [] ).getOperations(),
|
||||
[
|
||||
{ 'type': 'replace', 'remove': [ { 'type': 'paragraph' } ], 'replacement': [ { 'type': 'heading', 'level': 2 } ] },
|
||||
{ 'type': 'retain', 'length': 3 },
|
||||
{ 'type': 'replace', 'remove': [ { 'type': '/paragraph' } ], 'replacement': [ { 'type': '/heading' } ] },
|
||||
{ 'type': 'retain', 'length': 29 }
|
||||
],
|
||||
'prepareWrap changes a paragraph to a heading'
|
||||
);
|
||||
} );
|
|
@ -1,6 +1,6 @@
|
|||
module( 've/dm' );
|
||||
|
||||
test( 've.dm.TransactionProcessor', 33, function() {
|
||||
test( 've.dm.TransactionProcessor', 35, function() {
|
||||
var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
|
||||
|
||||
// FIXME: These tests shouldn't use prepareFoo() because those functions
|
||||
|
@ -404,4 +404,34 @@ test( 've.dm.TransactionProcessor', 33, function() {
|
|||
],
|
||||
'rollback restores content'
|
||||
);
|
||||
|
||||
var paragraphToHeading = documentModel.prepareWrap( new ve.Range( 1, 4 ), [ { 'type': 'paragraph' } ], [ { 'type': 'heading', 'level': 2 } ], [], [] );
|
||||
|
||||
// Test 33
|
||||
ve.dm.TransactionProcessor.commit( documentModel, paragraphToHeading );
|
||||
deepEqual(
|
||||
documentModel.getData( new ve.Range( 0, 5 ) ),
|
||||
[
|
||||
{ 'type': 'heading', 'level': 2 },
|
||||
'a',
|
||||
['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
|
||||
['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
|
||||
{ 'type': '/heading' }
|
||||
],
|
||||
'changing paragraph to heading'
|
||||
);
|
||||
|
||||
// Test 34
|
||||
ve.dm.TransactionProcessor.rollback( documentModel, paragraphToHeading );
|
||||
deepEqual(
|
||||
documentModel.getData( new ve.Range( 0, 5 ) ),
|
||||
[
|
||||
{ 'type': 'paragraph' },
|
||||
'a',
|
||||
['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
|
||||
['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
|
||||
{ 'type': '/paragraph' }
|
||||
],
|
||||
'rollback puts paragraph back'
|
||||
);
|
||||
} );
|
||||
|
|
Loading…
Reference in a new issue