mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-25 00:05:29 +00:00
165 lines
4.6 KiB
JavaScript
165 lines
4.6 KiB
JavaScript
|
( function ( mw, oo ) {
|
||
|
/**
|
||
|
* @class mw.echo.dm.SortedList
|
||
|
* Contains and a sorted mw.echo.dm.List
|
||
|
*
|
||
|
* @constructor
|
||
|
*/
|
||
|
mw.echo.dm.SortedList = function OoSortedEmitterList() {
|
||
|
// Mixin constructors
|
||
|
mw.echo.dm.List.call( this );
|
||
|
|
||
|
this.sortingCallback = function ( a, b ) {
|
||
|
return a.getTimestamp() - b.getTimestamp();
|
||
|
};
|
||
|
|
||
|
// Listen to sortChange event and make sure
|
||
|
// we re-sort the changed item when that happens
|
||
|
this.aggregate( {
|
||
|
sortChange: 'itemSortChange'
|
||
|
} );
|
||
|
|
||
|
this.connect( this, {
|
||
|
itemSortChange: 'onItemSortChange'
|
||
|
} );
|
||
|
};
|
||
|
|
||
|
oo.mixinClass( mw.echo.dm.SortedList, mw.echo.dm.List );
|
||
|
|
||
|
/**
|
||
|
* Handle a case where an item changed a property that relates
|
||
|
* to its sorted order
|
||
|
*
|
||
|
* @param {mw.echo.dm.NotificationItem} item Item in the list
|
||
|
*/
|
||
|
mw.echo.dm.SortedList.prototype.onItemSortChange = function ( item ) {
|
||
|
// Remove the item
|
||
|
this.removeItems( item );
|
||
|
// Re-add the item so it is in the correct place
|
||
|
this.addItems( item );
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set the sorting callback for this sorted list.
|
||
|
*
|
||
|
* @param {Function} sortingCallback Sorting callback
|
||
|
*/
|
||
|
mw.echo.dm.SortedList.prototype.setSortingCallback = function ( sortingCallback ) {
|
||
|
var items = this.getItems();
|
||
|
|
||
|
this.sortingCallback = sortingCallback;
|
||
|
|
||
|
// Empty the list
|
||
|
this.clearItems();
|
||
|
// Re-add the items in the new order
|
||
|
this.addItems( items );
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add items to the sorted list.
|
||
|
*
|
||
|
* @param {OO.EventEmitter|OO.EventEmitter[]} items Item to add or
|
||
|
* an array of items to add
|
||
|
*/
|
||
|
mw.echo.dm.SortedList.prototype.addItems = function ( items ) {
|
||
|
var index, i, insertionIndex;
|
||
|
|
||
|
if ( !Array.isArray( items ) ) {
|
||
|
items = [ items ];
|
||
|
}
|
||
|
|
||
|
if ( items.length === 0 ) {
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
// Call parent mixin
|
||
|
for ( i = 0; i < items.length; i++ ) {
|
||
|
// Find insertion index
|
||
|
insertionIndex = this.findInsertionIndex( items[ i ] );
|
||
|
|
||
|
// Check if the item exists using the sorting callback
|
||
|
// and remove it first if it exists
|
||
|
if (
|
||
|
// First make sure the insertion index is not at the end
|
||
|
// of the list (which means it does not point to any actual
|
||
|
// items)
|
||
|
insertionIndex <= this.items.length &&
|
||
|
// Make sure there actually is an item in this index
|
||
|
this.items[ insertionIndex ] &&
|
||
|
// The callback returns 0 if the items are equal
|
||
|
this.sortingCallback( this.items[ insertionIndex ], items[ i ] ) === 0
|
||
|
) {
|
||
|
// Remove the existing item
|
||
|
this.removeItems( this.items[ insertionIndex ] );
|
||
|
}
|
||
|
|
||
|
// Insert item at the insertion index
|
||
|
index = this.insertItem( items[ i ], insertionIndex );
|
||
|
this.emit( 'add', items[ i ], insertionIndex );
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Normalize requested index to fit into the array.
|
||
|
* In the case of a sorted list, the index
|
||
|
*
|
||
|
* @param {OO.EventEmitter} item Items to insert
|
||
|
* @return {number} The index the item should be inserted into
|
||
|
*/
|
||
|
mw.echo.dm.SortedList.prototype.findInsertionIndex = function ( item ) {
|
||
|
var list = this;
|
||
|
|
||
|
return this.binarySearchIndex(
|
||
|
this.items,
|
||
|
// Fake a this.sortingCallback.bind( null, item ) call here
|
||
|
// otherwise this doesn't pass tests in phantomJS
|
||
|
function ( otherItem ) {
|
||
|
return list.sortingCallback( item, otherItem );
|
||
|
},
|
||
|
true
|
||
|
);
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Use binary search to locate an element in a sorted array.
|
||
|
*
|
||
|
* searchFunc is given an element from the array. `searchFunc(elem)` must return a number
|
||
|
* above 0 if the element we're searching for is to the right of (has a higher index than) elem,
|
||
|
* below 0 if it is to the left of elem, or zero if it's equal to elem.
|
||
|
*
|
||
|
* To search for a specific value with a comparator function (a `function cmp(a,b)` that returns
|
||
|
* above 0 if `a > b`, below 0 if `a < b`, and 0 if `a == b`), you can use
|
||
|
* `searchFunc = cmp.bind( null, value )`.
|
||
|
*
|
||
|
* @param {Array} arr Array to search in
|
||
|
* @param {Function} searchFunc Search function
|
||
|
* @param {boolean} [forInsertion] If not found, return index where val could be inserted
|
||
|
* @return {number|null} Index where val was found, or null if not found
|
||
|
*/
|
||
|
mw.echo.dm.SortedList.prototype.binarySearchIndex = function ( arr, searchFunc, forInsertion ) {
|
||
|
// TODO: Replace this with OO.binarySearch
|
||
|
// See https://gerrit.wikimedia.org/r/#/c/246813/
|
||
|
var mid, cmpResult,
|
||
|
left = 0,
|
||
|
right = arr.length;
|
||
|
|
||
|
while ( left < right ) {
|
||
|
// Equivalent to Math.floor( ( left + right ) / 2 ) but much faster
|
||
|
/*jshint bitwise:false */
|
||
|
mid = ( left + right ) >> 1;
|
||
|
cmpResult = searchFunc( arr[ mid ] );
|
||
|
if ( cmpResult < 0 ) {
|
||
|
right = mid;
|
||
|
} else if ( cmpResult > 0 ) {
|
||
|
left = mid + 1;
|
||
|
} else {
|
||
|
return mid;
|
||
|
}
|
||
|
}
|
||
|
return forInsertion ? right : null;
|
||
|
};
|
||
|
}( mediaWiki, OO ) );
|