%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/dm/ |
| Current File : //www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/dm/ve.dm.MetaList.js |
/*!
* VisualEditor DataModel MetaList class.
*
* @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
*/
/**
* DataModel meta item.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {ve.dm.Surface} surface Surface model
*/
ve.dm.MetaList = function VeDmMetaList( surface ) {
var i, j, jlen, metadata, item, group;
// Mixin constructors
OO.EventEmitter.call( this );
// Properties
this.surface = surface;
this.document = surface.getDocument();
this.groups = {};
this.items = [];
// Event handlers
this.document.connect( this, { transact: 'onTransact' } );
// Populate from document
metadata = this.document.getMetadata();
for ( i in metadata ) {
if ( Object.prototype.hasOwnProperty.call( metadata, i ) && Array.isArray( metadata[ i ] ) ) {
for ( j = 0, jlen = metadata[ i ].length; j < jlen; j++ ) {
item = ve.dm.metaItemFactory.createFromElement( metadata[ i ][ j ] );
group = this.groups[ item.getGroup() ];
if ( !group ) {
group = this.groups[ item.getGroup() ] = [];
}
item.attach( this, Number( i ), j );
group.push( item );
this.items.push( item );
}
}
}
};
/* Inheritance */
OO.mixinClass( ve.dm.MetaList, OO.EventEmitter );
/* Events */
/**
* @event insert
* @param {ve.dm.MetaItem} item Item that was inserted
*/
/**
* @event remove
* @param {ve.dm.MetaItem} item Item that was removed
* @param {number} offset Linear model offset that the item was at
* @param {number} index Index within that offset the item was at
*/
/* Methods */
/**
* Event handler for transactions on the document.
*
* When a transaction occurs, update this list to account for it:
* - insert items for new metadata that was inserted
* - remove items for metadata that was removed
* - translate offsets and recompute indices for metadata that has shifted
*
* @param {ve.dm.Transaction} tx Transaction that was applied to the document
* @fires insert
* @fires remove
*/
ve.dm.MetaList.prototype.onTransact = function ( tx ) {
var i, ilen, j, jlen, k, klen, item, ins, rm, insMeta, rmMeta,
numItems = this.items.length,
itemIndex = 0, // Current index into this.items
offset = 0, // Current pre-transaction offset
newOffset = 0, // Current post-transaction offset
index = 0, // Current pre-transaction index
newIndex = 0, // Current post-transaction index
// Array of items that should appear in this.items after we're done. This includes newly
// inserted items as well as existing items that aren't being removed.
// [ { item: ve.dm.MetaItem, offset: offset to move to, index: index to move to } ]
newItems = [],
removedItems = [], // Array of items that should be removed from this.items
events = [], // Array of events that we should emit when we're done
ops = tx.getOperations();
// Go through the transaction operations and plan out where to add, remove and move items. We
// don't actually touch this.items yet, otherwise we 1) get it out of order which breaks
// findItem() and 2) lose information about what the pre-transaction state of this.items was.
for ( i = 0, ilen = ops.length; i < ilen; i++ ) {
switch ( ops[ i ].type ) {
case 'retain':
// Advance itemIndex through the retain and update items we encounter along the way
for ( ;
itemIndex < numItems && this.items[ itemIndex ].offset < offset + ops[ i ].length;
itemIndex++
) {
// Plan to move this item to the post-transaction offset and index
newItems.push( {
item: this.items[ itemIndex ],
offset: this.items[ itemIndex ].offset + newOffset - offset,
index: this.items[ itemIndex ].offset === offset ?
// Adjust index for insertions or removals that happened at this offset
newIndex - index + this.items[ itemIndex ].index :
// Offset is retained over completely, don't adjust index
this.items[ itemIndex ].index
} );
}
offset += ops[ i ].length;
newOffset += ops[ i ].length;
index = 0;
newIndex = 0;
break;
case 'retainMetadata':
// Advance itemIndex through the retain and update items we encounter along the way
for ( ;
itemIndex < numItems && this.items[ itemIndex ].offset === offset &&
this.items[ itemIndex ].index < index + ops[ i ].length;
itemIndex++
) {
newItems.push( {
item: this.items[ itemIndex ],
offset: newOffset,
index: this.items[ itemIndex ].index + newIndex - index
} );
}
index += ops[ i ].length;
newIndex += ops[ i ].length;
break;
case 'replace':
ins = ops[ i ].insert;
rm = ops[ i ].remove;
if ( ops[ i ].removeMetadata !== undefined ) {
insMeta = ops[ i ].insertMetadata;
rmMeta = ops[ i ].removeMetadata;
// Process removed metadata
for ( ;
itemIndex < numItems &&
this.items[ itemIndex ].offset < offset + rmMeta.length;
itemIndex++
) {
removedItems.push( this.items[ itemIndex ] );
}
// Process inserted metadata
for ( j = 0, jlen = insMeta.length; j < jlen; j++ ) {
if ( insMeta[ j ] ) {
for ( k = 0, klen = insMeta[ j ].length; k < klen; k++ ) {
item = ve.dm.metaItemFactory.createFromElement( insMeta[ j ][ k ] );
newItems.push( {
item: item,
offset: newOffset + j,
index: k
} );
}
}
}
} else {
// No metadata handling specified, which means we just have to deal with offset
// adjustments, same as a retain
for ( ;
itemIndex < numItems &&
this.items[ itemIndex ].offset < offset + rm.length;
itemIndex++
) {
newItems.push( {
item: this.items[ itemIndex ],
offset: this.items[ itemIndex ].offset + newOffset - offset,
index: this.items[ itemIndex ].index
} );
}
}
offset += rm.length;
newOffset += ins.length;
break;
case 'replaceMetadata':
insMeta = ops[ i ].insert;
rmMeta = ops[ i ].remove;
// Process removed items
for ( ;
itemIndex < numItems && this.items[ itemIndex ].offset === offset &&
this.items[ itemIndex ].index < index + rmMeta.length;
itemIndex++
) {
removedItems.push( this.items[ itemIndex ] );
}
// Process inserted items
for ( j = 0, jlen = insMeta.length; j < jlen; j++ ) {
item = ve.dm.metaItemFactory.createFromElement( insMeta[ j ] );
newItems.push( { item: item, offset: newOffset, index: newIndex + j } );
}
index += rmMeta.length;
newIndex += insMeta.length;
break;
}
}
// Update the remaining items that the transaction didn't touch or retain over
for ( ; itemIndex < numItems; itemIndex++ ) {
newItems.push( {
item: this.items[ itemIndex ],
offset: this.items[ itemIndex ].offset + newOffset - offset,
index: this.items[ itemIndex ].offset === offset ?
newIndex - index + this.items[ itemIndex ].index :
this.items[ itemIndex ].index
} );
}
// Process the changes, and queue up events. We emit the events at the end when the MetaList
// is back in a consistent state
// Remove removed items
for ( i = 0, ilen = removedItems.length; i < ilen; i++ ) {
this.deleteRemovedItem( removedItems[ i ].offset, removedItems[ i ].index );
events.push( [
'remove', removedItems[ i ], removedItems[ i ].offset, removedItems[ i ].index
] );
}
// Move moved items (these appear as inserted items that are already attached)
for ( i = 0, ilen = newItems.length; i < ilen; i++ ) {
if ( newItems[ i ].item.isAttached() ) {
if ( newItems[ i ].offset !== newItems[ i ].item.offset || newItems[ i ].index !== newItems[ i ].item.index ) {
this.deleteRemovedItem( newItems[ i ].item.offset, newItems[ i ].item.index );
newItems[ i ].preExisting = true;
}
}
}
// Insert new items
for ( i = 0, ilen = newItems.length; i < ilen; i++ ) {
if ( !newItems[ i ].item.isAttached() ) {
this.addInsertedItem( newItems[ i ].offset, newItems[ i ].index, newItems[ i ].item );
if ( !newItems[ i ].preExisting ) {
events.push( [ 'insert', newItems[ i ].item ] );
}
}
}
// Emit events
for ( i = 0, ilen = events.length; i < ilen; i++ ) {
this.emit.apply( this, events[ i ] );
}
};
/**
* Find an item by its offset, index and group.
*
* This function is mostly for internal usage.
*
* @param {number} offset Offset in the linear model
* @param {number} index Index in the metadata array associated with that offset
* @param {string} [group] Group to search in. If not set, search in all groups
* @param {boolean} [forInsertion] If the item is not found, return the index where it should have
* been rather than null
* @return {number|null} Index into this.items or this.groups[group] where the item was found, or
* null if not found
*/
ve.dm.MetaList.prototype.findItem = function ( offset, index, group, forInsertion ) {
var items = typeof group === 'string' ? ( this.groups[ group ] || [] ) : this.items;
return ve.binarySearch( items, function ( item ) {
return ve.compareTuples(
[ offset, index ],
[ item.getOffset(), item.getIndex() ]
);
}, forInsertion );
};
/**
* Get the item at a given offset and index, if there is one.
*
* @param {number} offset Offset in the linear model
* @param {number} index Index in the metadata array
* @return {ve.dm.MetaItem|null} The item at (offset,index), or null if not found
*/
ve.dm.MetaList.prototype.getItemAt = function ( offset, index ) {
var at = this.findItem( offset, index );
return at === null ? null : this.items[ at ];
};
/**
* Get all items in a group.
*
* This function returns a shallow copy, so the array isn't returned by reference but the items
* themselves are.
*
* @param {string} group Group
* @return {ve.dm.MetaItem[]} Array of items in the group (shallow copy)
*/
ve.dm.MetaList.prototype.getItemsInGroup = function ( group ) {
return ( this.groups[ group ] || [] ).slice( 0 );
};
/**
* Get all items in the list.
*
* This function returns a shallow copy, so the array isn't returned by reference but the items
* themselves are.
*
* @return {ve.dm.MetaItem[]} Array of items in the list
*/
ve.dm.MetaList.prototype.getAllItems = function () {
return this.items.slice( 0 );
};
/**
* Insert new metadata into the document. This builds and processes a transaction that inserts
* metadata into the document.
*
* Pass a plain object rather than a MetaItem into this function unless you know what you're doing.
*
* @param {Object|ve.dm.MetaItem} meta Metadata element (or MetaItem) to insert
* @param {number} [offset] Offset to insert the new metadata, or undefined to add to the end
* @param {number} [index] Index to insert the new metadata, or undefined to add to the end
*/
ve.dm.MetaList.prototype.insertMeta = function ( meta, offset, index ) {
var tx;
if ( meta instanceof ve.dm.MetaItem ) {
meta = meta.getElement();
}
if ( offset === undefined ) {
offset = this.document.data.getLength();
}
if ( index === undefined ) {
index = ( this.document.metadata.getData( offset ) || [] ).length;
}
tx = ve.dm.Transaction.newFromMetadataInsertion( this.document, offset, index, [ meta ] );
this.surface.change( tx );
};
/**
* Remove a meta item from the document. This builds and processes a transaction that removes the
* associated metadata from the document.
*
* @param {ve.dm.MetaItem} item Item to remove
*/
ve.dm.MetaList.prototype.removeMeta = function ( item ) {
var tx;
tx = ve.dm.Transaction.newFromMetadataRemoval(
this.document,
item.getOffset(),
new ve.Range( item.getIndex(), item.getIndex() + 1 )
);
this.surface.change( tx );
};
/**
* Insert an item at a given offset and index in response to a transaction.
*
* This function is for internal usage by onTransact(). To actually insert an item, use
* insertMeta().
*
* @param {number} offset Offset in the linear model of the new item
* @param {number} index Index of the new item in the metadata array at offset
* @param {ve.dm.MetaItem} item Item object
* @fires insert
*/
ve.dm.MetaList.prototype.addInsertedItem = function ( offset, index, item ) {
var group = item.getGroup(),
at = this.findItem( offset, index, null, true );
this.items.splice( at, 0, item );
if ( this.groups[ group ] ) {
at = this.findItem( offset, index, group, true );
this.groups[ group ].splice( at, 0, item );
} else {
this.groups[ group ] = [ item ];
}
item.attach( this, offset, index );
};
/**
* Remove an item in response to a transaction.
*
* This function is for internal usage by onTransact(). To actually remove an item, use
* removeItem().
*
* @param {number} offset Offset in the linear model of the item
* @param {number} index Index of the item in the metadata array at offset
* @fires remove
*/
ve.dm.MetaList.prototype.deleteRemovedItem = function ( offset, index ) {
var item, group, at = this.findItem( offset, index );
if ( at === null ) {
return;
}
item = this.items[ at ];
group = item.getGroup();
this.items.splice( at, 1 );
at = this.findItem( offset, index, group );
if ( at !== null ) {
this.groups[ group ].splice( at, 1 );
}
item.detach( this );
return item;
};