MediaWiki:Gadget-morebits.js: Difference between revisions

Content deleted Content added
Repo at e99ee60e: lookupCreation: add failure callback; Add number input type to quickform, use in tag
Repo at 3825f474: Update statusElement on successful lookupCreation; status: add extra class on actionCompleted message (#1343); fnApplyWatchlistExpiry to decide whether/when to apply a watchlistexpiry; Expand setWatchlist handling of expiry, default to infinity for watch/preferences; Allow adding/subtracting weeks as 7 days; taskManager: enable failure handling and setting context
Line 1,764:
* @memberof Morebits.date
* @type {object.<string, string>}
* @property {string} seconds
* @property {string} minutes
* @property {string} hours
* @property {string} days
* @property {string} weeks
* @property {string} months
* @property {string} years
*/
Morebits.date.unitMap = {
Line 1,770 ⟶ 1,777:
hours: 'Hours',
days: 'Date',
weeks: 'Week', // Not a function but handled in `add` through cunning use of multiplication
months: 'Month',
years: 'FullYear'
Line 1,829 ⟶ 1,837:
 
/**
* Add a given number of minutes, hours, days, weeks, months, or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
*
Line 1,846 ⟶ 1,854:
var unitNorm = unitMap[unit] || unitMap[unit + 's']; // so that both singular and plural forms work
if (unitNorm) {
// No built-in week functions, so rather than build out ISO's getWeek/setWeek, just multiply
// Probably can't be used for Julian->Gregorian changeovers, etc.
if (unitNorm === 'Week') {
unitNorm = 'Date', num *= 7;
}
this['set' + unitNorm](this['get' + unitNorm]() + num);
return this;
Line 1,853 ⟶ 1,866:
 
/**
* Subtracts a given number of minutes, hours, days, weeks, months, or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
*
Line 2,686 ⟶ 2,699:
}
 
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 2,944 ⟶ 2,957:
 
/**
* Set whether and how to watch the page, including setting an expiry.
* @param {boolean|string} [watchlistOption=false] -
*
* @param {boolean|string|Morebits.date|Date} [watchlistOption=false] -
* Basically a mix of MW API and Twinkley options available pre-expiry:
* - `true`|`'yes'`|`'watch'`: page will be added to the user's
* watchlist when the action is called. Defaults to an indefinite
* watch unless `watchlistExpiry` is provided.
* - `false`|`'no'`|`'nochange'`: watchlist status of the page will not be changed.
* - `false`|`'defaultno'`|`'preferencesnochange'`: watchlist status of the page (including expiry) will not be changed.
* - `'default'`|`'preferences'`: watchlist status of the page will be
* be set based on the user's preference settings when the action is
* set based on the user's preference settings when the action is
* called. Ignores ability of default + expiry.
* called. Defaults to an indefinite watch unless `watchlistExpiry` is
* - `'unwatch'`: explicitly unwatch the page
* provided.
* - {string|number}: watch page until the specified time (relative or absolute datestring)
* - `'unwatch'`: explicitly unwatch the page.
* - Any other `string` or `number`, or a `Morebits.date` or `Date`
* object: watch page until the specified time, deferring to
* `watchlistExpiry` if provided.
* @param {string|number|Morebits.date|Date} [watchlistExpiry=infinity] -
* A date-like string or number, or a date object. If a string or number,
* can be relative (2 weeks) or other similarly date-like (i.e. NOT "potato"):
* ISO 8601: 2038-01-09T03:14:07Z
* MediaWiki: 20380109031407
* UNIX: 2147483647
* SQL: 2038-01-09 03:14:07
* Can also be `infinity` or infinity-like (`infinite`, `indefinite`, and `never`).
* See {@link https://phabricator.wikimedia.org/source/mediawiki-libs-Timestamp/browse/master/src/ConvertibleTimestamp.php;4e53b859a9580c55958078f46dd4f3a44d0fcaa0$57-109?as=source&blame=off}
*/
this.setWatchlist = function(watchlistOption, watchlistExpiry) {
if (!watchlistOption || watchlistOption ===instanceof 'no'Morebits.date || watchlistOption ===instanceof 'nochange'Date) {
ctx.watchlistOption = 'nochange'watchlistOption.toISOString();
}
} else if (watchlistOption === 'default' || watchlistOption === 'preferences') {
if (typeof watchlistExpiry === 'undefined') {
ctx.watchlistOption = 'preferences';
watchlistExpiry = 'infinity';
} else if (watchlistOption === 'unwatch') {
} else if (watchlistExpiry instanceof Morebits.date || watchlistExpiry instanceof Date) {
ctx.watchlistOption = 'unwatch';
watchlistExpiry = watchlistExpiry.toISOString();
} else {
}
ctx.watchlistOption = 'watch';
 
if (typeof watchlistOption === 'number' || (typeof watchlistOption === 'string' && watchlistOption !== 'yes')) {
switch (watchlistOption) {
case 'nochange':
case 'no':
case false:
case undefined:
ctx.watchlistOption = 'nochange';
// The MW API allows for changing expiry with nochange (as "nochange" refers to the binary status),
// but by keeping this null it will default to any existing expiry, ensure there is actually "no change."
ctx.watchlistExpiry = null;
break;
case 'unwatch':
// expiry unimportant
ctx.watchlistOption = 'unwatch';
break;
case 'preferences':
case 'default':
ctx.watchlistOption = 'preferences';
// The API allows an expiry here, but there is as of yet (T265716)
// no expiry preference option, so it's a bit devoid of context.
ctx.watchlistExpiry = watchlistExpiry;
break;
case 'watch':
case 'yes':
case true:
ctx.watchlistOption = 'watch';
ctx.watchlistExpiry = watchlistExpiry;
break;
default: // Not really a "default" per se but catches "any other string"
ctx.watchlistOption = 'watch';
ctx.watchlistExpiry = watchlistOption;
} break;
}
};
 
/**
* Set ana watchlist expiry. setWatchlist can mostly handle this by itself if passed a
* stringitself, so this is here largely for completeness and compatibility.
* with the full suite of options.
*
* @param {string|number|Morebits.date|Date} [watchlistExpiry=infinity] - A date-like string or array of strings
* CanA bedate-like relativestring (2or weeks)number, or other similarlya date-like (i.eobject. NOT "potato"):If a string or number,
* can be relative (2 weeks) or other similarly date-like (i.e. NOT "potato"):
* ISO 8601: 2038-01-09T03:14:07Z
* MediaWiki: 20380109031407
Line 2,983 ⟶ 3,043:
*/
this.setWatchlistExpiry = function(watchlistExpiry) {
if (typeof watchlistExpiry === 'undefined') {
watchlistExpiry = 'infinity';
} else if (watchlistExpiry instanceof Morebits.date || watchlistExpiry instanceof Date) {
watchlistExpiry = watchlistExpiry.toISOString();
}
ctx.watchlistExpiry = watchlistExpiry;
};
Line 3,544 ⟶ 3,609:
 
// If a watchlist expiry is set, we must always load the page
// to avoid overwriting indefinite protection. Of course, not
// needed if setting indefinite watching!
if (ctx.watchlistExpiry) {
if (ctx.watchlistExpiry && !Morebits.string.isInfinity(ctx.watchlistExpiry)) {
return false;
}
Line 3,746 ⟶ 3,812:
}
return true; // all OK
};
 
/**
* Determine whether we should provide a watchlist expiry. Will not
* do so if the page is currently permanently watched, or the current
* expiry is *after* the new, provided expiry. Only handles strings
* recognized by {@link Morebits.date} or relative timeframes with
* unit it can process. Relies on the fact that fnCanUseMwUserToken
* requires page loading if a watchlistexpiry is provided, so we are
* ensured of knowing the watch status by the use of this.
*
* @returns {boolean}
*/
var fnApplyWatchlistExpiry = function() {
if (ctx.watchlistExpiry) {
if (!ctx.watched || Morebits.string.isInfinity(ctx.watchlistExpiry)) {
return true;
} else if (typeof ctx.watched === 'string') {
var newExpiry;
// Attempt to determine if the new expiry is a
// relative (e.g. `1 month`) or absolute datetime
var rel = ctx.watchlistExpiry.split(' ');
try {
newExpiry = new Morebits.date().add(rel[0], rel[1]);
} catch (e) {
newExpiry = new Morebits.date(ctx.watchlistExpiry);
}
 
// If the date is valid, only use it if it extends the current expiry
if (newExpiry.isValid()) {
if (newExpiry.isAfter(new Morebits.date(ctx.watched))) {
return true;
}
} else {
// If it's still not valid, hope it's a valid MW expiry format that
// Morebits.date doesn't recognize, so just default to using it.
// This will also include minor typos.
return true;
}
}
}
return false;
};
 
Line 3,884 ⟶ 3,992:
return;
}
 
