mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-28 17:50:39 +00:00
1ac72cc01a
Split the notifications into 'alert' and 'message' badget with two different flyouts. Also clean up styling and module behavior. ** Depends on ooui change Id4bbe14ba0bf6c for footers in popups. ** Depends on ooui change Ie93e4d6ed5637c for fixing a bug in inverted icons. ** MobileFrontend must also be updated to support the new modules in this patch I168f485d6e54cb4067 In this change: * Split notifcations into alert and messages and display those in two different badges. * Create two separate flyout/popups for each category with their notifications. * Create a view-model to control notification state and emit events for both the popup and the badge to intercept and react to. * Clean up module load and distribution: * Create an ext.echo.ui module for javascript-ui support and ooui widgets. * Create an ext.echo.nojs module that unifies all base classes that are needed for both nojs and js support, that the js version builds upon. * Create a separate ext.echo.logger module as a singleton that can be called to perform all logging. * Clean up style uses * Move the special page LESS file into nojs module so all styles load properly even in nojs mode. * Transfer some of the styling from JS to LESS for consistency. * Make the 'read more' button load already with the styles it needs to look like a button, since its behavior is similar in nojs and js vesions, but before its classes were applied only by the js, making it inconsistent and also making its appearance 'jump' from a link to a button. * Delete and clean up all old and unused files. * Moved 'Help.png' icon from modules/overlay to modules/icons for later use. Bug: T108190 Change-Id: I55f440ed9f64c46817f620328a6bb522d44c9ca9
273 lines
7 KiB
JavaScript
273 lines
7 KiB
JavaScript
( function ( $, mw ) {
|
||
/**
|
||
* Echo List mixin
|
||
*
|
||
* @mixin
|
||
* @abstract
|
||
* @constructor
|
||
* @param {Object} config Configuration options
|
||
*/
|
||
mw.echo.dm.List = function mwFlowDmList( config ) {
|
||
// Configuration initialization
|
||
config = config || {};
|
||
|
||
this.items = [];
|
||
|
||
// Store references to items by their ids
|
||
this.itemsById = {};
|
||
|
||
this.aggregateItemEvents = {};
|
||
};
|
||
|
||
/* Events */
|
||
|
||
/**
|
||
* @event add Items have been added
|
||
* @param {mw.echo.dm.NotificationItem[]} items Added items
|
||
* @param {number} index Index items were added at
|
||
*/
|
||
|
||
/**
|
||
* @event remove Items have been removed
|
||
* @param {mw.echo.dm.NotificationItem[]} items Removed items
|
||
*/
|
||
|
||
/**
|
||
* @event clear All items have been removed
|
||
*/
|
||
|
||
/* Methods */
|
||
|
||
/**
|
||
* Get all items
|
||
*
|
||
* @return {mw.echo.dm.NotificationItem[]} Items in the list
|
||
*/
|
||
mw.echo.dm.List.prototype.getItems = function () {
|
||
return this.items.slice( 0 );
|
||
};
|
||
|
||
/**
|
||
* Get an item by its id
|
||
* @param {string} id Item id
|
||
* @return {mw.echo.dm.NotificationItem} Item
|
||
*/
|
||
mw.echo.dm.List.prototype.getItemById = function ( id ) {
|
||
return this.itemsById[ id ];
|
||
};
|
||
|
||
/**
|
||
* Get the index of a specific item
|
||
*
|
||
* @param {mw.echo.dm.NotificationItem} item Requested item
|
||
* @return {number} Index of the item
|
||
*/
|
||
mw.echo.dm.List.prototype.getItemIndex = function ( item ) {
|
||
return this.items.indexOf( item );
|
||
};
|
||
|
||
/**
|
||
* Get number of items
|
||
*
|
||
* @return {number} Number of items in the list
|
||
*/
|
||
mw.echo.dm.List.prototype.getItemCount = function () {
|
||
return this.items.length;
|
||
};
|
||
|
||
/**
|
||
* Check if a list contains no items.
|
||
*
|
||
* @return {boolean} Group is empty
|
||
*/
|
||
mw.echo.dm.List.prototype.isEmpty = function () {
|
||
return !this.items.length;
|
||
};
|
||
|
||
/**
|
||
* Aggregate the events emitted by the group.
|
||
* Taken from oojs-ui's OO.ui.GroupElement#aggregate
|
||
*
|
||
* When events are aggregated, the group will listen to all contained items for the event,
|
||
* and then emit the event under a new name. The new event will contain an additional leading
|
||
* parameter containing the item that emitted the original event. Other arguments emitted from
|
||
* the original event are passed through.
|
||
*
|
||
* @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
|
||
* aggregated (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’).
|
||
* A `null` value will remove aggregated events.
|
||
|
||
* @throws {Error} An error is thrown if aggregation already exists.
|
||
*/
|
||
mw.echo.dm.List.prototype.aggregate = function ( events ) {
|
||
var i, len, item, add, remove, itemEvent, groupEvent;
|
||
|
||
for ( itemEvent in events ) {
|
||
groupEvent = events[ itemEvent ];
|
||
|
||
// Remove existing aggregated event
|
||
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
|
||
// Don't allow duplicate aggregations
|
||
if ( groupEvent ) {
|
||
throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
|
||
}
|
||
// Remove event aggregation from existing items
|
||
for ( i = 0, len = this.items.length; i < len; i++ ) {
|
||
item = this.items[ i ];
|
||
if ( item.connect && item.disconnect ) {
|
||
remove = {};
|
||
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
|
||
item.disconnect( this, remove );
|
||
}
|
||
}
|
||
// Prevent future items from aggregating event
|
||
delete this.aggregateItemEvents[ itemEvent ];
|
||
}
|
||
|
||
// Add new aggregate event
|
||
if ( groupEvent ) {
|
||
// Make future items aggregate event
|
||
this.aggregateItemEvents[ itemEvent ] = groupEvent;
|
||
// Add event aggregation to existing items
|
||
for ( i = 0, len = this.items.length; i < len; i++ ) {
|
||
item = this.items[ i ];
|
||
if ( item.connect && item.disconnect ) {
|
||
add = {};
|
||
add[ itemEvent ] = [ 'emit', groupEvent, item ];
|
||
item.connect( this, add );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Add items
|
||
*
|
||
* @param {mw.echo.dm.NotificationItem[]} items Items to add
|
||
* @param {number} index Index to add items at
|
||
* @chainable
|
||
* @fires add
|
||
*/
|
||
mw.echo.dm.List.prototype.addItems = function ( items, index ) {
|
||
var i, len, item, event, events, currentIndex, existingItem, at;
|
||
|
||
if ( items.length === 0 ) {
|
||
return this;
|
||
}
|
||
|
||
// Support adding existing items at new locations
|
||
for ( i = 0, len = items.length; i < len; i++ ) {
|
||
item = items[i];
|
||
existingItem = this.getItemById( item.getId() );
|
||
|
||
// Check if item exists then remove it first, effectively "moving" it
|
||
currentIndex = this.items.indexOf( existingItem );
|
||
if ( currentIndex >= 0 ) {
|
||
this.removeItems( [ existingItem ] );
|
||
// Adjust index to compensate for removal
|
||
if ( currentIndex < index ) {
|
||
index--;
|
||
}
|
||
}
|
||
|
||
// Add the item
|
||
if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
|
||
events = {};
|
||
for ( event in this.aggregateItemEvents ) {
|
||
events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
|
||
}
|
||
item.connect( this, events );
|
||
}
|
||
|
||
// Add by reference
|
||
this.itemsById[ item.getId() ] = items[i];
|
||
}
|
||
|
||
if ( index === undefined || index < 0 || index >= this.items.length ) {
|
||
at = this.items.length;
|
||
this.items.push.apply( this.items, items );
|
||
} else if ( index === 0 ) {
|
||
at = 0;
|
||
this.items.unshift.apply( this.items, items );
|
||
} else {
|
||
at = index;
|
||
this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
|
||
}
|
||
this.emit( 'add', items, at );
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Remove items
|
||
*
|
||
* @param {mw.echo.dm.NotificationItem[]} items Items to remove
|
||
* @chainable
|
||
* @fires remove
|
||
*/
|
||
mw.echo.dm.List.prototype.removeItems = function ( items ) {
|
||
var i, len, item, index, remove, itemEvent,
|
||
removed = [];
|
||
|
||
if ( items.length === 0 ) {
|
||
return this;
|
||
}
|
||
|
||
// Remove specific items
|
||
for ( i = 0, len = items.length; i < len; i++ ) {
|
||
item = items[ i ];
|
||
index = this.items.indexOf( item );
|
||
if ( index !== -1 ) {
|
||
if (
|
||
item.connect && item.disconnect &&
|
||
!$.isEmptyObject( this.aggregateItemEvents )
|
||
) {
|
||
remove = {};
|
||
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
|
||
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
|
||
}
|
||
item.disconnect( this, remove );
|
||
}
|
||
this.items.splice( index, 1 );
|
||
// Remove reference by Id
|
||
delete this.itemsById[ item.getId() ];
|
||
}
|
||
}
|
||
this.emit( 'remove', removed );
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Clear all items
|
||
*
|
||
* @fires clear
|
||
*/
|
||
mw.echo.dm.List.prototype.clearItems = function () {
|
||
var i, len, item, remove, itemEvent;
|
||
|
||
// Remove all items
|
||
for ( i = 0, len = this.items.length; i < len; i++ ) {
|
||
item = this.items[ i ];
|
||
if (
|
||
item.connect && item.disconnect &&
|
||
!$.isEmptyObject( this.aggregateItemEvents )
|
||
) {
|
||
remove = {};
|
||
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
|
||
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
|
||
}
|
||
item.disconnect( this, remove );
|
||
}
|
||
}
|
||
|
||
this.items = [];
|
||
this.itemsById = {};
|
||
|
||
this.emit( 'clear' );
|
||
|
||
return this;
|
||
};
|
||
}( jQuery, mediaWiki ) );
|