0
edits
m (1 revision imported) |
ImportMaster (talk | contribs) (robot: Import pages to supprt Twinkle) |
||
Line 1: | Line 1: | ||
// <nowiki> | // <nowiki> | ||
/** | /** | ||
* A library full of lots of goodness for user scripts on MediaWiki wikis, including | * A library full of lots of goodness for user scripts on MediaWiki wikis, including Bharatpedia. | ||
* | * | ||
* The highlights include: | * The highlights include: | ||
Line 28: | Line 28: | ||
* | * | ||
* This library is maintained by the maintainers of Twinkle. | * This library is maintained by the maintainers of Twinkle. | ||
* For queries, suggestions, help, etc., head to [ | * For queries, suggestions, help, etc., head to [Bharatpedia talk:Twinkle on English Bharatpedia](http://en.wikipedia.org/wiki/WT:TW). | ||
* The latest development source is available at {@link https://github.com/wikimedia-gadgets/twinkle/blob/master/morebits.js|GitHub}. | * The latest development source is available at {@link https://github.com/wikimedia-gadgets/twinkle/blob/master/morebits.js|GitHub}. | ||
* | * | ||
Line 40: | Line 40: | ||
var Morebits = {}; | var Morebits = {}; | ||
window.Morebits = Morebits; // allow global access | window.Morebits = Morebits; // allow global access | ||
/** | |||
* i18n support for strings in Morebits | |||
*/ | |||
Morebits.i18n = { | |||
parser: null, | |||
/** | |||
* Set an i18n library to use with Morebits. | |||
* Examples: | |||
* Use jquery-i18n: | |||
* Morebits.i18n.setParser({ get: $.i18n }); | |||
* Use banana-i18n or orange-i18n: | |||
* var banana = new Banana('en'); | |||
* Morebits.i18n.setParser({ get: banana.i18n }); | |||
* @param {Object} parser | |||
*/ | |||
setParser: function(parser) { | |||
if (!parser || typeof parser.get !== 'function') { | |||
throw new Error('Morebits.i18n: parser must implement get()'); | |||
} | |||
Morebits.i18n.parser = parser; | |||
}, | |||
/** | |||
* @private | |||
* @returns {string} | |||
*/ | |||
getMessage: function () { | |||
var args = Array.prototype.slice.call(arguments); // array of size `n` | |||
// 1st arg: message name | |||
// 2nd to (n-1)th arg: message parameters | |||
// nth arg: legacy English fallback | |||
var msgName = args[0]; | |||
var fallback = args[args.length - 1]; | |||
if (!Morebits.i18n.parser) { | |||
return fallback; | |||
} | |||
// i18n libraries are generally invoked with variable number of arguments | |||
// as msg(msgName, ...parameters) | |||
var i18nMessage = Morebits.i18n.parser.get.apply(null, args.slice(0, -1)); | |||
// if no i18n message exists, i18n libraries generally give back the message name | |||
if (i18nMessage === msgName) { | |||
return fallback; | |||
} | |||
return i18nMessage; | |||
} | |||
}; | |||
// shortcut | |||
var msg = Morebits.i18n.getMessage; | |||
/** | |||
* Wiki-specific configurations for Morebits | |||
*/ | |||
Morebits.l10n = { | |||
/** | |||
* Local aliases for "redirect" magic word. | |||
* Check using api.php?action=query&format=json&meta=siteinfo&formatversion=2&siprop=magicwords | |||
*/ | |||
redirectTagAliases: ['#REDIRECT'], | |||
/** | |||
* Takes a string as argument and checks if it is a timestamp or not | |||
* If not, it returns null. If yes, it returns an array of integers | |||
* in the format [year, month, date, hour, minute, second] | |||
* which can be passed to Date.UTC() | |||
* @param {string} str | |||
* @returns {number[] | null} | |||
*/ | |||
signatureTimestampFormat: function (str) { | |||
// HH:mm, DD Month YYYY (UTC) | |||
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 63: | Line 147: | ||
* Converts an IPv6 address to the canonical form stored and used by MediaWiki. | * Converts an IPv6 address to the canonical form stored and used by MediaWiki. | ||
* JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} | * JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} | ||
* function from the IPUtils library. | * function from the IPUtils library. Addresses are verbose, uppercase, | ||
* normalized, and expanded to 8 words. | * normalized, and expanded to 8 words. | ||
* | * | ||
Line 77: | Line 161: | ||
* Determines whether the current page is a redirect or soft redirect. Fails | * Determines whether the current page is a redirect or soft redirect. Fails | ||
* to detect soft redirects on edit, history, etc. pages. Will attempt to | * to detect soft redirects on edit, history, etc. pages. Will attempt to | ||
* detect | * detect Module:RfD, with the same failure points. | ||
* | * | ||
* @returns {boolean} | * @returns {boolean} | ||
*/ | */ | ||
Morebits.isPageRedirect = function() { | Morebits.isPageRedirect = function() { | ||
return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect') || $('.box- | return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect') || $('.box-RfD').length); | ||
}; | }; | ||
Line 115: | Line 199: | ||
/** | /** | ||
* Create a string for use in regex matching all namespace aliases, regardless | * Converts string or array of DOM nodes into an HTML fragment. | ||
* of the capitalization and underscores/spaces. Doesn't include the optional | * Wikilink syntax (`[[...]]`) is transformed into HTML anchor. | ||
* leading `:`, but if there's more than one item, wraps the list in a | * Used in Morebits.quickForm and Morebits.status | ||
* non-capturing group. This means you can do `Morebits.namespaceRegex([4]) + | * @internal | ||
* @param {string|Node|(string|Node)[]} input | |||
* @returns {DocumentFragment} | |||
*/ | |||
Morebits.createHtml = function(input) { | |||
var fragment = document.createDocumentFragment(); | |||
if (!input) { | |||
return fragment; | |||
} | |||
if (!Array.isArray(input)) { | |||
input = [ input ]; | |||
} | |||
for (var i = 0; i < input.length; ++i) { | |||
if (input[i] instanceof Node) { | |||
fragment.appendChild(input[i]); | |||
} else { | |||
$.parseHTML(Morebits.createHtml.renderWikilinks(input[i])).forEach(function(node) { | |||
fragment.appendChild(node); | |||
}); | |||
} | |||
} | |||
return fragment; | |||
}; | |||
/** | |||
* Converts wikilinks to HTML anchor tags. | |||
* @param text | |||
* @returns {*} | |||
*/ | |||
Morebits.createHtml.renderWikilinks = function (text) { | |||
var ub = new Morebits.unbinder(text); | |||
// Don't convert wikilinks within code tags as they're used for displaying wiki-code | |||
ub.unbind('<code>', '</code>'); | |||
ub.content = ub.content.replace( | |||
/\[\[:?(?:([^|\]]+?)\|)?([^\]|]+?)\]\]/g, | |||
function(_, target, text) { | |||
if (!target) { | |||
target = text; | |||
} | |||
return '<a target="_blank" href="' + mw.util.getUrl(target) + | |||
'" title="' + target.replace(/"/g, '"') + '">' + text + '</a>'; | |||
}); | |||
return ub.rebind(); | |||
}; | |||
/** | |||
* Create a string for use in regex matching all namespace aliases, regardless | |||
* of the capitalization and underscores/spaces. Doesn't include the optional | |||
* leading `:`, but if there's more than one item, wraps the list in a | |||
* non-capturing group. This means you can do `Morebits.namespaceRegex([4]) + | |||
* ':' + Morebits.pageNameRegex('Twinkle')` to match a full page. Uses | * ':' + Morebits.pageNameRegex('Twinkle')` to match a full page. Uses | ||
* {@link Morebits.pageNameRegex}. | * {@link Morebits.pageNameRegex}. | ||
Line 137: | Line 270: | ||
if (namespaces.indexOf(number) !== -1) { | if (namespaces.indexOf(number) !== -1) { | ||
// Namespaces are completely agnostic as to case, | // Namespaces are completely agnostic as to case, | ||
// and a regex string is more useful/ | // and a regex string is more useful/compatible than a RegExp object, | ||
// so we accept any casing for any letter. | // so we accept any casing for any letter. | ||
aliases.push(name.split('').map(function(char) { | aliases.push(name.split('').map(function(char) { | ||
Line 201: | Line 334: | ||
* | * | ||
* Index to Morebits.quickForm.element types: | * Index to Morebits.quickForm.element types: | ||
* - Global attributes: id, className, style, tooltip, extra, adminonly | * - Global attributes: id, className, style, tooltip, extra, $data, adminonly | ||
* - `select`: A combo box (aka drop-down). | * - `select`: A combo box (aka drop-down). | ||
* - Attributes: name, label, multiple, size, list, event, disabled | * - Attributes: name, label, multiple, size, list, event, disabled | ||
Line 236: | Line 369: | ||
* - `fragment`: A DocumentFragment object. | * - `fragment`: A DocumentFragment object. | ||
* - No attributes, and no global attributes except adminonly. | * - No attributes, and no global attributes except adminonly. | ||
* There is some difference on how types handle the `label` attribute: | |||
* - `div`, `select`, `field`, `checkbox`/`radio`, `input`, `textarea`, `header`, and `dyninput` can accept an array of items, | |||
* and the label item(s) can be `Element`s. | |||
* - `option`, `optgroup`, `_dyninput_element`, `submit`, and `button` accept only a single string. | |||
* | * | ||
* @memberof Morebits.quickForm | * @memberof Morebits.quickForm | ||
Line 253: | Line 390: | ||
this.data = data; | this.data = data; | ||
this.childs = []; | this.childs = []; | ||
}; | }; | ||
Line 297: | Line 433: | ||
return currentNode[0]; | return currentNode[0]; | ||
}; | }; | ||
/** @memberof Morebits.quickForm.element */ | /** @memberof Morebits.quickForm.element */ | ||
Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) { | Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) { | ||
var node; | var node; | ||
var | var childContainer = null; | ||
var label; | var label; | ||
var id = (in_id ? in_id + '_' : '') + 'node_' + | var id = (in_id ? in_id + '_' : '') + 'node_' + Morebits.quickForm.element.id++; | ||
if (data.adminonly && !Morebits.userIsSysop) { | if (data.adminonly && !Morebits.userIsSysop) { | ||
// hell hack alpha | // hell hack alpha | ||
Line 330: | Line 467: | ||
label = node.appendChild(document.createElement('label')); | label = node.appendChild(document.createElement('label')); | ||
label.setAttribute('for', id); | label.setAttribute('for', id); | ||
label.appendChild( | label.appendChild(Morebits.createHtml(data.label)); | ||
label.style.marginRight = '3px'; | |||
} | } | ||
var select = node.appendChild(document.createElement('select')); | var select = node.appendChild(document.createElement('select')); | ||
Line 362: | Line 500: | ||
} | } | ||
} | } | ||
childContainer = select; | |||
break; | break; | ||
case 'option': | case 'option': | ||
Line 395: | Line 533: | ||
node = document.createElement('fieldset'); | node = document.createElement('fieldset'); | ||
label = node.appendChild(document.createElement('legend')); | label = node.appendChild(document.createElement('legend')); | ||
label.appendChild( | label.appendChild(Morebits.createHtml(data.label)); | ||
if (data.name) { | if (data.name) { | ||
node.setAttribute('name', data.name); | node.setAttribute('name', data.name); | ||
Line 442: | Line 580: | ||
} | } | ||
label = cur_div.appendChild(document.createElement('label')); | label = cur_div.appendChild(document.createElement('label')); | ||
label.appendChild( | |||
label.appendChild(Morebits.createHtml(current.label)); | |||
label.setAttribute('for', cur_id); | label.setAttribute('for', cur_id); | ||
if (current.tooltip) { | if (current.tooltip) { | ||
Line 528: | Line 667: | ||
if (data.label) { | if (data.label) { | ||
label = node.appendChild(document.createElement('label')); | label = node.appendChild(document.createElement('label')); | ||
label.appendChild( | label.appendChild(Morebits.createHtml(data.label)); | ||
label.setAttribute('for', data.id || id); | label.setAttribute('for', data.id || id); | ||
label.style.marginRight = '3px'; | |||
} | } | ||
Line 560: | Line 700: | ||
} | } | ||
childContainer = subnode; | |||
break; | break; | ||
case 'dyninput': | case 'dyninput': | ||
Line 569: | Line 709: | ||
label = node.appendChild(document.createElement('h5')); | label = node.appendChild(document.createElement('h5')); | ||
label.appendChild( | label.appendChild(Morebits.createHtml(data.label)); | ||
var listNode = node.appendChild(document.createElement('div')); | var listNode = node.appendChild(document.createElement('div')); | ||
Line 622: | Line 761: | ||
label.appendChild(document.createTextNode(data.label)); | label.appendChild(document.createTextNode(data.label)); | ||
label.setAttribute('for', id); | label.setAttribute('for', id); | ||
label.style.marginRight = '3px'; | |||
} | } | ||
Line 670: | Line 810: | ||
case 'header': | case 'header': | ||
node = document.createElement('h5'); | node = document.createElement('h5'); | ||
node.appendChild( | node.appendChild(Morebits.createHtml(data.label)); | ||
break; | break; | ||
case 'div': | case 'div': | ||
Line 678: | Line 818: | ||
} | } | ||
if (data.label) { | if (data.label) { | ||
var result = document.createElement('span'); | var result = document.createElement('span'); | ||
result.className = 'quickformDescription'; | result.className = 'quickformDescription'; | ||
result.appendChild(Morebits.createHtml(data.label)); | |||
node.appendChild(result); | node.appendChild(result); | ||
} | } | ||
Line 695: | Line 826: | ||
case 'submit': | case 'submit': | ||
node = document.createElement('span'); | node = document.createElement('span'); | ||
childContainer = node.appendChild(document.createElement('input')); | |||
childContainer.setAttribute('type', 'submit'); | |||
if (data.label) { | if (data.label) { | ||
childContainer.setAttribute('value', data.label); | |||
} | } | ||
childContainer.setAttribute('name', data.name || 'submit'); | |||
if (data.disabled) { | if (data.disabled) { | ||
childContainer.setAttribute('disabled', 'disabled'); | |||
} | } | ||
break; | break; | ||
case 'button': | case 'button': | ||
node = document.createElement('span'); | node = document.createElement('span'); | ||
childContainer = node.appendChild(document.createElement('input')); | |||
childContainer.setAttribute('type', 'button'); | |||
if (data.label) { | if (data.label) { | ||
childContainer.setAttribute('value', data.label); | |||
} | } | ||
childContainer.setAttribute('name', data.name); | |||
if (data.disabled) { | if (data.disabled) { | ||
childContainer.setAttribute('disabled', 'disabled'); | |||
} | } | ||
if (data.event) { | if (data.event) { | ||
childContainer.addEventListener('click', data.event, false); | |||
} | } | ||
break; | break; | ||
Line 726: | Line 857: | ||
label = node.appendChild(document.createElement('h5')); | label = node.appendChild(document.createElement('h5')); | ||
var labelElement = document.createElement('label'); | var labelElement = document.createElement('label'); | ||
labelElement. | labelElement.appendChild(Morebits.createHtml(data.label)); | ||
labelElement.setAttribute('for', data.id || id); | labelElement.setAttribute('for', data.id || id); | ||
label.appendChild(labelElement); | label.appendChild(labelElement); | ||
Line 750: | Line 881: | ||
subnode.value = data.value; | subnode.value = data.value; | ||
} | } | ||
childContainer = subnode; | |||
break; | break; | ||
default: | default: | ||
Line 756: | Line 887: | ||
} | } | ||
if (! | if (!childContainer) { | ||
childContainer = node; | |||
} | } | ||
if (data.tooltip) { | if (data.tooltip) { | ||
Line 764: | Line 895: | ||
if (data.extra) { | if (data.extra) { | ||
childContainer.extra = data.extra; | |||
} | |||
if (data.$data) { | |||
$(childContainer).data(data.$data); | |||
} | } | ||
if (data.style) { | if (data.style) { | ||
childContainer.setAttribute('style', data.style); | |||
} | } | ||
if (data.className) { | if (data.className) { | ||
childContainer.className = childContainer.className ? | |||
childContainer.className + ' ' + data.className : | |||
data.className; | data.className; | ||
} | } | ||
childContainer.setAttribute('id', data.id || id); | |||
return [ node, | return [ node, childContainer ]; | ||
}; | }; | ||
Line 791: | Line 925: | ||
tooltipButton.className = 'morebits-tooltipButton'; | tooltipButton.className = 'morebits-tooltipButton'; | ||
tooltipButton.title = data.tooltip; // Provides the content for jQuery UI | tooltipButton.title = data.tooltip; // Provides the content for jQuery UI | ||
tooltipButton.appendChild(document.createTextNode('?')); | tooltipButton.appendChild(document.createTextNode(msg('tooltip-mark', '?'))); | ||
$(tooltipButton).tooltip({ | $(tooltipButton).tooltip({ | ||
position: { my: 'left top', at: 'center bottom', collision: 'flipfit' }, | position: { my: 'left top', at: 'center bottom', collision: 'flipfit' }, | ||
Line 1,148: | Line 1,282: | ||
* Converts an IPv6 address to the canonical form stored and used by MediaWiki. | * Converts an IPv6 address to the canonical form stored and used by MediaWiki. | ||
* JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} | * JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} | ||
* function from the IPUtils library. | * function from the IPUtils library. Addresses are verbose, uppercase, | ||
* normalized, and expanded to 8 words. | * normalized, and expanded to 8 words. | ||
* | * | ||
Line 1,213: | Line 1,347: | ||
/** | /** | ||
* Check that an IP range is within the CIDR limits. Most likely to be useful | * Check that an IP range is within the CIDR limits. Most likely to be useful | ||
* in conjunction with `wgRelevantUserName`. CIDR limits are | * in conjunction with `wgRelevantUserName`. CIDR limits are hardcoded as /16 | ||
* for IPv4 and /32 for IPv6. | * for IPv4 and /32 for IPv6. | ||
* | * | ||
Line 1,256: | Line 1,390: | ||
return ipv6.replace(ip_re, '$1' + '0:0:0:0/64'); | return ipv6.replace(ip_re, '$1' + '0:0:0:0/64'); | ||
} | } | ||
}; | }; | ||
Line 1,435: | Line 1,545: | ||
* Escapes a string to be used in a RegExp, replacing spaces and | * Escapes a string to be used in a RegExp, replacing spaces and | ||
* underscores with `[_ ]` as they are often equivalent. | * underscores with `[_ ]` as they are often equivalent. | ||
* | * | ||
* @param {string} text - String to be escaped. | * @param {string} text - String to be escaped. | ||
Line 1,591: | Line 1,700: | ||
var search = target.data('select2').dropdown.$search || | var search = target.data('select2').dropdown.$search || | ||
target.data('select2').selection.$search; | target.data('select2').selection.$search; | ||
search.focus(); | // Use DOM .focus() to work around a jQuery 3.6.0 regression (https://github.com/select2/select2/issues/5993) | ||
search[0].focus(); | |||
} | } | ||
Line 1,699: | Line 1,809: | ||
} else if (typeof param === 'string') { | } else if (typeof param === 'string') { | ||
// Wikitext signature timestamp | // Wikitext signature timestamp | ||
var dateParts = Morebits. | var dateParts = Morebits.l10n.signatureTimestampFormat(param); | ||
if (dateParts) { | if (dateParts) { | ||
this._d = new Date(Date.UTC.apply(null, dateParts)); | this._d = new Date(Date.UTC.apply(null, dateParts)); | ||
Line 1,730: | Line 1,840: | ||
*/ | */ | ||
Morebits.date.localeData = { | Morebits.date.localeData = { | ||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], | // message names here correspond to MediaWiki message names | ||
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | months: [msg('january', 'January'), msg('february', 'February'), msg('march', 'March'), | ||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | msg('april', 'April'), msg('may_long', 'May'), msg('june', 'June'), | ||
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], | msg('july', 'July'), msg('august', 'August'), msg('september', 'September'), | ||
msg('october', 'October'), msg('november', 'November'), msg('december', 'December')], | |||
monthsShort: [msg('jan', 'Jan'), msg('feb', 'Feb'), msg('mar', 'Mar'), | |||
msg('apr', 'Apr'), msg('may', 'May'), msg('jun', 'Jun'), | |||
msg('jul', 'Jul'), msg('aug', 'Aug'), msg('sep', 'Sep'), | |||
msg('oct', 'Oct'), msg('nov', 'Nov'), msg('dec', 'Dec')], | |||
days: [msg('sunday', 'Sunday'), msg('monday', 'Monday'), msg('tuesday', 'Tuesday'), | |||
msg('wednesday', 'Wednesday'), msg('thursday', 'Thursday'), msg('friday', 'Friday'), | |||
msg('saturday', 'Saturday')], | |||
daysShort: [msg('sun', 'Sun'), msg('mon', 'Mon'), msg('tue', 'Tue'), | |||
msg('wed', 'Wed'), msg('thu', 'Thu'), msg('fri', 'Fri'), | |||
msg('sat', 'Sat')], | |||
relativeTimes: { | relativeTimes: { | ||
thisDay: '[Today at] h:mm A', | thisDay: msg('relative-today', '[Today at] h:mm A'), | ||
prevDay: '[Yesterday at] h:mm A', | prevDay: msg('relative-prevday', '[Yesterday at] h:mm A'), | ||
nextDay: '[Tomorrow at] h:mm A', | nextDay: msg('relative-nextday', '[Tomorrow at] h:mm A'), | ||
thisWeek: 'dddd [at] h:mm A', | thisWeek: msg('relative-thisweek', 'dddd [at] h:mm A'), | ||
pastWeek: '[Last] dddd [at] h:mm A', | pastWeek: msg('relative-pastweek', '[Last] dddd [at] h:mm A'), | ||
other: 'YYYY-MM-DD' | other: msg('relative-other', 'YYYY-MM-DD') | ||
} | } | ||
}; | }; | ||
Line 1,885: | Line 1,993: | ||
* |--------|--------| | * |--------|--------| | ||
* | H | Hours (24-hour) | | * | H | Hours (24-hour) | | ||
* | HH | Hours (24-hour, padded) | | * | HH | Hours (24-hour, padded to 2 digits) | | ||
* | h | Hours (12-hour) | | * | h | Hours (12-hour) | | ||
* | hh | Hours (12-hour, padded) | | * | hh | Hours (12-hour, padded to 2 digits) | | ||
* | A | AM or PM | | * | A | AM or PM | | ||
* | m | Minutes | | * | m | Minutes | | ||
* | mm | Minutes (padded) | | * | mm | Minutes (padded to 2 digits) | | ||
* | s | Seconds | | * | s | Seconds | | ||
* | ss | Seconds (padded) | | * | ss | Seconds (padded to 2 digits) | | ||
* | SSS | Milliseconds fragment, | * | SSS | Milliseconds fragment, 3 digits | | ||
* | d | Day number of the week (Sun=0) | | * | d | Day number of the week (Sun=0) | | ||
* | ddd | Abbreviated day name | | * | ddd | Abbreviated day name | | ||
* | dddd | Full day name | | * | dddd | Full day name | | ||
* | D | Date | | * | D | Date | | ||
* | DD | Date (padded) | | * | DD | Date (padded to 2 digits) | | ||
* | M | Month number ( | * | M | Month number (1-indexed) | | ||
* | MM | Month number ( | * | MM | Month number (1-indexed, padded to 2 digits) | | ||
* | MMM | Abbreviated month name | | * | MMM | Abbreviated month name | | ||
* | MMMM | Full month name | | * | MMMM | Full month name | | ||
Line 1,938: | Line 2,046: | ||
var h24 = udate.getHours(), m = udate.getMinutes(), s = udate.getSeconds(), ms = udate.getMilliseconds(); | var h24 = udate.getHours(), m = udate.getMinutes(), s = udate.getSeconds(), ms = udate.getMilliseconds(); | ||
var D = udate.getDate(), M = udate.getMonth() + 1, Y = udate.getFullYear(); | var D = udate.getDate(), M = udate.getMonth() + 1, Y = udate.getFullYear(); | ||
var h12 = h24 % 12 || 12, amOrPm = h24 >= 12 ? 'PM' : 'AM'; | var h12 = h24 % 12 || 12, amOrPm = h24 >= 12 ? msg('period-pm', 'PM') : msg('period-am', 'AM'); | ||
var replacementMap = { | var replacementMap = { | ||
HH: pad(h24), H: h24, hh: pad(h12), h: h12, A: amOrPm, | HH: pad(h24), H: h24, hh: pad(h12), h: h12, A: amOrPm, | ||
Line 2,297: | Line 2,405: | ||
this.onSuccess.call(this.parent, this); | this.onSuccess.call(this.parent, this); | ||
} else { | } else { | ||
this.statelem.info('done'); | this.statelem.info(msg('done', 'done')); | ||
} | } | ||
Line 2,309: | Line 2,417: | ||
this.statusText = statusText; | this.statusText = statusText; | ||
this.errorThrown = errorThrown; // frequently undefined | this.errorThrown = errorThrown; // frequently undefined | ||
this.errorText = statusText + ' "' + jqXHR.statusText + '" occurred while contacting the API.'; | this.errorText = msg('api-error', statusText, jqXHR.statusText, statusText + ' "' + jqXHR.statusText + '" occurred while contacting the API.'); | ||
return this.returnError(); | return this.returnError(); | ||
} | } | ||
Line 2,318: | Line 2,426: | ||
returnError: function(callerAjaxParameters) { | returnError: function(callerAjaxParameters) { | ||
if (this.errorCode === 'badtoken' && !this.badtokenRetry) { | if (this.errorCode === 'badtoken' && !this.badtokenRetry) { | ||
this.statelem.warn('Invalid token. Getting a new token and retrying...'); | this.statelem.warn(msg('invalid-token-retrying', 'Invalid token. Getting a new token and retrying...')); | ||
this.badtokenRetry = true; | this.badtokenRetry = true; | ||
// Get a new CSRF token and retry. If the original action needs a different | // Get a new CSRF token and retry. If the original action needs a different | ||
Line 2,362: | Line 2,470: | ||
} | } | ||
}; | |||
/** Retrieves wikitext from a page. Caching enabled, duration 1 day. */ | |||
Morebits.wiki.getCachedJson = function(title) { | |||
var query = { | |||
action: 'query', | |||
prop: 'revisions', | |||
titles: title, | |||
rvslots: '*', | |||
rvprop: 'content', | |||
format: 'json', | |||
smaxage: '86400', // cache for 1 day | |||
maxage: '86400' // cache for 1 day | |||
}; | |||
return new Morebits.wiki.api('', query).post().then(function(apiobj) { | |||
apiobj.getStatusElement().unlink(); | |||
var response = apiobj.getResponse(); | |||
var wikitext = response.query.pages[0].revisions[0].slots.main.content; | |||
return JSON.parse(wikitext); | |||
}); | |||
}; | }; | ||
Line 2,402: | Line 2,530: | ||
*/ | */ | ||
Morebits.wiki.api.getToken = function() { | Morebits.wiki.api.getToken = function() { | ||
var tokenApi = new Morebits.wiki.api('Getting token', { | var tokenApi = new Morebits.wiki.api(msg('getting-token', 'Getting token'), { | ||
action: 'query', | action: 'query', | ||
meta: 'tokens', | meta: 'tokens', | ||
Line 2,465: | Line 2,593: | ||
if (!status) { | if (!status) { | ||
status = 'Opening page "' + pageName + '"'; | status = msg('opening-page', pageName, 'Opening page "' + pageName + '"'); | ||
} | } | ||
Line 2,632: | Line 2,760: | ||
} | } | ||
ctx.loadApi = new Morebits.wiki.api('Retrieving page...', ctx.loadQuery, fnLoadSuccess, ctx.statusElement, ctx.onLoadFailure); | ctx.loadApi = new Morebits.wiki.api(msg('retrieving-page', 'Retrieving page...'), ctx.loadQuery, fnLoadSuccess, ctx.statusElement, ctx.onLoadFailure); | ||
ctx.loadApi.setParent(this); | ctx.loadApi.setParent(this); | ||
ctx.loadApi.post(); | ctx.loadApi.post(); | ||
Line 2,638: | Line 2,766: | ||
/** | /** | ||
* Saves the text for the page to | * Saves the text for the page to Bharatpedia. | ||
* Must be preceded by successfully calling `load()`. | * Must be preceded by successfully calling `load()`. | ||
* | * | ||
Line 2,644: | Line 2,772: | ||
* previous `load()` callbacks to recover from edit conflicts! In this | * previous `load()` callbacks to recover from edit conflicts! In this | ||
* case, callers must make the same edit to the new pageText and | * case, callers must make the same edit to the new pageText and | ||
* | * re-invoke `save()`. This behavior can be disabled with | ||
* `setMaxConflictRetries(0)`. | * `setMaxConflictRetries(0)`. | ||
* | * | ||
Line 2,677: | Line 2,805: | ||
// shouldn't happen if canUseMwUserToken === true | // shouldn't happen if canUseMwUserToken === true | ||
if (ctx.fullyProtected && !ctx.suppressProtectWarning && | if (ctx.fullyProtected && !ctx.suppressProtectWarning && | ||
!confirm('You are about to make an edit to the fully protected page "' + ctx.pageName + | !confirm( | ||
ctx.fullyProtected === 'infinity' | |||
? msg('protected-indef-edit-warning', ctx.pageName, | |||
ctx.statusElement.error('Edit to fully protected page was aborted.'); | 'You are about to make an edit to the fully protected page "' + ctx.pageName + '" (protected indefinitely). \n\nClick OK to proceed with the edit, or Cancel to skip this edit.' | ||
) | |||
: msg('protected-edit-warning', ctx.pageName, ctx.fullyProtected, | |||
'You are about to make an edit to the fully protected page "' + ctx.pageName + | |||
'" (protection expiring ' + new Morebits.date(ctx.fullyProtected).calendar('utc') + ' (UTC)). \n\nClick OK to proceed with the edit, or Cancel to skip this edit.' | |||
) | |||
) | |||
) { | |||
ctx.statusElement.error(msg('protected-aborted', 'Edit to fully protected page was aborted.')); | |||
ctx.onSaveFailure(this); | ctx.onSaveFailure(this); | ||
return; | return; | ||
Line 2,714: | Line 2,850: | ||
} | } | ||
// Set bot edit attribute. If this | // Set bot edit attribute. If this parameter is present with any value, it is interpreted as true | ||
if (ctx.botEdit) { | if (ctx.botEdit) { | ||
query.bot = true; | query.bot = true; | ||
Line 2,771: | Line 2,907: | ||
} | } | ||
ctx.saveApi = new Morebits.wiki.api('Saving page...', query, fnSaveSuccess, ctx.statusElement, fnSaveError); | ctx.saveApi = new Morebits.wiki.api(msg('saving-page', 'Saving page...'), query, fnSaveSuccess, ctx.statusElement, fnSaveError); | ||
ctx.saveApi.setParent(this); | ctx.saveApi.setParent(this); | ||
ctx.saveApi.post(); | ctx.saveApi.post(); | ||
Line 3,104: | Line 3,240: | ||
* 1. If there are no revisions among the first 50 that are | * 1. If there are no revisions among the first 50 that are | ||
* non-redirects, or if there are less 50 revisions and all are | * non-redirects, or if there are less 50 revisions and all are | ||
* redirects, the original creation is | * redirects, the original creation is retrieved. | ||
* 2. Revisions that the user is not privileged to access | * 2. Revisions that the user is not privileged to access | ||
* (revdeled/suppressed) will be treated as non-redirects. | * (revdeled/suppressed) will be treated as non-redirects. | ||
Line 3,335: | Line 3,471: | ||
} | } | ||
ctx.lookupCreationApi = new Morebits.wiki.api('Retrieving page creation information', query, fnLookupCreationSuccess, ctx.statusElement, ctx.onLookupCreationFailure); | ctx.lookupCreationApi = new Morebits.wiki.api(msg('getting-creator', 'Retrieving page creation information'), query, fnLookupCreationSuccess, ctx.statusElement, ctx.onLookupCreationFailure); | ||
ctx.lookupCreationApi.setParent(this); | ctx.lookupCreationApi.setParent(this); | ||
ctx.lookupCreationApi.post(); | ctx.lookupCreationApi.post(); | ||
Line 3,385: | Line 3,521: | ||
var query = fnNeedTokenInfoQuery('move'); | var query = fnNeedTokenInfoQuery('move'); | ||
ctx.moveApi = new Morebits.wiki.api('retrieving token...', query, fnProcessMove, ctx.statusElement, ctx.onMoveFailure); | ctx.moveApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessMove, ctx.statusElement, ctx.onMoveFailure); | ||
ctx.moveApi.setParent(this); | ctx.moveApi.setParent(this); | ||
ctx.moveApi.post(); | ctx.moveApi.post(); | ||
Line 3,424: | Line 3,560: | ||
}; | }; | ||
ctx.patrolApi = new Morebits.wiki.api('retrieving token...', patrolQuery, fnProcessPatrol); | ctx.patrolApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), patrolQuery, fnProcessPatrol); | ||
ctx.patrolApi.setParent(this); | ctx.patrolApi.setParent(this); | ||
ctx.patrolApi.post(); | ctx.patrolApi.post(); | ||
Line 3,463: | Line 3,599: | ||
var query = fnNeedTokenInfoQuery('triage'); | var query = fnNeedTokenInfoQuery('triage'); | ||
ctx.triageApi = new Morebits.wiki.api('retrieving token...', query, fnProcessTriageList); | ctx.triageApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessTriageList); | ||
ctx.triageApi.setParent(this); | ctx.triageApi.setParent(this); | ||
ctx.triageApi.post(); | ctx.triageApi.post(); | ||
Line 3,490: | Line 3,626: | ||
var query = fnNeedTokenInfoQuery('delete'); | var query = fnNeedTokenInfoQuery('delete'); | ||
ctx.deleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessDelete, ctx.statusElement, ctx.onDeleteFailure); | ctx.deleteApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessDelete, ctx.statusElement, ctx.onDeleteFailure); | ||
ctx.deleteApi.setParent(this); | ctx.deleteApi.setParent(this); | ||
ctx.deleteApi.post(); | ctx.deleteApi.post(); | ||
Line 3,515: | Line 3,651: | ||
var query = fnNeedTokenInfoQuery('undelete'); | var query = fnNeedTokenInfoQuery('undelete'); | ||
ctx.undeleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessUndelete, ctx.statusElement, ctx.onUndeleteFailure); | ctx.undeleteApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessUndelete, ctx.statusElement, ctx.onUndeleteFailure); | ||
ctx.undeleteApi.setParent(this); | ctx.undeleteApi.setParent(this); | ||
ctx.undeleteApi.post(); | ctx.undeleteApi.post(); | ||
Line 3,546: | Line 3,682: | ||
var query = fnNeedTokenInfoQuery('protect'); | var query = fnNeedTokenInfoQuery('protect'); | ||
ctx.protectApi = new Morebits.wiki.api('retrieving token...', query, fnProcessProtect, ctx.statusElement, ctx.onProtectFailure); | ctx.protectApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessProtect, ctx.statusElement, ctx.onProtectFailure); | ||
ctx.protectApi.setParent(this); | ctx.protectApi.setParent(this); | ||
ctx.protectApi.post(); | ctx.protectApi.post(); | ||
Line 3,581: | Line 3,717: | ||
var query = fnNeedTokenInfoQuery('stabilize'); | var query = fnNeedTokenInfoQuery('stabilize'); | ||
ctx.stabilizeApi = new Morebits.wiki.api('retrieving token...', query, fnProcessStabilize, ctx.statusElement, ctx.onStabilizeFailure); | ctx.stabilizeApi = new Morebits.wiki.api(msg('getting-token', 'retrieving token...'), query, fnProcessStabilize, ctx.statusElement, ctx.onStabilizeFailure); | ||
ctx.stabilizeApi.setParent(this); | ctx.stabilizeApi.setParent(this); | ||
ctx.stabilizeApi.post(); | ctx.stabilizeApi.post(); | ||
Line 3,704: | Line 3,840: | ||
ctx.csrfToken = response.tokens.csrftoken; | ctx.csrfToken = response.tokens.csrftoken; | ||
if (!ctx.csrfToken) { | if (!ctx.csrfToken) { | ||
ctx.statusElement.error('Failed to retrieve edit token.'); | ctx.statusElement.error(msg('token-fetch-fail', 'Failed to retrieve edit token.')); | ||
ctx.onLoadFailure(this); | ctx.onLoadFailure(this); | ||
return; | return; | ||
Line 3,778: | Line 3,914: | ||
// check for invalid titles | // check for invalid titles | ||
if (page.invalid) { | if (page.invalid) { | ||
ctx.statusElement.error('The page title is invalid: ' + ctx.pageName); | ctx.statusElement.error(msg('invalid-title', ctx.pageName, 'The page title is invalid: ' + ctx.pageName)); | ||
onFailure(this); | onFailure(this); | ||
return false; // abort | return false; // abort | ||
Line 3,791: | Line 3,927: | ||
var newNs = new mw.Title(resolvedName).namespace; | var newNs = new mw.Title(resolvedName).namespace; | ||
if (origNs !== newNs && !ctx.followCrossNsRedirect) { | if (origNs !== newNs && !ctx.followCrossNsRedirect) { | ||
ctx.statusElement.error(ctx.pageName + ' is a cross-namespace redirect to ' + resolvedName + ', aborted'); | ctx.statusElement.error(msg('cross-redirect-abort', ctx.pageName, resolvedName, ctx.pageName + ' is a cross-namespace redirect to ' + resolvedName + ', aborted')); | ||
onFailure(this); | onFailure(this); | ||
return false; | return false; | ||
Line 3,797: | Line 3,933: | ||
// only notify user for redirects, not normalization | // only notify user for redirects, not normalization | ||
new Morebits.status('Note', 'Redirected from ' + ctx.pageName + ' to ' + resolvedName); | new Morebits.status('Note', msg('redirected', ctx.pageName, resolvedName, 'Redirected from ' + ctx.pageName + ' to ' + resolvedName)); | ||
} | } | ||
Line 3,804: | Line 3,940: | ||
} else { | } else { | ||
// could be a circular redirect or other problem | // could be a circular redirect or other problem | ||
ctx.statusElement.error('Could not resolve redirects for: ' + ctx.pageName); | ctx.statusElement.error(msg('redirect-resolution-fail', ctx.pageName, 'Could not resolve redirects for: ' + ctx.pageName)); | ||
onFailure(this); | onFailure(this); | ||
Line 3,881: | Line 4,017: | ||
ctx.statusElement.error('Could not save the page because the wiki server wanted you to fill out a CAPTCHA.'); | ctx.statusElement.error('Could not save the page because the wiki server wanted you to fill out a CAPTCHA.'); | ||
} else { | } else { | ||
ctx.statusElement.error('Unknown error received from API while saving page'); | ctx.statusElement.error(msg('api-error-unknown', 'Unknown error received from API while saving page')); | ||
} | } | ||
Line 3,903: | Line 4,039: | ||
}; | }; | ||
var purgeApi = new Morebits.wiki.api('Edit conflict detected, purging server cache', purgeQuery, function() { | var purgeApi = new Morebits.wiki.api(msg('editconflict-purging', 'Edit conflict detected, purging server cache'), purgeQuery, function() { | ||
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds | --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds | ||
ctx.statusElement.info('Edit conflict detected, reapplying edit'); | ctx.statusElement.info(msg('editconflict-retrying', 'Edit conflict detected, reapplying edit')); | ||
if (fnCanUseMwUserToken('edit')) { | if (fnCanUseMwUserToken('edit')) { | ||
ctx.saveApi.post(); // necessarily append, prepend, or newSection, so this should work as desired | ctx.saveApi.post(); // necessarily append, prepend, or newSection, so this should work as desired | ||
Line 3,919: | Line 4,055: | ||
// the error might be transient, so try again | // the error might be transient, so try again | ||
ctx.statusElement.info('Save failed, retrying in 2 seconds ...'); | ctx.statusElement.info(msg('save-failed-retrying', 2, 'Save failed, retrying in 2 seconds ...')); | ||
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds | --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds | ||
// wait for sometime for client to regain | // wait for sometime for client to regain connectivity | ||
sleep(2000).then(function() { | sleep(2000).then(function() { | ||
ctx.saveApi.post(); // give it another go! | ctx.saveApi.post(); // give it another go! | ||
Line 3,962: | Line 4,098: | ||
} | } | ||
} | } | ||
}; | |||
var isTextRedirect = function(text) { | |||
if (!text) { // no text - content empty or inaccessible (revdelled or suppressed) | |||
return false; | |||
} | |||
return Morebits.l10n.redirectTagAliases.some(function(tag) { | |||
return new RegExp('^\\s*' + tag + '\\W', 'i').test(text); | |||
}); | |||
}; | }; | ||
Line 3,976: | Line 4,121: | ||
ctx.onLookupCreationFailure(this); | ctx.onLookupCreationFailure(this); | ||
return; | return; | ||
} | } | ||
if (!ctx.lookupNonRedirectCreator || !/ | if (!ctx.lookupNonRedirectCreator || !isTextRedirect(rev.content)) { | ||
ctx.creator = rev.user; | |||
if (!ctx.creator) { | |||
ctx.statusElement.error('Could not find name of page creator'); | |||
ctx.onLookupCreationFailure(this); | |||
return; | |||
} | |||
ctx.timestamp = rev.timestamp; | |||
if (!ctx.timestamp) { | |||
ctx.statusElement.error('Could not find timestamp of page creation'); | |||
ctx.onLookupCreationFailure(this); | |||
return; | |||
} | |||
ctx.statusElement.info('retrieved page creation information'); | |||
ctx.onLookupCreationSuccess(this); | |||
} else { | |||
ctx.lookupCreationApi.query.rvlimit = 50; // modify previous query to fetch more revisions | |||
ctx.lookupCreationApi.query.titles = ctx.pageName; // update pageName if redirect resolution took place in earlier query | |||
ctx.lookupCreationApi = new Morebits.wiki.api('Retrieving page creation information', ctx.lookupCreationApi.query, fnLookupNonRedirectCreator, ctx.statusElement, ctx.onLookupCreationFailure); | |||
ctx.lookupCreationApi.setParent(this); | |||
ctx.lookupCreationApi.post(); | |||
} | |||
}; | |||
var fnLookupNonRedirectCreator = function() { | |||
var response = ctx.lookupCreationApi.getResponse().query; | |||
var revs = response.pages[0].revisions; | |||
for (var i = 0; i < revs.length; i++) { | |||
if (!isTextRedirect(revs[i].content)) { | |||
if (! | |||
ctx.creator = revs[i].user; | ctx.creator = revs[i].user; | ||
ctx.timestamp = revs[i].timestamp; | ctx.timestamp = revs[i].timestamp; | ||
Line 4,162: | Line 4,308: | ||
} | } | ||
ctx.moveProcessApi = new Morebits.wiki.api('moving page...', query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure); | ctx.moveProcessApi = new Morebits.wiki.api(msg('moving-page', 'moving page...'), query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure); | ||
ctx.moveProcessApi.setParent(this); | ctx.moveProcessApi.setParent(this); | ||
ctx.moveProcessApi.post(); | ctx.moveProcessApi.post(); | ||
Line 4,775: | Line 4,921: | ||
*/ | */ | ||
removeLink: function(link_target) { | removeLink: function(link_target) { | ||
// | // Remove a leading colon, to be handled later | ||
if (link_target.indexOf(':') === 0) { | if (link_target.indexOf(':') === 0) { | ||
link_target = link_target.slice(1); | link_target = link_target.slice(1); | ||
Line 4,837: | Line 4,983: | ||
unbinder.unbind('<!--', '-->'); | unbinder.unbind('<!--', '-->'); | ||
// Check free image usages, for example as template arguments, might have the File: prefix excluded, but must be | // Check free image usages, for example as template arguments, might have the File: prefix excluded, but must be preceded by an | | ||
// Will only eat the image name and the | // Will only eat the image name and the preceding bar and an eventual named parameter | ||
var free_image_re = new RegExp('(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:' + Morebits.namespaceRegex(6) + ':\\s*)?' + image_re_string + ')', 'mg'); | var free_image_re = new RegExp('(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:' + Morebits.namespaceRegex(6) + ':\\s*)?' + image_re_string + ')', 'mg'); | ||
unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->'); | unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->'); | ||
Line 5,048: | Line 5,194: | ||
Morebits.status = function Status(text, stat, type) { | Morebits.status = function Status(text, stat, type) { | ||
this.textRaw = text; | this.textRaw = text; | ||
this.text = | this.text = Morebits.createHtml(text); | ||
this.type = type || 'status'; | this.type = type || 'status'; | ||
this.generate(); | this.generate(); | ||
Line 5,113: | Line 5,259: | ||
this.linked = false; | this.linked = false; | ||
} | } | ||
}, | }, | ||
Line 5,151: | Line 5,270: | ||
update: function(status, type) { | update: function(status, type) { | ||
this.statRaw = status; | this.statRaw = status; | ||
this.stat = | this.stat = Morebits.createHtml(status); | ||
if (type) { | if (type) { | ||
this.type = type; | this.type = type; | ||
Line 5,402: | Line 5,521: | ||
// internal counters, etc. | // internal counters, etc. | ||
statusElement: new Morebits.status(currentAction || 'Performing batch operation'), | statusElement: new Morebits.status(currentAction || msg('batch-starting', 'Performing batch operation')), | ||
worker: null, // function that executes for each item in pageList | worker: null, // function that executes for each item in pageList | ||
postFinish: null, // function that executes when the whole batch has been processed | postFinish: null, // function that executes when the whole batch has been processed | ||
Line 5,469: | Line 5,588: | ||
var total = ctx.pageList.length; | var total = ctx.pageList.length; | ||
if (!total) { | if (!total) { | ||
ctx.statusElement.info('no pages specified'); | ctx.statusElement.info(msg('batch-no-pages', 'no pages specified')); | ||
ctx.running = false; | ctx.running = false; | ||
if (ctx.postFinish) { | if (ctx.postFinish) { | ||
Line 5,487: | Line 5,606: | ||
/** | /** | ||
* To be called by worker before it terminates | * To be called by worker before it terminates successfully. | ||
* | * | ||
* @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg - | * @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg - | ||
Line 5,496: | Line 5,615: | ||
*/ | */ | ||
this.workerSuccess = function(arg) { | this.workerSuccess = function(arg) { | ||
if (arg instanceof Morebits.wiki.api || arg instanceof Morebits.wiki.page) { | if (arg instanceof Morebits.wiki.api || arg instanceof Morebits.wiki.page) { | ||
Line 5,511: | Line 5,623: | ||
// we know the page title - display a relevant message | // we know the page title - display a relevant message | ||
var pageName = arg.getPageName ? arg.getPageName() : arg.pageName || arg.query.title; | var pageName = arg.getPageName ? arg.getPageName() : arg.pageName || arg.query.title; | ||
statelem.info( | statelem.info(msg('batch-done-page', pageName, 'completed ([[' + pageName + ']])')); | ||
} else { | } else { | ||
// we don't know the page title - just display a generic message | // we don't know the page title - just display a generic message | ||
statelem.info('done'); | statelem.info(msg('done', 'done')); | ||
} | } | ||
} else { | } else { | ||
Line 5,522: | Line 5,634: | ||
} else if (typeof arg === 'string' && ctx.options.preserveIndividualStatusLines) { | } else if (typeof arg === 'string' && ctx.options.preserveIndividualStatusLines) { | ||
new Morebits.status(arg, | new Morebits.status(arg, msg('batch-done-page', arg, 'completed ([[' + arg + ']])')); | ||
} | } | ||
Line 5,556: | Line 5,668: | ||
var total = ctx.pageList.length; | var total = ctx.pageList.length; | ||
if (ctx.countFinished < total) { | if (ctx.countFinished < total) { | ||
var progress = Math.round(100 * ctx.countFinished / total); | |||
ctx.statusElement.status(msg('percent', progress, progress + '%')); | |||
// start a new chunk if we're close enough to the end of the previous chunk, and | // start a new chunk if we're close enough to the end of the previous chunk, and | ||
Line 5,565: | Line 5,678: | ||
} | } | ||
} else if (ctx.countFinished === total) { | } else if (ctx.countFinished === total) { | ||
var statusString = 'Done (' + ctx.countFinishedSuccess + | var statusString = msg('batch-progress', ctx.countFinishedSuccess, ctx.countFinished, 'Done (' + ctx.countFinishedSuccess + | ||
'/' + ctx.countFinished + ' actions completed successfully)'; | '/' + ctx.countFinished + ' actions completed successfully)'); | ||
if (ctx.countFinishedSuccess < ctx.countFinished) { | if (ctx.countFinishedSuccess < ctx.countFinished) { | ||
ctx.statusElement.warn(statusString); | ctx.statusElement.warn(statusString); | ||
Line 5,866: | Line 5,979: | ||
value.style.display = 'none'; | value.style.display = 'none'; | ||
var button = document.createElement('button'); | var button = document.createElement('button'); | ||
button.textContent = value.hasAttribute('value') ? value.getAttribute('value') : value.textContent ? value.textContent : 'Submit Query'; | button.textContent = value.hasAttribute('value') ? value.getAttribute('value') : value.textContent ? value.textContent : msg('submit', 'Submit Query'); | ||
button.className = value.className || 'submitButtonProxy'; | button.className = value.className || 'submitButtonProxy'; | ||
// here is an instance of cheap coding, probably a memory-usage hit in using a closure here | // here is an instance of cheap coding, probably a memory-usage hit in using a closure here | ||
Line 5,914: | Line 6,027: | ||
if (this.hasFooterLinks) { | if (this.hasFooterLinks) { | ||
var bullet = document.createElement('span'); | var bullet = document.createElement('span'); | ||
bullet.textContent = ' \u2022 '; // U+2022 BULLET | bullet.textContent = msg('bullet-separator', ' \u2022 '); // U+2022 BULLET | ||
if (prep) { | if (prep) { | ||
$footerlinks.prepend(bullet); | $footerlinks.prepend(bullet); | ||
Line 5,982: | Line 6,095: | ||
window.SimpleWindow = Morebits.simpleWindow; | window.SimpleWindow = Morebits.simpleWindow; | ||
window.QuickForm = Morebits.quickForm; | window.QuickForm = Morebits.quickForm; | ||
window. | window.Bharatpedia = Morebits.wiki; | ||
window.Status = Morebits.status; | window.Status = Morebits.status; | ||
} | } | ||
// </nowiki> | // </nowiki> |