//
/*jshint browser:true jquery:true curly:false laxbreak:true smarttabs:true bitwise:false */
/*global mediaWiki UserTagsJS */
//
// MODULE: New Users
// Tags users who have not met a minimum threshold
//
// Editcount is broken (Sep-2012). It returns nonsense values.
// Using the Special:Editcount page is more flexible (namespace filtering) and accurate.
// Unfortunately, the AJAX is manual since Sledge can't handle non-queries.
// NOTE: Parse produces *localised* HTML so you get stuff like "<p>1,000\n</p>"
//
// By user request: We accept a custom computation function for this so that they can do
// stuff like "edits < 30 || days < 14 || (days < 7 && edits < 10)". Also transitively
// enables AND instead of just OR.
//
UserTagsJS.extensions.newuser = (function($, mw, Date) {
"use strict";
return {
start: function(config, username, logger/*, lang*/) {
// Anonymous users don't work with Special:Editcount, it returns blank
if (mw.util.isIPv4Address(username) || mw.util.isIPv6Address(username)) return;
// The 10/4 default matches Wikia's config for autoconfirmed
config = $.extend({ edits: 10, days: 4 }, config);
// Convert to a computation function if there isn't one
if (typeof(config.computation) !== 'function') {
var minEdits = config.edits | 0;
var minDays = config.days | 0;
// Sanity. Give up if both days and edits are zero.
if (minEdits < 1 && minDays < 1) return;
config.computation = function(days, edits) {
return days < minDays || edits < minEdits;
};
}
this._logger = logger;
// Our promises for the async data we need to do this calculation
var daysPromise = this._daysPromise = $.Deferred(),
editsPromise = this._editsPromise = $.Deferred();
// Sanity, convert namespace string to number.
var namespace = config.namespace;
if (namespace && typeof(namespace) !== 'number') {
namespace = mw.config.get('wgNamespaceIds')[namespace];
if (typeof(namespace) !== 'number') {
logger.err('Invalid namespace:', config.namespace);
namespace = null;
}
}
// Performance optimising the common case.
// In Oasis, with no namespace configuration, we can just scrape the DOM contributions
// counter instead of AJAXing which is WAY faster (especially since we're manually
// AJAXing instead of using Sledge)
if ((typeof(namespace) === 'number' && namespace === namespace) // n === n is false for NaN
|| ({oasis:1, wikia:1})[mw.config.get('skin')] !== 1
) {
this._doCustomAjax(username, namespace);
} else {
$($.proxy(this._onDomReady, this, username));
}
// Start the core AJAX if needed and return the promise to delay further
// processing until we're ready.
return {
ajax: {
type: 'contributions',
limit: 1,
dir: 'newer',
prop: ['timestamp']
},
promise: $.when(daysPromise, editsPromise).then(function(a, b) {
try {
return config.computation(a, b) && ['newuser'];
} catch(e) {
logger.err('Custom Computation function exploded!', e, e.stack);
return $.Deferred().reject();
}
})
};
},
_onDomReady: function(username, $) {
var $node = $('#UserProfileMasthead .masthead-info .tally > em');
if ($node.length) {
var num = $node.text().replace(/[^\d]/g, '');
if (num) {
return this._editsPromise.resolve(+num);
}
// WTF?
this._logger.err('Oasis masthead gave us junk instead of a number:', $node.text(), '(Falling back to AJAX)');
} else {
// Er... crap
this._logger.err('Cannot find Oasis masthead to scrape counter! Falling back to AJAX');
}
return this._doCustomAjax(username, null);
},
_doCustomAjax: function(username, namespace) {
// Construct the wikitext fragment to invoke the Special:Editcount pseudo-template
var text = '{{Special:Editcount/' + username;
if (typeof(namespace) === 'number') {
text += '/' + namespace;
}
text += '}}';
// Issue our AJAX to run the parser.
// I'm not using .then() as we need to wait for the generate() callback before
// we can actually issue a response.
var editsPromise = this._editsPromise, logger = this._logger;
this._selfajax = $.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'parse',
format: 'json',
uselang: 'en', // Force English so we don't have to deal with non-English digit chars
text: text,
prop: 'text',
disablepp: 1 // Disable the preprocessor report (HTML comment at end)
},
dataType: 'json'
})
.done(function(json) {
if (json && json.parse && json.parse.text) {
var num = $(document.createElement('div')).html(json.parse.text['*']).text().replace(/[^\d]/g, '');
if (num) {
editsPromise.resolve(+num);
return;
}
}
logger.err('Got a bad response from Special:Editcount:', json);
editsPromise.reject();
})
.fail(function(xhr, status, exception) {
logger.err('AJAX fail:', xhr.status, status, exception);
editsPromise.reject();
})
;
},
generate: function(json) {
json = json.query.usercontribs[0];
if (!json) return this.generateFailed();
// Calculate days since their oldest (i.e. first) edit was made
this._daysPromise.resolve((Date.now() - Date.parseISO8601(json.timestamp)) / 864e5);
},
generateFailed: function() {
if (this._selfajax) this._selfajax.abort();
this._daysPromise.reject();
}
};
})(jQuery, mediaWiki, Date);
//