MediaWiki:Gadget-morebits.js: Difference between revisions
Content deleted Content added
Amorymeltzer (talk | contribs) Repo at c0f5591: Better zero-pad (don't break on properly-padded dates) |
Amorymeltzer (talk | contribs) Repo at 4cba4ef: date: cleanup constructor (#1187); Fix bug in fnProcessChecks; Rename Morebits.wiki.isPageRedirect to Morebits.isPageRedirect; Add intestactions to loadQuery; Default date.format to ISOString; Properly detect and process existing cascading protection; Don't overwrite unprovided edit protection with cascading; Use common checks for m.w.p methods; Do triage properly, with extra query to determine if page is in queue |
||
Line 1,033:
RegExp.escape = function(text, space_fix) {
if (space_fix) {
console.
return Morebits.string.escapeRegExp(text);
}
console.
return mw.util.escapeRegExp(text);
};
Line 1,303:
};
/**
* Determines whether the current page is a redirect or soft redirect
* (fails to detect soft redirects on edit, history, etc. pages)
* Will attempt to detect Module:RfD, with the same failure points
* @returns {boolean}
*/
Morebits.isPageRedirect = function() {
return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect') || $('.box-RfD').length);
};
/**
Line 1,403 ⟶ 1,413:
Morebits.date = function() {
var args = Array.prototype.slice.call(arguments);
this._d = new (Function.prototype.bind.apply(Date, [Date].concat(args)));
if (!this.isValid()) {
if (args.length === 1 && typeof args[0] === 'string') {
// Check if it's a MediaWiki signature timestamp (which the native Date cannot parse directly)
var dateParts = Morebits.date.localeData.signatureTimestampFormat(args[0]);
if (dateParts) {
this._d = new Date(Date.UTC.apply(null, dateParts));
}
}
}
// Still no?
if (!this.isValid()) {
mw.log.warn('Invalid Morebits.date initialisation:', args);
}
};
Line 1,437 ⟶ 1,442:
pastWeek: '[Last] dddd [at] h:mm A',
other: 'YYYY-MM-DD'
},
signatureTimestampFormat: function (str) {
var rgx = /(\d{2}):(\d{2}), (\d{1,2}) (\w+) (\d{4}) \(UTC\)/;
var match = rgx.exec(str);
if (!match) {
return null;
}
var month = Morebits.date.localeData.months.indexOf(match[4]);
if (month === -1) {
return null;
}
// ..... year ... month .. date ... hour .... minute
return [match[5], month, match[3], match[1], match[2]];
}
};
Line 1,534 ⟶ 1,552:
*/
format: function(formatstr, zone) {
if (!this.isValid()) {
return 'Invalid date'; // Put the truth out, preferable to "NaNNaNNan NaN:NaN" or whatever
}
var udate = this;
// create a new date object that will contain the date to display as system time
Line 1,541 ⟶ 1,562:
// convert to utc, then add the utc offset given
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes');
}
// default to ISOString
if (!formatstr) {
return udate.toISOString();
}
Line 1,641 ⟶ 1,667:
Morebits.wiki = {};
/** @deprecated in favor of Morebits.isPageRedirect */
Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() {
console.warn('NOTE: Morebits.wiki.isPageRedirect has been deprecated, use Morebits.isPageRedirect instead.'); // eslint-disable-line no-console
return Morebits.isPageRedirect();
};
Line 2,095 ⟶ 2,117:
editSummary: null,
changeTags: null,
testActions: null, // array if any valid actions
callbackParameters: null,
statusElement: new Morebits.status(currentAction),
Line 2,130 ⟶ 2,153:
protectMove: null,
protectCreate: null,
protectCascade:
// - creation lookup
Line 2,178 ⟶ 2,201:
patrolProcessApi: null,
triageApi: null,
triageProcessListApi: null,
triageProcessApi: null,
deleteApi: null,
Line 2,210 ⟶ 2,234:
action: 'query',
prop: 'info|revisions',
intestactions: 'edit', // can be expanded
curtimestamp: '',
meta: 'tokens',
Line 2,760 ⟶ 2,785:
this.getCreationTimestamp = function() {
return ctx.timestamp;
};
/** @returns {boolean} whether or not you can edit the page */
this.canEdit = function() {
return !!ctx.testActions && ctx.testActions.indexOf('edit') !== -1;
};
Line 2,834 ⟶ 2,864:
ctx.onMoveFailure = onFailure || emptyFunction;
if (!fnPreflightChecks.call(this, 'move', ctx.
return; // abort
}
if (!ctx.moveDestination) {
ctx.statusElement.error('Internal error: destination page name was not set before move!');
Line 2,906 ⟶ 2,935:
* passing a pageid to the API is sufficient, so in those cases just
* using Morebits.wiki.api is probably preferable.
*
* Will first check if the page is queued via fnProcessTriageList
*
* No error handling since we don't actually care about the errors
Line 2,921 ⟶ 2,952:
if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) {
ctx.pageID = mw.config.get('wgArticleId');
} else {
var query = fnNeedTokenInfoQuery('triage');
ctx.triageApi = new Morebits.wiki.api('retrieving token...', query,
ctx.triageApi.setParent(this);
ctx.triageApi.post();
Line 2,942 ⟶ 2,973:
ctx.onDeleteFailure = onFailure || emptyFunction;
if (!fnPreflightChecks.call(this, 'delete', ctx.onDeleteFailure)) {
return; // abort
}
Line 2,974 ⟶ 2,997:
ctx.onUndeleteFailure = onFailure || emptyFunction;
if (!fnPreflightChecks.call(this, 'undelete', ctx.onUndeleteFailure)) {
return; // abort
}
Line 3,006 ⟶ 3,021:
ctx.onProtectFailure = onFailure || emptyFunction;
if (!fnPreflightChecks.call(this, 'protect', ctx.onProtectFailure)) {
return; // abort
}
if (!ctx.protectEdit && !ctx.protectMove && !ctx.protectCreate) {
ctx.statusElement.error('Internal error: you must set edit and/or move and/or create protection before calling protect()!');
ctx.onProtectFailure(this);
return;
Line 3,044 ⟶ 3,052:
ctx.onStabilizeFailure = onFailure || emptyFunction;
if (!fnPreflightChecks.call(this, 'FlaggedRevs', ctx.onStabilizeFailure)) {
return; // abort
}
if (!ctx.flaggedRevs) {
ctx.statusElement.error('Internal error: you must set flaggedRevs before calling stabilize()!');
ctx.onStabilizeFailure(this);
return;
Line 3,192 ⟶ 3,193:
ctx.lastEditTime = $(xml).find('rev').attr('timestamp');
ctx.revertCurID = $(xml).find('page').attr('lastrevid');
var testactions = $(xml).find('actions');
if (testactions.length) {
ctx.testActions = []; // was null
$.each(testactions[0].attributes, function(_idx, value) {
ctx.testActions.push(value.name);
});
}
if (ctx.editMode === 'revert') {
Line 3,433 ⟶ 3,442:
ctx.onLookupCreationSuccess(this);
};
// Common checks for action methods
// Used for move, undelete, delete, protect, stabilize
var fnPreflightChecks = function(action, onFailure) {
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop && action !== 'move') {
ctx.statusElement.error('Cannot ' + action + 'page : only admins can do that');
onFailure(this);
return false;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: ' + action + ' reason not set (use setEditSummary function)!');
onFailure(this);
return false;
}
return true; // all OK
};
// Common checks for fnProcess functions (fnProcessDelete, fnProcessMove, etc.)
// Used for move, undelete, delete, protect, stabilize
var fnProcessChecks = function(action, onFailure, xml) {
var missing = $(xml).find('page').attr('missing') === '';
// No undelete as an existing page could have deleted revisions
var actionMissing = missing && ['delete', 'stabilize', 'move'].indexOf(action) !== -1;
var protectMissing = action === 'protect' && missing && (ctx.protectEdit || ctx.protectMove);
var saltMissing = action === 'protect' && !missing && ctx.protectCreate;
if (actionMissing || protectMissing || saltMissing) {
ctx.statusElement.error('Cannot ' + action + ' the page because it ' + (missing ? 'no longer' : 'already') + ' exists');
onFailure(this);
return false;
}
// Delete, undelete, move
// extract protection info
var editprot;
if (action === 'undelete') {
editprot = $(xml).find('pr[type="create"]');
} else if (action === 'delete' || action === 'move') {
editprot = $(xml).find('pr[type="edit"]');
}
if (editprot && editprot.length && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to ' + action + ' the fully protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with ' + action + ', or Cancel to skip.')) {
ctx.statusElement.error('Aborted ' + action + ' on fully protected page.');
onFailure(this);
return false;
}
if (!$(xml).find('tokens').attr('csrftoken')) {
ctx.statusElement.error('Failed to retrieve token.');
onFailure(this);
return false;
}
return true; // all OK
};
Line 3,444 ⟶ 3,512:
var xml = ctx.moveApi.getXML();
if (
return; // abort
}
token = $(xml).find('tokens').attr('csrftoken');
pageTitle = $(xml).find('page').attr('title');
}
Line 3,541 ⟶ 3,588:
};
// Ensure that the page is curatable
var fnProcessTriageList = function() {
if (ctx.pageID) {
} else {
var xml = ctx.triageApi.getXML();
ctx.pageID = $(xml).find('page').attr('pageid');
if (!ctx.pageID) {
return;
}
if (!
return;
}
Line 3,562 ⟶ 3,607:
var query = {
action: '
};
ctx.triageProcessListApi.setParent(this);
ctx.triageProcessListApi.post();
};
var fnProcessTriage = function() {
var $xml = $(ctx.triageProcessListApi.getXML());
// Exit if not in the queue
if ($
return;
}
var page = $xml.find('pages _v');
// Nothing if page already triaged/patrolled
if (!page || !parseInt(page.attr('patrol_status'), 10)) {
var query = {
action: 'pagetriageaction',
pageid: ctx.pageID,
reviewed: 1,
// tags: ctx.changeTags, // pagetriage tag support: [[phab:T252980]]
// Could use an adder to modify/create note:
// summaryAd, but that seems overwrought
token: ctx.csrfToken
};
var triageStat = new Morebits.status('Marking page as curated');
ctx.triageProcessApi = new Morebits.wiki.api('curating page...', query, null, triageStat);
ctx.triageProcessApi.setParent(this);
ctx.triageProcessApi.post();
}
};
Line 3,603 ⟶ 3,650:
var xml = ctx.deleteApi.getXML();
if (
return; // abort
}
token = $(xml).find('tokens').attr('csrftoken');
pageTitle = $(xml).find('page').attr('title');
}
Line 3,681 ⟶ 3,709:
var xml = ctx.undeleteApi.getXML();
if (
return; // abort
}
token = $(xml).find('tokens').attr('csrftoken');
pageTitle = $(xml).find('page').attr('title');
}
Line 3,759 ⟶ 3,768:
var xml = ctx.protectApi.getXML();
if (!fnProcessChecks('protect', ctx.onProtectFailure, xml)) {
return; // abort
}
var token = $(xml).find('tokens').attr('csrftoken');
var pageTitle = $(xml).find('page').attr('title');
//
var prs = $(xml).find('pr');
var editprot = prs.filter('[type="edit"]:not([source])');
var moveprot = prs.filter('[type="move"]');
var createprot = prs.filter('[type="create"]');
// Fall back to current levels if not explicitly set
if (!ctx.protectEdit && editprot.length) {
ctx.protectEdit = { level: editprot.attr('level'), expiry: editprot.attr('expiry') };
}
if (!ctx.protectMove && moveprot.length) {
ctx.protectMove = { level: moveprot.attr('level'), expiry: moveprot.attr('expiry') };
}
if (!ctx.protectCreate && createprot.length) {
ctx.protectCreate = { level: createprot.attr('level'), expiry: createprot.attr('expiry') };
}
// Warn if cascading protection being applied with an invalid protection level,
// which for edit protection will cause cascading to be silently stripped
// Also default to pre-existing cascading protection if unchanged (as with others)
if (ctx.protectCascade || (ctx.protectCascade === null && !!prs.filter('[cascade]').length)) {
// On move protection, this is technically stricter than the MW API,
// but seems reasonable to avoid dumb values and misleading log entries (T265626)
if (((!ctx.protectEdit || ctx.protectEdit.level !== 'sysop') ||
(!ctx.protectMove || ctx.protectMove.level !== 'sysop')) &&
!confirm('You have cascading protection enabled on "' + ctx.pageName +
'" but have not selected uniform sysop-level protection.\n\n' +
'Click OK to adjust and proceed with sysop-level cascading protection, or Cancel to skip this action.')) {
ctx.statusElement.error('Cascading protection was aborted.');
ctx.onProtectFailure(this);
return;
}
ctx.protectEdit.level = 'sysop';
ctx.protectMove.level = 'sysop';
ctx.protectCascade = 'true';
}
//
var protections = [], expirys = [];
if (ctx.protectEdit) {
protections.push('edit=' + ctx.protectEdit.level);
expirys.push(ctx.protectEdit.expiry);
}
Line 3,802 ⟶ 3,823:
protections.push('move=' + ctx.protectMove.level);
expirys.push(ctx.protectMove.expiry);
}
Line 3,810 ⟶ 3,828:
protections.push('create=' + ctx.protectCreate.level);
expirys.push(ctx.protectCreate.expiry);
}
Line 3,847 ⟶ 3,862:
var xml = ctx.stabilizeApi.getXML();
// 'stabilize' as a verb not necessarily well understood
if (!fnProcessChecks('stabilize', ctx.onStabilizeFailure, xml)) {
return; // abort
}
token = $(xml).find('tokens').attr('csrftoken');
pageTitle = $(xml).find('page').attr('title');
}
|