summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js')
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js244
1 files changed, 244 insertions, 0 deletions
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js b/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js
new file mode 100644
index 00000000..14348d5f
--- /dev/null
+++ b/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js
@@ -0,0 +1,244 @@
+( function ( mw, $ ) {
+ /**
+ * Notifications list widget.
+ * All of its items must be of the mw.echo.ui.NotificationItem type.
+ *
+ * @class
+ * @extends mw.echo.ui.SortedListWidget
+ *
+ * @constructor
+ * @param {mw.echo.Controller} controller Echo notifications controller
+ * @param {mw.echo.dm.ModelManager} manager Model manager
+ * @param {Object} [config] Configuration object
+ * marked as read when they are seen.
+ * @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
+ * for popups.
+ */
+ mw.echo.ui.NotificationsListWidget = function MwEchoUiNotificationsListWidget( controller, manager, config ) {
+ config = config || {};
+ // Parent constructor
+ mw.echo.ui.NotificationsListWidget.parent.call(
+ this,
+ // Sorting callback
+ function ( a, b ) {
+ if ( !a.isRead() && b.isRead() ) {
+ return -1; // Unread items are always above read items
+ } else if ( a.isRead() && !b.isRead() ) {
+ return 1;
+ } else if ( !a.isForeign() && b.isForeign() ) {
+ return -1;
+ } else if ( a.isForeign() && !b.isForeign() ) {
+ return 1;
+ }
+
+ // Reverse sorting
+ if ( b.getTimestamp() < a.getTimestamp() ) {
+ return -1;
+ } else if ( b.getTimestamp() > a.getTimestamp() ) {
+ return 1;
+ }
+
+ // Fallback on IDs
+ return b.getId() - a.getId();
+ },
+ config
+ );
+
+ // Initialize models
+ this.controller = controller;
+ this.manager = manager;
+ this.models = {};
+
+ // Properties
+ this.$overlay = config.$overlay || this.$element;
+ this.timestamp = config.timestamp || 0;
+
+ // Dummy 'loading' option widget
+ this.loadingOptionWidget = new mw.echo.ui.PlaceholderItemWidget();
+
+ this.resetLoadingOption();
+
+ this.manager.connect( this, {
+ update: 'resetDataFromModel',
+ discard: 'onModelManagerDiscard'
+ } );
+
+ this.$element
+ .addClass( 'mw-echo-ui-notificationsListWidget' );
+ };
+
+ /* Initialization */
+
+ OO.inheritClass( mw.echo.ui.NotificationsListWidget, mw.echo.ui.SortedListWidget );
+
+ /* Events */
+
+ /**
+ * @event modified
+ *
+ * The content of this list has changed.
+ * This event is to state that not only has the content changed
+ * but the actual DOM has been manipulated.
+ */
+
+ /* Methods */
+
+ mw.echo.ui.NotificationsListWidget.prototype.onModelManagerDiscard = function ( modelName ) {
+ var i,
+ items = this.getItems();
+
+ // For the moment, this is only relevant for xwiki bundles.
+ // Local single items will not get their entire model removed, but
+ // local bundles may - when that happens, the condition below should
+ // also deal with local bundles and removing them specifically
+ if ( modelName === 'xwiki' ) {
+ for ( i = 0; i < items.length; i++ ) {
+ if ( items[ i ] instanceof mw.echo.ui.CrossWikiNotificationItemWidget ) {
+ this.removeItems( [ items[ i ] ] );
+ this.checkForEmptyNotificationsList();
+ return;
+ }
+ }
+ }
+
+ this.emit( 'modified' );
+ };
+
+ /**
+ * Respond to model manager update event.
+ * This event means we are repopulating the entire list and the
+ * associated models within it.
+ *
+ * @param {Object} models Object of new models to populate the
+ * list.
+ * @fires modified
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.resetDataFromModel = function ( models ) {
+ var i, modelId, model, subItems, subItem, widget,
+ itemWidgets = [],
+ $elements = $();
+
+ // Detach all attached models
+ for ( modelId in this.models ) {
+ this.detachModel( modelId );
+ }
+
+ // Attach and process new models
+ for ( modelId in models ) {
+ model = models[ modelId ];
+ this.attachModel( modelId, model );
+
+ // Build widgets based on the data in the model
+ if ( model.isGroup() ) {
+ if ( model.isForeign() ) {
+ // One Widget to Rule Them All
+ widget = new mw.echo.ui.CrossWikiNotificationItemWidget(
+ this.controller,
+ model,
+ {
+ $overlay: this.$overlay,
+ animateSorting: this.animated
+ }
+ );
+ } else {
+ // local bundle
+ widget = new mw.echo.ui.BundleNotificationItemWidget(
+ this.controller,
+ model,
+ {
+ $overlay: this.$overlay,
+ bundle: false,
+ animateSorting: this.animated
+ }
+ );
+ }
+ itemWidgets.push( widget );
+ $elements = $elements.add( widget.$element );
+ } else {
+ subItems = model.getItems();
+ // Separate widgets per item
+ for ( i = 0; i < subItems.length; i++ ) {
+ subItem = subItems[ i ];
+ widget = new mw.echo.ui.SingleNotificationItemWidget(
+ this.controller,
+ subItem,
+ {
+ $overlay: this.$overlay,
+ bundle: false
+ }
+ );
+ itemWidgets.push( widget );
+ $elements = $elements.add( widget.$element );
+ }
+ }
+ }
+
+ // Reset the current items and re-add the new item widgets
+ this.clearItems();
+
+ // fire render hook
+ mw.hook( 'ext.echo.notifications.beforeRender' ).fire( this.$element, $elements );
+
+ this.addItems( itemWidgets );
+
+ this.checkForEmptyNotificationsList();
+
+ this.emit( 'modified' );
+ };
+
+ /**
+ * Attach a model to the widget
+ *
+ * @param {string} modelId Symbolic name for the model
+ * @param {mw.echo.dm.SortedList} model Notifications list model
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.attachModel = function ( modelId, model ) {
+ this.models[ modelId ] = model;
+ };
+
+ /**
+ * Detach a model from the widget
+ *
+ * @param {string} modelId Notifications list model
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.detachModel = function ( modelId ) {
+ this.models[ modelId ].disconnect( this );
+ delete this.models[ modelId ];
+ };
+
+ /**
+ * Reset the loading 'dummy' option widget
+ *
+ * @param {string} [label] Label for the option widget
+ * @param {string} [link] Link for the option widget
+ * @fires modified
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.resetLoadingOption = function ( label, link ) {
+ this.loadingOptionWidget.setLabel( label || '' );
+ this.loadingOptionWidget.setLink( link || '' );
+ if ( this.isEmpty() ) {
+ this.addItems( [ this.loadingOptionWidget ] );
+ }
+ this.emit( 'modified' );
+ };
+
+ /**
+ * Check if the list of notifications is empty and udpate the placeholder
+ * widget as needed.
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.checkForEmptyNotificationsList = function () {
+ this.resetLoadingOption( this.isEmpty() ? mw.msg( 'echo-notification-placeholder' ) : '' );
+ };
+
+ /**
+ * Reset the 'initiallyUnseen' state of all items
+ */
+ mw.echo.ui.NotificationsListWidget.prototype.resetInitiallyUnseenItems = function () {
+ var i,
+ itemWidgets = this.getItems();
+
+ for ( i = 0; i < itemWidgets.length; i++ ) {
+ itemWidgets[ i ].resetInitiallyUnseen();
+ }
+ };
+}( mediaWiki, jQuery ) );