ctx.statusElement.info('retrieved page creation information');
ctx.onLookupCreationSuccess(this);
 
Line 3,926 ⟶ 4,036:
}
 
ctx.statusElement.info('retrieved page creation information');
ctx.onLookupCreationSuccess(this);
 
Line 4,038 ⟶ 4,149:
}
 
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 4,184 ⟶ 4,295:
}
 
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 4,249 ⟶ 4,360:
}
 
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 4,384 ⟶ 4,495:
}
 
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 4,430 ⟶ 4,541:
 
/* Doesn't support watchlist expiry [[phab:T263336]]
if (fnApplyWatchlistExpiry()) {
if (ctx.watchlistExpiry && ctx.watched !== true) {
query.watchlistexpiry = ctx.watchlistExpiry;
}
Line 5,137 ⟶ 5,248:
var node = document.createElement('div');
node.appendChild(document.createElement('b')).appendChild(document.createTextNode(text));
node.className = 'morebits_status_info morebits_action_complete';
if (Morebits.status.root) {
Morebits.status.root.appendChild(node);
Line 5,478 ⟶ 5,589:
/**
* Given a set of asynchronous functions to run along with their dependencies,
* figurerun outthem in an efficient sequence of running them so that multiple functions
* that don't depend on each other are triggered simultaneously. Where
* dependencies exist, it ensures that the dependency functions finish running
Line 5,487 ⟶ 5,598:
* @class
*/
Morebits.taskManager = function(context) {
this.taskDependencyMap = new Map();
this.failureCallbackMap = new Map();
this.deferreds = new Map();
this.allDeferreds = []; // Hack: IE doesn't support Map.prototype.values
this.context = context || window;
 
/**
Line 5,500 ⟶ 5,613:
* @param {Function} func - A task.
* @param {Function[]} deps - Its dependencies.
* @param {Function} [onFailure] - a failure callback that's run if the task or any one
* of its dependencies fail.
*/
this.add = function(func, deps, onFailure) {
this.taskDependencyMap.set(func, deps);
this.failureCallbackMap.set(func, onFailure || function() {});
var deferred = $.Deferred();
this.deferreds.set(func, deferred);
Line 5,511 ⟶ 5,627:
* Run all the tasks. Multiple tasks may be run at once.
*
* @returns {promisejQuery.Promise} - AResolved jQueryif promiseall objecttasks that is resolved orsucceed, rejected with the api objectotherwise.
*/
this.execute = function() {
Line 5,519 ⟶ 5,635:
return self.deferreds.get(dep);
});
$.when.apply(nullself.context, dependencyPromisesArray).then(function() {
var result = task.apply(nullself.context, arguments).then(function() {;
if (result === undefined) { // maybe the function threw, or it didn't return anything
self.deferreds.get(task).resolve.apply(null, arguments);
mw.log.error('Morebits.taskManager: task returned undefined');
self.deferreds.get(task).reject.apply(self.context, arguments);
self.failureCallbackMap.get(task).apply(self.context, []);
}
result.then(function() {
self.deferreds.get(task).resolve.apply(self.context, arguments);
}, function() { // task failed
self.deferreds.get(task).reject.apply(self.context, arguments);
self.failureCallbackMap.get(task).apply(self.context, arguments);
});
}, function() { // one or more of the dependencies failed
self.failureCallbackMap.get(task).apply(self.context, arguments);
});
});