User:Pathoschild/Scripts/globalview.js
< User:Pathoschild | Scripts
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/********
** GlobalView
** This experimental script adds a [[Special:GlobalView]] page on the current
** wiki to view your new messages and unread notifications on all wikis.
**
** This is a quick and dirty script — it's not ready for reuse! Please contact
** me if you're interested in using it.
********/
/* global $, mw */
var pathoschild = pathoschild || {};
/**
* Creates an HTML5 progress bar with an interface to update the progress.
* @param {int} value The current progress value.
* @param {int} total The maximum progress value.
*/
pathoschild.ProgressBar = function(value, max) {
/*********
** Fields
*********/
var self = {
/**
* The HTML5 progress bar.
*/
progressBar: $('<progress></progress>', { value: value, max: max }),
/**
* The current progress value.
*/
value: value,
/**
* The maximum progress value.
*/
max: max,
/**
* A promise which is completed when the progress bar hits 100%.
*/
promise: $.Deferred(),
/**
* Tracks the result of watched queries for troubleshooting.
*/
watched: {
'all': [],
'done': [],
'failed': []
}
};
/*********
** Public methods
*********/
/**
* Append the HTML5 progress bar to an element.
* @param {jQuery|string} A jQuery element or selector to which to append the progress bar.
* @returns The progress bar instance for chaining.
*/
self.appendTo = function(parent) {
self.progressBar.appendTo(parent);
return self;
};
/**
* Increment the progress value.
* @returns The progress bar instance for chaining.
*/
self.increment = function() {
self.value += 1;
self.progressBar.attr('value', self.value);
if(self.value == self.max)
self.promise.resolve();
return self;
};
/**
* Watch a promise and increment when it completes.
* @param {string} key A unique key which identifies the watched promise for troubleshooting.
* @param {jQuery.Deferred} promise A promise to watch.
* @returns The progress bar instance for chaining.
*/
self.watch = function(key, promise) {
self.watched.all.push(key);
promise
.then(function() { self.watched.done.push(key); self.increment(); })
.fail(function() { self.watched.failed.push(key); self.increment(); });
};
return self;
};
/**
* Implements the [[Special:GlobalView]] page.
*/
pathoschild.GlobalView = function() {
var self = {};
/*********
** Public methods
*********/
/**
* Render the [[Special:GlobalView]] UI.
*/
self.initialiseUI = function() {
// get context
if(mw.config.get('wgCanonicalNamespace') != 'Special' || mw.config.get('wgTitle') != 'GlobalView')
return; // only initialise on [[Special:GlobalView]]
var username = mw.config.get('wgUserName');
// bootstrap UI
mw.loader.using(['ext.echo.styles.badge', 'ext.echo.styles.notifications']); // load badge styles
$('#firstHeading, title:first').text('Global view');
var page = $('#mw-content-text').empty().append($('<p></p>', { text: 'Hi ' + username + '!' }));
var placeholder = $('<div></div>').appendTo(page);
// fetch wikis
placeholder.text('↻ fetching unified wikis...');
self.getUnifiedWikis().then(function(wikis) {
// add UI
page.append($('<h3></h3>', { text: 'Messages & notifications' }));
placeholder.text('↻ scanning wikis...');
var progress = new pathoschild.ProgressBar(0, wikis.length).appendTo(placeholder);
// fetch messages & notifications
var hasNotifications = false;
$.each(wikis, function(w, wiki) {
progress.watch(wiki.wiki,
self.getUserData(wiki.url).then(function(data) {
// collect notifications
var list = [];
if('messages' in data.userinfo)
list.push($('<div class="new-message"><a href="' + wiki.url + '/wiki/User talk:' + username + '">You have new messages.</a></div>'));
if(data.notifications.length) {
$.each(data.notifications, function(n, notification) {
var html = $(notification['*']);
// fix local links
html.find('a').attr('href', function(i, href) {
return href.match(/^\/w/)
? wiki.url + href
: href;
});
list.push(html);
});
}
// build UI
if(list.length) {
hasNotifications = true;
page.append([
$('<dt></dt>').append(
$('<a></a>', { href: wiki.url, text: wiki.wiki })
),
$('<dd></dd>').append(
$('<ul></ul>').append(list)
)
]);
}
})
);
});
// update UI when done
progress.promise.then(function() {
if(!hasNotifications)
page.append('You have no notifications or messages. :(');
placeholder.remove();
});
});
};
/**
* Get a list of wikis attached to the current user's global account.
* The schema is { wiki: 'aawiki', url: 'https://aa.wikipedia.org' }.
* @returns A promise which provides an array of wikis on completion.
*/
self.getUnifiedWikis = function() {
return self.getApi().then(function(api) {
return api.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'merged' }).then(function(data) {
return $.map(data.query.globaluserinfo.merged, function(wiki) {
// exclude non-wikis
if(['loginwiki', 'ukwikimedia'].indexOf(wiki.wiki) === -1)
return wiki;
});
});
});
};
/**
* Get user info and notifications from a remote wiki.
* @param {string} url The wiki's root URL (like 'https://aa.wikipedia.org').
* @returns A promise which provides user info.
*/
self.getUserData = function(url) {
return self.getApi(url).then(function(api) {
return api
.get({
action: 'query',
meta: ['userinfo', 'notifications'],
notprop: 'list',
notformat: 'html',
uiprop: 'hasmsg'
})
.then(function(data) {
return {
userinfo: data.query.userinfo,
notifications: $.map(data.query.notifications.list, function(notification) {
if(!('read' in notification))
return notification;
})
};
});
});
};
/**
* Get an API client for a given wiki.
* @param {string} url The wiki's root URL (like 'https://aa.wikipedia.org'), or nothing for the current wiki.
* @returns A promise which provides an API wrapper.
*/
self.getApi = function(url) {
return mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
return !url || url.indexOf(mw.config.get('wgServer')) !== -1
? new mw.Api()
: new mw.ForeignApi(url + '/w/api.php');
});
};
return self;
};
// initialise
new pathoschild.GlobalView().initialiseUI();