MediaWiki:ConsistentNotifications.js

/** * * @module                  ConsistentModules * @description            Makes threaded notifications consistent with WDS. * @author                 Speedit * @version                0.8.0 * @license                CC-BY-SA 3.0 * @notes                  Broken - don't install this yet. * */ require(['jquery', 'wikia.window', 'wikia.nirvana', 'ext.wikia.design-system.loading-spinner'], function($, window, nirvana, Spinner) {    'use strict';    // Script variables    var conf = mw.config.get([ 'wgCityId', 'skin' ]);   // Double-run protection    window.dev = window.dev || {};    if ( window.dev.consistentnotifications || conf.skin !== 'oasis' ) {       return;    }    window.dev.consistentnotifications = true;    /**     * Main ConsistentNotifications class     * @class cn     */    var cn = {};    /**     * Threaded notifications dropdown.     * @member {jQuery} $dropdown     */    cn.$dropdown = $('.wds-global-navigation #notificationsEntryPoint');        /**         * Threaded notifications bubble.         * @member {jQuery} $bubble         */    cn.$bubble = cn.$dropdown.find('.bubbles');        /**         * Threaded notifications menu counter.         * @member {jQuery} $counter         */    cn.$counter = cn.$dropdown.find('.notifications-count');        /**         * Threaded notifications list container.         * @member {jQuery} $list         */    cn.$list = cn.$dropdown.find('#notifications');    /**     * ConsistentNotifications initialiser.     * @method init     */    cn.init = function {        // Replace threaded notif dropdown id        cn.$dropdown.attr('id', 'cn-entrypoint') // Override threaded notifications UI       cn.$list.replaceWith(cn.ui.render({ type: 'div', attr: { id: 'notifications', class: [ 'wds-dropdown__content', 'wds-is-right-aligned', 'wds-notifications__dropdown-content' ].join(' ') },           children: [ {                   type: 'div', attr: { id: 'cn-entrypoint_mark-all-as-read', class: 'wds-notifications__mark-all-as-read-button' },                   events: { click: cn.markread },                   children: [{ type: 'a', attr: { class: 'wds-notifications__mark-all-as-read' },                       text: mw.messages.get('notifications-mark-all-as-read') }]               },                {                    type: 'p', attr: { class: 'wds-notifications__zero-state wds-is-hidden' },                   text: mw.messages.get('notifications-no-notifications-message') },               {                    type: 'ul', attr: { class: [ 'wds-notifications__notification-list', 'wds-list wds-has-lines-between' ].join(' '), id: 'cn-entrypoint__list' },                   children: [{ type: 'li', attr: { class: 'wds-is-hidden', id: 'on-site-notifications-loader' },                       html: (function {                            return (new Spinner(14, 3)).html                                .replace('wds-block', 'wds-spinner__block')                                .replace('wds-path', 'wds-spinner__stroke');                        }) }]               },                {                    type: 'ul', attr: { class: 'wds-is-hidden', id: 'cn-entrypoint__cache' }               }            ]        }));        // Assigning elements as members $.extend(cn, {           /**              * Zero notification indicator             * @member {jQuery} $zero             * @memberof cn             */            $zero: cn.$dropdown.find('.wds-notifications__zero-state'),            /**              * Zero notification indicator             * @member {jQuery} $zero             * @memberof cn             */            $notifs: cn.$dropdown.find('.wds-notifications__notification-list'),            /**              * Zero notification indicator             * @member {jQuery} $zero             * @memberof cn             */            $spinner: cn.$dropdown.find('#on-site-notificatons-loader'),            /**              * Threaded notifications cache             * @member {jQuery} $cache             * @memberof cn             */            $cache: cn.$dropdown.find('#consistentNotificationsCache')        }); // Override threaded notifications UI       cn.$dropdown.off('mouseenter').on('mouseenter', cn.load); };   /**     * ConsistentNotifications loader. * Is initialised on any mouse interation with notifications. * @method load */   cn.load = function { // Load indicator cn.$spinner.removeClass('wds-is-hidden'); // Controller query var data = cn.params; nirvana.sendRequest({           controller: 'WallNotificationsExternalController',            method: 'getUpdateCounts',            format: 'json',            type: 'GET',            data: data,            callback: cn.fetch        }); };   /**     * Threaded notification data callback. * Updates counter and fetches per-wiki notifications. * @method fetch * @param {Object} d WallNotificationsExternalController 'getUpdateCounts' */   cn.fetch = function(d) { if (!d.status) { return; }       // Update bubble count if (!d.count) { cn.$bubble.removeClass('show'); cn.$counter.empty; } else { cn.$bubble.addClass('show') cn.$counter.text(d.count); console.log(cn); if (d.count !== cn.count) { // Reset notification cache and count cn.data = []; cn.$notifs.children(':not(#on-site-notifications-loader)').remove; cn.count = d.count; cn.$cache.empty; cn.$cache.append($(d.html).find('.notifications-for-wiki')); // Fetching per-wiki notifications cn.$cache.children.each(function {                   var id = parseInt($(this).attr('data-wiki-id'), 10),                        cw = (id === conf.wgCityId) ? '0' : '1',                        data = $.extend({ wikiId: id, isCrossWiki: cw                       }, cn.params);                    nirvana.sendRequest({ controller: 'WallNotificationsExternalController', method: 'getUpdateWiki', type: 'GET', data: data, callback: $.proxy(cn.wiki, { wiki: this, id: id }) });               });            }        }    };    /**     * Threaded notification wiki data method. * Fetches data, caches it and handles final render. * @method wiki * @param {Object} d WallNotificationsExternalController 'getUpdateWiki' * @this {Object} wiki element & id    */ cn.wiki = function(d) { // Restrict caching to valid notification data if (!d.status || !d.html.length) { return; } else if (d.html.indexOf('notification empty') > -1) { // Empty the wiki cache list $(this.wiki).empty; } else { // Store data in the DOM $(d.html).appendTo(cn.$cache); // Notification data extraction cn.data = cn.$cache.find('.notification:not(.empty)') .map(cn.serializer) .sort(cn.chronological); }       // Notification rendering if (!cn.$cache.find('.empty').exists) { cn.$spinner.addClass('wds-is-hidden'); if (cn.data.length > 0) { cn.$notifs.append(cn.ui.render(cn.data.map(cn.view))); } else { cn.$zero.removeClass('wds-is-hidden'); }       }    };    /**     * Threaded notification data serializer. * Extracts data from wall notifications cache. * @method serializer * @param n notification element * @returns {Object} notification data */   cn.serializer = function(n) { var $n = $(n); return { community: { id: $n.closest('.notifications-for-wiki').data('wiki-id'), name: $n.closest('.notifications-for-wiki') .find('.notifications-wiki-header').text.trim },           when: $n.find('time').attr('datetime'), actor: { name: $n.find('.avatars img').attr('alt'), img: $n.find('.avatars img').attr('src') },           read: $n.hasClass('.read'), uri: $n.children('a').attr('href'), title: $n.find('h4').text, type: (function {               var type;                if ($n.hasClass('admin-notification')) {                    type = "admin-notification";                } else if ( $n.children('a').attr('href') .split('/').slice(-1)[0].indexOf('#') > -1 ) {                   type = "thread-reply";                } else {                    type = "thread-creation";                }            }) }   };    /**     * Threaded notification chronological sorter. * @method chronological * @param {Object} a notification object * @param {Object} b notification object */   cn.chronological = function(a, b) { var d = { a: new Date(a.when).getTime, b: new Date(b.when).getTime }       return d.b - d.a;    }; /**    * Consistent notification card renderer. * @method view * @param {Object} n notification data * @returns {Object} UI-js notification card */   cn.view = function(n) { return { type: 'li', attr: { class: n.read ? 'wds-notification-card' : 'wds-notification-card wds-is-unread', 'data-type': n.type, 'data-uri': n.uri },           children: { type: 'a', attr: { class: 'wds-notification-card__outer-body', href: n.uri },               children: [ {                       type: 'div', attr: { class: 'wds-avatar-stack__avatar', title: n.actor.name },                       children: { type: 'img', attr: { class: 'wds-avatar', src: n.actor.img }                       }                    },                    {                        type: 'div', attr: { class: 'wds-notification-card__body', },                       children: [ {                               type: 'p', attr: { class: 'wds-notification-card__text' },                               html: cn.i18n.msg(n.type, n.actor.name, n.title).parse },                           {                                type: 'ul', attr: { class: 'wds-notification-card__context-list' },                               children: cn.context(n) }                       ]                    }                ]            }        };    };    /**     * Threaded notification chronological sorter. * @method context * @param {Object} n notification data * @returns {Array} UI-js notification context list */   cn.context = function(n) { return [ {               type: 'li', attr: { class: 'wds-notification-card__context-item' },               text: $.timeago(n.when) },           {                type: 'li', attr: { class: 'wds-notification-card__context-separator' },               text: '\u00B7' },           {                type: 'li', attr: { class: 'wds-notification-card__context-item wds-notification-card__community' },               text: n.community.name }       ]    };    /**     * Threaded notification count. * @member {Number} count */   cn.count = -1; /**    * Threaded notification "mark all as read" handler * @method markread */   cn.markread = function { nirvana.sendRequest({           controller: 'WallNotificationsExternalController',            method: 'markAllAsRead',            format: 'json',            data: {                forceAll: 'FORCE'            },            callback: $.proxy(function(data) { if (!data.status) { return; }               cn.$bubble.removeClass('show'); cn.$counter.empty; cn.$list.find('.wds-notification-card') .removeClass('wds-is-unread'); })       });    };    /**     * Parameter utility * @method params * @returns {Object} query string parameters */   cn.params = function { var data = {}, qs = Wikia.Querystring; ['useskin', 'uselang'].forEach(function(u) {           if (qs.getVal(u)) {                data[u] = qs.getVal(u)            }        }); return data; };   /**     * I18n message handling * @member {Object} i18n */   cn.i18n = { /**        * Message & utility loader * @method handler * @param {Object} i18no i18n library */       handler: function(i18no) { i18no.loadMessages('ConsistentNotifications').done(cn.i18n.store); },       /**         * Message data handler * @method store * @param {Object} i18n message utilities */       store: function(i18n) { // Cache our msg data, utils $.extend(cn.i18n, i18n); // Message loading done cn.i18n.$loaded.resolve(cn.i18n); },       /**         * Load event * @member {Object} $loaded */       $loaded: $.Deferred };   /**     * UI library * @member {Object} ui    */ cn.ui = { /**        * WDS utility handler * @method handler * @param {Function} ui UI library */       handler: function(ui) { // Cache UI utility cn.ui.render = ui; // Asset loading done cn.ui.$loaded.resolve(cn.ui); },       /**         * Load event * @member {Object} $loaded */       $loaded: $.Deferred };       // Import dependencies $.each({       'i18n': 'u:dev:I18n-js/code.js',        'ui': 'u:dev:UI-js/code.js'    }, function(l, s) {        // Import scripts        if (!window.dev.hasOwnProperty(l)) {            importArticles({ type: 'script', article: s });        }        // Fetch dependencies        mw.hook('dev.' + l).add(cn[l].handler);    }) // Script bootloader $(importArticles({ type: 'style', article: 'u:dev:MediaWiki:ConsistentNotifications.css' })).load(function {       $.when(cn.i18n.$loaded, cn.ui.$loaded).then(cn.init);    }); });