mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-25 00:05:29 +00:00
02530f19e1
This is especially important for combined notifications and notification lists from different sources; the model list should be sorted to reflect items by timestamp and unread status. Note: The dm.List and dm.SortedList now mirror the structures OO.EmitterList and OO.SortedEmitterList that are awaiting to be added for oojs in Ib94e4e4a49 and I3fd569691549 respectively. Once those are available, the dm.List and dm.SortedList can be removed, and the model can mixin OO.SortedEmitterList instead. Change-Id: I97e1ecbe5dccc478be527a94f037500f78f74b14
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 ) );
|