MediaWiki:Gadget-morebits.js: Difference between revisions
Content deleted Content added
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 |
Amorymeltzer (talk | contribs) Repo at c695df4: Improve and standardize JSDocs |
||
Line 1:
// <nowiki>
/**
* A library full of lots of goodness for user scripts on MediaWiki wikis, including Wikipedia.
*
* The highlights include:
* -
*
* -
* -
* -
* -
* - {@link Morebits.wikitext} - utilities for dealing with wikitext
* - {@link Morebits.string} - utilities for manipulating strings
* - {@link Morebits.array} - utilities for manipulating arrays
*
* Dependencies:
*
*
*
*
* -
*
* -
* and then load ext.gadget.morebits as one of the dependencies for the new gadget.
*
* All the stuff here works on all browsers for which MediaWiki provides JavaScript support.
*
* This library is maintained by the maintainers of Twinkle.
* For queries, suggestions, help, etc., head to
* The latest development source is available at
*
* @namespace Morebits
*/
Line 33 ⟶ 36:
(function (window, document, $) { // Wrap entire file with anonymous function
/** @lends Morebits */
var Morebits = {};
window.Morebits = Morebits; // allow global access
/**
* Simple helper function to see what groups a user might belong.
*
* @param {string} group -
* @returns {boolean}
*/
Line 47 ⟶ 50:
return mw.config.get('wgUserGroups').indexOf(group) !== -1;
};
/** Hardcodes whether the user is a sysop, used a lot.
*
* @constant
* @type {boolean}
*/
Morebits.userIsSysop = Morebits.userIsInGroup('sysop');
/**
* 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/libs/IPUtils/+/refs/heads/master/src/IPUtils.php#214|`IP::sanitizeIP()`}
* function from the IPUtils library.
*
* @param {string} address - The IPv6 address.
* @returns {string}
*/
Line 103 ⟶ 108:
}
// Remove leading zeros from each bloc as needed
};
/**
* 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);
};
/**
* Stores a normalized (underscores converted to spaces) version of the
* `wgPageName` variable.
*
* @type {string}
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
/**
* Create a string for use in regex matching a page name, regardless of the
* leading character's capitalization.
*
* @param {string} pageName - Page name without namespace.
* @returns {string} - For a page name `Foo bar`, returns the string `[Ff]oo bar`.
*/
Morebits.pageNameRegex = function(pageName) {
return '[' + pageName[0].toUpperCase() + pageName[0].toLowerCase() + ']' + pageName.slice(1);
};
/* **************** Morebits.quickForm **************** */
/**
* Creation of simple and standard forms without much specific coding.
*
* @namespace Morebits.quickForm
* @memberof Morebits
* @class
* @param {event} event - Function to execute when form is submitted.
* @param {string} [eventType=submit] - Type of the event.
*/
Morebits.quickForm = function QuickForm(event, eventType) {
Line 163 ⟶ 158:
/**
* Renders the HTML output of the quickForm.
*
* @memberof Morebits.quickForm
* @returns {HTMLElement}
*/
Line 173 ⟶ 170:
/**
* Append element to the form.
*
* @memberof Morebits.quickForm
* @param {(object|Morebits.quickForm.element)} data - A quickform element, or the object with which
* a quickform element is constructed.
* @returns {Morebits.quickForm.element} -
*/
Morebits.quickForm.prototype.append = function QuickFormAppend(data) {
Line 183 ⟶ 182:
/**
* Create a new element for the the form.
*
* Index to Morebits.quickForm.element types:
* - Global attributes: id, className, style, tooltip, extra, adminonly
* - `select`: A combo box (aka drop-down).
* - Attributes: name, label, multiple, size, list, event, disabled
* - `option`: An element for a combo box.
* - Attributes: value, label, selected, disabled
* - `optgroup`: A group of "option"s.
* - Attributes: label, list
* - `field`: A fieldset (aka group box).
* - Attributes: name, label, disabled
* - `checkbox`: A checkbox. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* - `radio`: A radio button. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* - `input`: A text box.
* - Attributes: name, label, value, size, disabled, required, readonly, maxlength, event
* - `dyninput`: A set of text boxes with "Remove" buttons and an "Add" button.
* - Attributes: name, label, min, max, sublabel, value, size, maxlength, event
* - `hidden`: An invisible form field.
* - Attributes: name, value
* - `header`: A level 5 header.
* - Attributes: label
* - `div`: A generic placeholder element or label.
* - Attributes: name, label
* - `submit`: A submit button. Morebits.simpleWindow moves these to the footer of the dialog.
* - Attributes: name, label, disabled
* - `button`: A generic button.
* - Attributes: name, label, disabled, event
* - `textarea`: A big, multi-line text box.
* - Attributes: name, label, value, cols, rows, disabled, required, readonly
* - `fragment`: A DocumentFragment object.
* - No attributes, and no global attributes except adminonly.
*
* @memberof Morebits.quickForm
* @class
* @param {object} data - Object representing the quickform element. Should
* specify one of the available types from the index above, as well as any
* relevant and available attributes.
* @example new Morebits.quickForm.element({
* name: 'target',
* type: 'input',
* label: 'Your target:',
* tooltip: 'Enter your target. Required.',
* required: true
* });
*/
Morebits.quickForm.element = function QuickFormElement(data) {
Line 193 ⟶ 238:
};
/**
* @memberof Morebits.quickForm.element
* @type {number}
*/
Morebits.quickForm.element.id = 0;
/**
* Appends an element to current element.
*
*
* @
* create the quickForm element.
* @returns {Morebits.quickForm.element} The same element passed in.
*/
Morebits.quickForm.element.prototype.append = function QuickFormElementAppend(data) {
Line 213 ⟶ 264:
/**
* Renders the HTML output for the quickForm element. This should be called
*
*
* @memberof Morebits.quickForm.element
* @returns {HTMLElement}
*/
Line 227 ⟶ 280:
};
/** @memberof Morebits.quickForm.element */
Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) {
var node;
Line 702 ⟶ 756:
/**
* Create a
*
* @memberof Morebits.quickForm.element
* @requires jquery.ui
* @param {HTMLElement} node -
* @param {
*/
Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip(node, data) {
Line 726 ⟶ 782:
* Returns an object containing all filled form data entered by the user, with the object
* keys being the form element names. Disabled fields will be ignored, but not hidden fields.
*
* @memberof Morebits.quickForm
* @param {HTMLFormElement} form
* @returns {
*/
Morebits.quickForm.getInputData = function(form) {
Line 778 ⟶ 836:
/**
* Returns all form elements with a given field name or ID.
*
* @memberof Morebits.quickForm
* @param {HTMLFormElement} form
* @param {string} fieldName -
* @returns {HTMLElement[]} -
*/
Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) {
Line 797 ⟶ 857:
* Searches the array of elements for a checkbox or radio button with a certain
* `value` attribute, and returns the first such element. Returns null if not found.
*
* @memberof Morebits.quickForm
* @param {HTMLInputElement[]} elementArray - Array of checkbox or radio elements.
* @param {string} value - Value to search for.
* @returns {HTMLInputElement}
*/
Line 812 ⟶ 874:
/**
* Returns the
* May not work as expected on checkboxes or radios.
*
* @memberof Morebits.quickForm
* @param {HTMLElement} element
* @returns {HTMLElement}
Line 830 ⟶ 894:
/**
* Gets the HTML element that contains the label of the given form element
* (mainly for internal use).
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {HTMLElement}
Line 851 ⟶ 917:
/**
* Gets the label text of the element.
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {string}
Line 865 ⟶ 933:
/**
* Sets the label of the element to the given text.
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} labelText
* @returns {boolean}
*/
Morebits.quickForm.setElementLabel = function QuickFormSetElementLabel(element, labelText) {
Line 881 ⟶ 951:
/**
* Stores the element's current label, and temporarily sets the label to the given text.
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} temporaryLabelText
* @returns {boolean} `true` if succeeded, `false` if the label element is unavailable.
*/
Morebits.quickForm.overrideElementLabel = function QuickFormOverrideElementLabel(element, temporaryLabelText) {
Line 894 ⟶ 966:
/**
* Restores the label stored by overrideElementLabel.
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {boolean}
*/
Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) {
Line 906 ⟶ 980:
/**
* Shows or hides a form element plus its label and tooltip.
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|jQuery|string)} element - HTML/jQuery element, or jQuery selector string.
* @param {boolean} [visibility] - Skip this to toggle visibility.
*/
Morebits.quickForm.setElementVisibility = function QuickFormSetElementVisibility(element, visibility) {
Line 915 ⟶ 991:
/**
* Shows or hides the
*
* @memberof Morebits.quickForm
* @param {(HTMLElement|jQuery)} element
* @param {boolean} [visibility] - Skip this to toggle visibility.
*/
Morebits.quickForm.setElementTooltipVisibility = function QuickFormSetElementTooltipVisibility(element, visibility) {
Line 926 ⟶ 1,004:
/**
*
*/
/**
* Get checked items in the form.
*
* @function external:HTMLFormElement.getChecked
* @param {string} name - Find checked property of elements (i.e. a checkbox
* or a radiobutton) with the given name, or select options that have selected
* set to true (don't try to mix selects with radio/checkboxes).
* @param {string} [type] - Optionally specify either radio or checkbox (for
* the event that both checkboxes and radiobuttons have the same name).
* @returns {string[]} - Contains the values of elements with the given name
* checked property set to true.
*/
HTMLFormElement.prototype.getChecked = function(name, type) {
Line 980 ⟶ 1,061:
/**
* Does the same as {@link HTMLFormElement.getChecked|getChecked}, but with unchecked elements.
*
* @function external:HTMLFormElement.getUnchecked
* @param {string} name - Find checked property of elements (i.e. a checkbox
* or a radiobutton) with the given name, or select options that have selected
* set to true (don't try to mix selects with radio/checkboxes).
* @param {string} [type] - Optionally specify either radio or checkbox (for
* the event that both checkboxes and radiobuttons have the same name).
* @returns {string[]} - Contains the values of elements with the given name
* checked property set to true.
*/
HTMLFormElement.prototype.getUnchecked = function(name, type) {
var elements = this.elements[name];
Line 1,028 ⟶ 1,116:
/**
* @external RegExp
*/
/**
* Deprecated as of September 2020, use {@link Morebits.string.escapeRegExp}
* or `mw.util.escapeRegExp`.
*
* @function external:RegExp.escape
* @deprecated Use {@link Morebits.string.escapeRegExp} or `mw.util.escapeRegExp`.
* @param {string} text - String to be escaped.
* @param {boolean} [space_fix=false] - Whether to replace spaces and
* underscores with `[ _]` as they are often equivalent.
* @returns {string} - The escaped text.
*/
RegExp.escape = function(text, space_fix) {
Line 1,042 ⟶ 1,140:
/**
* Helper functions to manipulate strings.
*
* @namespace Morebits.string
* @memberof Morebits
*/
Morebits.string = {
/**
* @param {string} str
* @returns {string}
*/
toUpperCaseFirstChar: function(str) {
str = str.toString();
return str.substr(0, 1).toUpperCase() + str.substr(1);
},
/**
* @param {string} str
* @returns {string}
*/
toLowerCaseFirstChar: function(str) {
str = str.toString();
Line 1,058 ⟶ 1,165:
/**
* Gives an array of substrings of `str` starting with `start` and
* ending with `end`, which is not in `skiplist`.
*
* @param {string} str
* @param {string} start
* @param {string} end
* @param {(string[]|string)} [skiplist]
* @returns {
* @throws If the `start` and `end` strings aren't of the same length.
*/
splitWeightedByKeys: function(str, start, end, skiplist) {
Line 1,109 ⟶ 1,218:
/**
* Formats freeform "reason" (from a textarea) for deletion/other templates
* that are going to be substituted, (e.g. PROD, XFD, RPP).
*
* @param {string} str
* @returns {string}
Line 1,122 ⟶ 1,232:
/**
* Formats a "reason" (from a textarea) for inclusion in a userspace log.
*
* @param {string} str
* @returns {string}
Line 1,136 ⟶ 1,247:
/**
* Like `String.prototype.replace()`, but escapes any dollar signs in
* the replacement string. Useful when the the replacement string is
* arbitrary, such as a username or freeform user input, and could *
*
* @param {string} string - Text in which to replace.
* @param {(string|RegExp)} pattern
* @param {string} replacement
Line 1,149 ⟶ 1,262:
/**
* Determine if the user
* infinite-length by MW
* * @see {@link https://phabricator.wikimedia.org/T68646} *
* @param {string} expiry
* @returns {boolean}
Line 1,160 ⟶ 1,276:
/**
* Escapes a string to be used in a RegExp, replacing spaces and
* underscores with `[ _]` as they are often equivalent.
* Replaced RegExp.escape September 2020.
*
* @
* @returns {string} - The escaped text.
*/
escapeRegExp: function(text) {
Line 1,172 ⟶ 1,289:
/**
* Helper functions to manipulate arrays.
*
* @namespace Morebits.array
* @memberof Morebits
*/
Morebits.array = {
/**
* Remove duplicated items from an array.
*
* @param {Array} arr
* @returns {Array} A copy of the array with duplicates removed.
* @throws When provided a non-array.
*/
uniq: function(arr) {
Line 1,189 ⟶ 1,312:
/**
* Remove non-duplicated items from an array.
*
* @param {Array} arr
* @returns {Array} A copy of the array with the first instance of each value
* removed; subsequent instances of those values (duplicates) remain.
* @throws When provided a non-array.
*/
dups: function(arr) {
Line 1,204 ⟶ 1,331:
/**
* Break up an array into smaller arrays.
*
* @param {Array} arr
* @param {number} size - Size of each chunk (except the last, which could be different).
* @returns {Array[]}
* @throws When provided a non-array.
*/
chunk: function(arr, size) {
Line 1,225 ⟶ 1,354:
/**
* Utilities to enhance select2 menus. See twinklewarn, twinklexfd,
* twinkleblock for sample usages.
*
* @see {@link https://select2.org/}
*
* @namespace Morebits.select2
* @memberof Morebits
* @requires jquery.select2
*/
Morebits.select2 = {
matchers: {
/**
* Custom matcher in which if the optgroup name matches, all options in that
* group are shown, like in jquery.chosen.
*/
optgroupFull: function(params, data) {
Line 1,247 ⟶ 1,380:
},
/** Custom matcher that matches from the beginning of words only. */
wordBeginning: function(params, data) {
var originalMatcher = $.fn.select2.defaults.defaults.matcher;
Line 1,259 ⟶ 1,392:
},
/** Underline matched part of options. */
highlightSearchMatches: function(data) {
var searchTerm = Morebits.select2SearchQuery;
Line 1,277 ⟶ 1,410:
},
/** Intercept query as it is happening, for use in highlightSearchMatches. */
queryInterceptor: function(params) {
Morebits.select2SearchQuery = params && params.term;
Line 1,283 ⟶ 1,416:
/**
* Open dropdown and begin search when the `.select2-selection` has
* focus and a key is pressed. *
* @see {@link https://github.com/select2/select2/issues/3279#issuecomment-442524147}
*/
autoStart: function(ev) {
Line 1,305 ⟶ 1,440:
/**
* Temporarily hide a part of a string while processing the rest of it.
* Used by {@link Morebits.wikitext.page#commentOutImage|Morebits.wikitext.page.commentOutImage}.
*
* @memberof Morebits
* @class
* @param {string} string - The initial text to process.
* @example var u = new
* u.unbind('<!--', '-->'); // text inside comment remains intact
* u.content = u.content.replace(/world/g, 'earth');
* u.rebind(); // gives 'Hello earth <!-- world --> earth'
*/
Morebits.unbinder = function Unbinder(string) {
Line 1,356 ⟶ 1,455:
throw new Error('not a string');
}
/** The text being processed. */
this.content = string;
this.counter = 0;
Line 1,365 ⟶ 1,465:
Morebits.unbinder.prototype = {
/**
* Hide the region encapsulated by the `prefix` and `postfix` from string processing.
*
* @param {string} prefix
* @param {string} postfix
Line 1,373 ⟶ 1,475:
},
/**
* Restore the hidden portion of the `content` string.
*
* @returns {string} The processed output.
*/
rebind: function UnbinderRebind() {
var content = this.content;
Line 1,390 ⟶ 1,496:
history: null // {}
};
/** @memberof Morebits.unbinder */
Morebits.unbinder.getCallback = function UnbinderGetCallback(self) {
return function UnbinderCallback(match) {
Line 1,402 ⟶ 1,508:
/* **************** Morebits.date **************** */
/**
* Create a date object with enhanced processing capabilities, a la {@link
* https://momentjs.com/|moment.js}. MediaWiki timestamp format is also
* acceptable, in addition to everything that JS Date() accepts.
* @memberof Morebits
* @class
*/
Morebits.date = function() {
Line 1,430 ⟶ 1,536:
};
/**
* Localized strings for date processing.
*
* @memberof Morebits.date
* @type {object.<string, string>}
* @property {string[]} months
* @property {string[]} monthsShort
* @property {string[]} days
* @property {string[]} daysShort
* @property {object.<string, string>} relativeTimes
* @private
*/
Morebits.date.localeData = {
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
Line 1,458 ⟶ 1,576:
};
Morebits.date.prototype = {
/** @returns {boolean} */
isValid: function() {
return !isNaN(this.getTime());
},
/**
* @param {(Date|Morebits.date)} date
* @returns {boolean}
*/
isBefore: function(date) {
return this.getTime() < date.getTime();
},
/**
* @param {(Date|Morebits.date)} date
* @returns {boolean}
*/
isAfter: function(date) {
return this.getTime() > date.getTime();
},
/** @
getUTCMonthName: function() {
return Morebits.date.localeData.months[this.getUTCMonth()];
},
/** @returns {string} */
getUTCMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getUTCMonth()];
},
/** @returns {string} */
getMonthName: function() {
return Morebits.date.localeData.months[this.getMonth()];
},
/** @returns {string} */
getMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getMonth()];
},
/** @returns {string} */
getUTCDayName: function() {
return Morebits.date.localeData.days[this.getUTCDay()];
},
/** @returns {string} */
getUTCDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getUTCDay()];
},
/** @returns {string} */
getDayName: function() {
return Morebits.date.localeData.days[this.getDay()];
},
/** @returns {string} */
getDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getDay()];
Line 1,508 ⟶ 1,633:
* Add a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
*
* @param {number} number - Should be an integer.
* @param {string} unit
* @throws
* @returns {Morebits.date}
*/
Line 1,534 ⟶ 1,660:
* Subtracts a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
*
* @param {number} number - Should be an integer.
* @param {string} unit
* @throws
* @returns {Morebits.date}
*/
Line 1,545 ⟶ 1,672:
/**
* Formats the date into a string per the given format string.
* Replacement syntax is a subset of that in moment.js
*
* | Syntax | Output |
* |--------|--------|
* | H | Hours (24-hour) |
* | HH | Hours (24-hour, padded) |
* | h | Hours (12-hour) |
* | hh | Hours (12-hour, padded) |
* | A | AM or PM |
* | m | Minutes |
* | mm | Minutes (padded) |
* | s | Seconds |
* | ss | Seconds (padded) |
* | d | Day number of the week (Sun=0) |
* | ddd | Abbreviated day name |
* | dddd | Full day name |
* | D | Date |
* | DD | Date (padded) |
* | M | Month number (0-indexed) |
* | MM | Month number (0-indexed, padded) |
* | MMM | Abbreviated month name |
* | MMMM | Full month name |
* | Y | Year |
* | YY | Final two digits of year (20 for 2020, 42 for 1942) |
* | YYYY | Year (same as `Y`) |
*
* @param {string} formatstr - Format the date into a string, using
* the replacement syntax. Use `[` and `]` to escape items. If not
* provided, will return the ISO-8601-formatted string.
* @param {(string|number)} [zone=system] - `system` (for browser-default time zone),
* `utc`, or specify a time zone as number of minutes relative to UTC.
* @returns {string}
*/
Line 1,602 ⟶ 1,756:
/**
* Gives a readable relative time string such as "Yesterday at 6:43 PM" or "Last Thursday at 11:45 AM".
* Similar to `calendar` in moment.js, but with time zone support.
*
* @param {(string|number)} [zone=system] - 'system' (for browser-default time zone),
* 'utc' (for UTC), or specify a time zone as number of minutes past UTC.
* @returns {string}
*/
Line 1,629 ⟶ 1,784:
/**
*
* as `==December 2019==` or `=== Jan 2018 ===`.
*
* @returns {RegExp}
*/
monthHeaderRegex: function() {
Line 1,638 ⟶ 1,795:
/**
* Creates a wikitext section header with the month and year.
*
* @param {number} [level=2] - Header level. Pass 0 for just the text
* with no wikitext markers (==).
* @returns {string}
*/
Line 1,658 ⟶ 1,816:
}
};
// Allow native Date.prototype methods to be used on Morebits.date objects
Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
Morebits.date.prototype[func] = function() {
return this._d[func].apply(this._d, Array.prototype.slice.call(arguments));
};
});
/* **************** Morebits.wiki **************** */
/**
* Various objects for wiki editing and API access, including {@link
* Morebits.wiki.api} and {@link Morebits.wiki.page}.
*
* @namespace Morebits.wiki
* @memberof Morebits
*/
Morebits.wiki = {};
/**
* @deprecated in favor of Morebits.isPageRedirect
* @memberof Morebits.wiki
* @returns {boolean}
*/
Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() {
console.warn('NOTE: Morebits.wiki.isPageRedirect has been deprecated, use Morebits.isPageRedirect instead.'); // eslint-disable-line no-console
Line 1,674 ⟶ 1,847:
/* **************** Morebits.wiki.actionCompleted **************** */
/**
*
* @type {number}
*/
Morebits.wiki.numberOfActionsLeft = 0;
/**
* @memberof Morebits.wiki
* @type {number}
*/
Morebits.wiki.nbrOfCheckpointsLeft = 0;
/**
* Display message and/or redirect to page upon completion of tasks.
*
* Every call to Morebits.wiki.api.post() results in the dispatch of an
* asynchronous callback. Each callback can in turn make an additional call to
* Morebits.wiki.api.post() to continue a processing sequence. At the
* conclusion of the final callback of a processing sequence, it is not
* possible to simply return to the original caller because there is no call
* stack leading back to the original context. Instead,
* Morebits.wiki.actionCompleted.event() is called to display the result to
* the user and to perform an optional page redirect.
*
* The determination of when to call Morebits.wiki.actionCompleted.event() is
* managed through the globals Morebits.wiki.numberOfActionsLeft and
* Morebits.wiki.nbrOfCheckpointsLeft. Morebits.wiki.numberOfActionsLeft is
* incremented at the start of every Morebits.wiki.api call and decremented
* after the completion of a callback function. If a callback function does
* not create a new Morebits.wiki.api object before exiting, it is the final
* step in the processing chain and Morebits.wiki.actionCompleted.event() will
* then be called.
*
* Optionally, callers may use Morebits.wiki.addCheckpoint() to indicate that
* processing is not complete upon the conclusion of the final callback
* function. This is used for batch operations. The end of a batch is
* signaled by calling Morebits.wiki.removeCheckpoint().
*
* @memberof Morebits.wiki
*/
Morebits.wiki.actionCompleted = function(self) {
if (--Morebits.wiki.numberOfActionsLeft <= 0 && Morebits.wiki.nbrOfCheckpointsLeft <= 0) {
Line 1,713 ⟶ 1,894:
// Change per action wanted
/** @memberof Morebits.wiki */
Morebits.wiki.actionCompleted.event = function() {
if (Morebits.wiki.actionCompleted.notice) {
Line 1,731 ⟶ 1,913:
};
/** @memberof Morebits.wiki */
Morebits.wiki.actionCompleted.timeOut = typeof window.wpActionCompletedTimeOut === 'undefined' ? 5000 : window.wpActionCompletedTimeOut;
/** @memberof Morebits.wiki */
Morebits.wiki.actionCompleted.redirect = null;
/** @memberof Morebits.wiki */
Morebits.wiki.actionCompleted.notice = null;
/** @memberof Morebits.wiki */
Morebits.wiki.addCheckpoint = function() {
++Morebits.wiki.nbrOfCheckpointsLeft;
};
/** @memberof Morebits.wiki */
Morebits.wiki.removeCheckpoint = function() {
if (--Morebits.wiki.nbrOfCheckpointsLeft <= 0 && Morebits.wiki.numberOfActionsLeft <= 0) {
Line 1,745 ⟶ 1,932:
};
/* **************** Morebits.wiki.api **************** */
/**
* An easy way to talk to the MediaWiki API.
* In new code, the use of the last 3 parameters should be avoided, instead
* use {@link Morebits.wiki.api#setStatusElement|setStatusElement()} to bind
* the status element (if needed) and use `.then()` or `.catch()` on the
* promise returned by `post()`, rather than specify the `onSuccess` or
* `onFailure` callbacks.
*
* @memberof Morebits.wiki
* @class
* @param {
* @param {
* @param {
* @param {
* @param {Function} [onError] - The function to call if an error occurs.
*/
Morebits.wiki.api = function(currentAction, query, onSuccess, statusElement, onError) {
Line 1,803 ⟶ 1,992:
/**
* Keep track of parent object for callbacks.
*
* @param {*} parent
setParent: function(parent) {
this.parent = parent;
Line 1,817 ⟶ 2,007:
/**
*
*
* @param {object} callerAjaxParameters - Do not specify a parameter unless you really
* really want to give jQuery some extra parameters.
* @returns {promise} - */
post: function(callerAjaxParameters) {
Line 1,937 ⟶ 2,128:
};
/**
* Custom user agent header, used by WMF for server-side logging. Set via
* {@link Morebits.wiki.api.setApiUserAgent|setApiUserAgent}.
*
* @see {@link https://lists.wikimedia.org/pipermail/mediawiki-api-announce/2014-November/000075.html}
* for original announcement.
*
* @memberof Morebits.wiki.api
* @type {string}
*/
var morebitsWikiApiUserAgent = 'morebits.js ([[w:WT:TW]])';
/**
* Sets the custom user agent header.
*
* @memberof Morebits.wiki.api
* @param {string} [ua] - User agent.
*/
Morebits.wiki.api.setApiUserAgent = function(ua) {
Line 1,949 ⟶ 2,150:
};
/**
* Change/revision tag applied to Morebits actions when no other tags are specified.
* Defaults to unused per {@link https://en.wikipedia.org/w/index.php?oldid=970618849#Adding_tags_to_Twinkle_edits_and_actions|consensus}.
*
* @constant
* @memberof Morebits.wiki.api
* @type {string}
*/
var morebitsWikiChangeTag = '';
/**
* Get a new CSRF token on encountering token errors.
*
* @memberof Morebits.wiki.api
* @returns {string} MediaWiki CSRF token.
*/
Morebits.wiki.api.getToken = function() {
var tokenApi = new Morebits.wiki.api('Getting token', {
Line 1,967 ⟶ 2,181:
/* **************** Morebits.wiki.page **************** */
/**
* Use the MediaWiki API to load a page and optionally edit it, move it, etc.
*
* Callers are not permitted to directly access the properties of this class!
* All property access is through the appropriate get___() or set___() method.
*
* Callers should set {@link Morebits.wiki.actionCompleted.notice} and {@link Morebits.wiki.actionCompleted.redirect}
* before the first call to {@link Morebits.wiki.page.load()}.
*
* Each of the callback functions takes one parameter, which is a
Line 1,982 ⟶ 2,196:
*
*
* Call sequence for common operations (optional final user callbacks not shown):
*
* - Edit current contents of a page (no edit conflict):
* `.load(userTextEditCallback) -> ctx.loadApi.post() ->
* ctx.loadApi.post.success() -> ctx.fnLoadSuccess() -> userTextEditCallback() ->
* .save() -> ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()`
*
* - Edit current contents of a page (with edit conflict):
* `.load(userTextEditCallback) -> ctx.loadApi.post() ->
* ctx.loadApi.post.success() -> ctx.fnLoadSuccess() -> userTextEditCallback() ->
* .save() -> ctx.saveApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnSaveError() -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()`
*
* - Append to a page (similar for prepend and newSection):
* `.append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() -> ctx.saveApi.post() ->
* ctx.loadApi.post.success() -> ctx.fnSaveSuccess()`
*
* Notes:
* 1. All functions following Morebits.wiki.api.post() are invoked asynchronously from the jQuery AJAX library.
* 2. The sequence for append/prepend/newSection could be slightly shortened,
* but it would require significant duplication of code for little benefit.
*
*
* @memberof Morebits.wiki
* @class
* @param {string} pageName - The name of the page, prefixed by the namespace (if any).
* For the current page, use `mw.config.get('wgPageName')`.
* @param {string} [currentAction] - A string describing the action about to be undertaken.
*/
Morebits.wiki.page = function(pageName, currentAction) {
Line 2,106 ⟶ 2,235:
/**
* Private context variables.
*
* This context is not visible to the outside, thus all the data here
* must be accessed via getter and setter functions.
*
* @private
*/
var ctx = {
Line 2,216 ⟶ 2,347:
/**
* Loads the text for the page.
*
* @param {Function}
* @param {Function} [onFailure] - Callback function which is called when the load fails.
*/
this.load = function(onSuccess, onFailure) {
Line 2,266 ⟶ 2,398:
/**
* Saves the text for the page to Wikipedia.
* Must be preceded by successfully calling `load()`.
*
* Warning: Calling `save()` can result in additional calls to the
* previous `load()` callbacks to recover from edit conflicts! In this
* case, callers must make the same edit to the new pageText and
* reinvoke `save()`. This behavior can be disabled with
* `setMaxConflictRetries(0)`.
*
* @param {Function} [onSuccess] - Callback function which is called when the save has succeeded.
* @param {Function} [onFailure] - Callback function which is called when the save fails.
*/
this.save = function(onSuccess, onFailure) {
Line 2,401 ⟶ 2,533:
/**
* Adds the text provided via `setAppendText()` to the end of the page.
* Does not require calling `load()` first.
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function which is called when the method fails.
*/
this.append = function(onSuccess, onFailure) {
Line 2,419 ⟶ 2,552:
/**
* Adds the text provided via `setPrependText()` to the start of the page.
* Does not require calling `load()` first.
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function which is called when the method fails.
*/
this.prepend = function(onSuccess, onFailure) {
Line 2,437 ⟶ 2,571:
/**
* Creates a new section with the text provided by `setNewSectionText()`
* and section title from `setNewSectionTitle()`.
* If `editSummary` is provided, that will be used instead of the
* autogenerated "->Title (new section" edit summary.
* Does not require calling `load()` first.
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function which is called when the method fails.
*/
this.newSection = function(onSuccess, onFailure) {
Line 2,457 ⟶ 2,592:
};
/** @returns {string}
this.getPageName = function() {
return ctx.pageName;
};
/** @returns {string}
this.getPageText = function() {
return ctx.pageText;
};
/** @param {string} pageText -
this.setPageText = function(pageText) {
ctx.editMode = 'all';
Line 2,473 ⟶ 2,608:
};
/** @param {string} appendText -
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
Line 2,479 ⟶ 2,614:
};
/** @param {string} prependText -
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
Line 2,485 ⟶ 2,620:
};
/** @param {string} newSectionText -
this.setNewSectionText = function(newSectionText) {
ctx.editMode = 'new';
Line 2,492 ⟶ 2,627:
/**
* @param {string} newSectionTitle -
* If missing, `ctx.editSummary` will be used. Issues may occur if a substituted template is used.
*/
this.setNewSectionTitle = function(newSectionTitle) {
Line 2,504 ⟶ 2,639:
// Edit-related setter methods:
/**
*
* Unnecessary if editMode is 'new' and newSectionTitle is provided.
*
* @param {string} summary
*/
this.setEditSummary = function(summary) {
Line 2,512 ⟶ 2,649:
/**
* Set any custom tag(s) to be applied to the API action.
* A number of actions don't support it, most notably watch, review,
* and stabilize ({@link https://phabricator.wikimedia.org/T247721|T247721}), and
* pagetriageaction ({@link https://phabricator.wikimedia.org/T252980|T252980}).
*
* @param {string|string[]} tags - String or array of tag(s).
*/
this.setChangeTags = function(tags) {
Line 2,524 ⟶ 2,662:
/**
* @param {string} [createOption=null] -
* -
* -
* error if
* -
* -
* in the
*
*/
Line 2,537 ⟶ 2,675:
};
/** @param {boolean} minorEdit -
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
/** @param {boolean} botEdit -
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
Line 2,548 ⟶ 2,686:
/**
* @param {number} pageSection -
* If specified as `null`, the entire page will be retrieved.
*/
Line 2,556 ⟶ 2,694:
/**
* @param {number} maxConflictRetries -
* loss of token. Default: 2.
*/
this.setMaxConflictRetries = function(maxConflictRetries) {
Line 2,564 ⟶ 2,702:
/**
* @param {number} maxRetries -
* loss of token. Default: 2.
*/
this.setMaxRetries = function(maxRetries) {
Line 2,572 ⟶ 2,710:
/**
* @param {boolean} [watchlistOption=false] -
* -
* -
*/
this.setWatchlist = function(watchlistOption) {
Line 2,585 ⟶ 2,723:
/**
* @param {boolean} [watchlistOption=false] -
* -
*
* -
*
*
*
* the page from
*
*
*
*
*
*
*/
this.setWatchlistFromPreferences = function(watchlistOption) {
Line 2,609 ⟶ 2,747:
/**
* @param {boolean} [followRedirect=false] -
* -
*
*
* -
* @param {boolean} [followCrossNsRedirect=true] - Not applicable if `followRedirect` is not set true.
* - `true`: (default) follow redirect even if it is a cross-namespace redirect
* - `false`: don't follow redirect if it is cross-namespace, edit the redirect itself.
*/
this.setFollowRedirect = function(followRedirect, followCrossNsRedirect) {
Line 2,631 ⟶ 2,767:
// lookup-creation setter function
/**
* @param {boolean} flag -
* the first non-redirect version of the page is retrieved.
*
* Warning:
* 1. If there are no revisions among the first 50 that are
* non-redirects, or if there are less 50 revisions and all are *
* 2. Revisions that the user is not privileged to access
* (revdeled/suppressed) will be treated as non-redirects. * 3. Must not be used when the page has a non-wikitext contentmodel
*
*/
this.setLookupNonRedirectCreator = function(flag) {
Line 2,669 ⟶ 2,806:
// Protect-related setter functions
/**
* @param {string} level - The right required for the specific action
* e.g. autoconfirmed, sysop, templateeditor, extendedconfirmed
* (enWiki-only). * @param {string} [expiry=infinity]
*/
Line 2,698 ⟶ 2,836:
};
/** @returns {string}
this.getCurrentID = function() {
return ctx.revertCurID;
};
/** @returns {string}
this.getRevisionUser = function() {
return ctx.revertUser;
Line 2,716 ⟶ 2,854:
/**
*
*
*
* allow a caller to pass the proper context into its callback
* function. Callers must ensure that any changes to the
* callbackParameters object within a `load()` callback still permit a
* proper re-entry into the `load()` callback if an edit conflict is
* detected upon calling `save()`. *
* @param {object} callbackParameters
*/
this.setCallbackParameters = function(callbackParameters) {
Line 2,729 ⟶ 2,870:
/**
* @returns
*/
this.getCallbackParameters = function() {
Line 2,736 ⟶ 2,877:
/**
* @returns {Morebits.status} Status element created by the constructor.
*/
this.getStatusElement = function() {
Line 2,743 ⟶ 2,884:
/**
* @param {string} level - The right required for edits not to require
* review. Possible options: none, autoconfirmed, review (not on enWiki).
* @param {string} [expiry=infinity]
Line 2,752 ⟶ 2,893:
/**
* @returns {boolean}
*/
this.exists = function() {
Line 2,767 ⟶ 2,908:
/**
* @returns {string} ISO 8601 timestamp at which the page was last loaded.
*/
this.getLoadTime = function() {
Line 2,774 ⟶ 2,915:
/**
* @returns {string}
*/
this.getCreator = function() {
Line 2,781 ⟶ 2,922:
/**
* @returns {string}
*/
this.getCreationTimestamp = function() {
Line 2,794 ⟶ 2,935:
/**
* Retrieves the username of the user who created the page as well as
* the timestamp of creation. The username can be retrieved using the
* `getCreator()` function; the timestamp can be retrieved using the
* `getCreationTimestamp()` function.
* Prior to June 2019 known as `lookupCreator()`.
*
* @param {Function} onSuccess - Callback function to be called when
* the username and timestamp are found within the callback.
*/
this.lookupCreation = function(onSuccess) {
Line 2,837 ⟶ 2,979:
/**
* Reverts a page to `revertOldID` set by `setOldID`.
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.revert = function(onSuccess, onFailure) {
Line 2,856 ⟶ 2,999:
/**
* Moves a page to another title.
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.move = function(onSuccess, onFailure) {
Line 2,886 ⟶ 3,030:
/**
* Marks the page as patrolled, using `rcid` (if available) or `revid`.
*
* Patrolling as such doesn't need to rely on loading the page in
* question; simply passing a revid to the API is sufficient, so in
* those cases just using {@link Morebits.wiki.api} is probably preferable.
*
* No error handling since we don't actually care about the errors.
*/
this.patrol = function() {
Line 2,924 ⟶ 3,068:
/**
* Marks the page as reviewed by the PageTriage extension.
*
* Will, by it's nature, mark as patrolled as well. Falls back to
Line 2,933 ⟶ 3,074:
*
* Doesn't inherently rely on loading the page in question; simply
* passing a `pageid` to the API is sufficient, so in those cases just
* using {@link Morebits.wiki.api} is probably preferable.
*
* Will first check if the page is queued via {@link
* Morebits.wiki.page~fnProcessTriageList|fnProcessTriageList}.
*
* No error handling since we don't actually care about the errors.
*
* @see {@link https://www.mediawiki.org/wiki/Extension:PageTriage} Referred to as "review" on-wiki.
*/
this.triage = function() {
Line 2,965 ⟶ 3,109:
// |delete| is a reserved word in some flavours of JS
/**
* Deletes a page (for admins only).
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.deletePage = function(onSuccess, onFailure) {
Line 2,989 ⟶ 3,134:
/**
* Undeletes a page (for admins only).
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.undeletePage = function(onSuccess, onFailure) {
Line 3,013 ⟶ 3,159:
/**
* Protects a page (for admins only).
*
* @param {Function} [
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.protect = function(onSuccess, onFailure) {
Line 3,042 ⟶ 3,189:
/**
* Apply FlaggedRevs protection
*
* i.e. where FlaggedRevs settings appear on the
*
* @see {@link https://www.mediawiki.org/wiki/Extension:FlaggedRevs}
* Referred to as "pending changes" on-wiki.
*
* @param {Function} [onSuccess]
* @param {Function} [onFailure]
*/
this.stabilize = function(onSuccess, onFailure) {
Line 3,082 ⟶ 3,233:
* HTML, or whether we need to ask the server for more info (e.g. protection expiry).
*
* Currently used for `append`, `prepend`, `newSection`, `move
* `stabilize`, `deletePage`, and `undeletePage`. Can't use for
* `protect` since it always needs to request protection status.
*
* @param {string} [action=edit] - The action being undertaken, e.g.
* "edit" or "delete". In practice, only "edit" or "notedit" matters.
* @returns {boolean}
Line 3,122 ⟶ 3,273:
/**
* When functions can't use
* Morebits.wiki.page~fnCanUseMwUserToken|fnCanUseMwUserToken} or
* require checking protection, maintain the query in one place. Used
* for {@link Morebits.wiki.page#deletePage|delete}, {@link
* Morebits.wiki.page#undeletePage|undelete}, {@link
* Morebits.wiki.page#protect|protect}, {@link
* Morebits.wiki.page#stabilize|stabilize}, and {@link
* Morebits.wiki.page#move|move} (basically, just not {@link
* Morebits.wiki.page#load|load}).
*
* @param {string} action - The action being undertaken, e.g. "edit" or
* "delete".
* @returns {object} Appropriate token query.
*/
var fnNeedTokenInfoQuery = function(action) {
Line 3,444 ⟶ 3,602:
};
/**
* protect, stabilize
*
* @param {string} action - The action being checked.
* @param {string} onFailure - Failure callback.
* @returns {boolean}
*/
var fnPreflightChecks = function(action, onFailure) {
// if a non-admin tries to do this, don't bother
Line 3,462 ⟶ 3,626:
};
/**
* Common checks for fnProcess functions (`fnProcessDelete`, `fnProcessMove`, etc.
* Used for move, undelete, delete, protect, stabilize.
*
* @param {string} action - The action being checked.
* @param {string} onFailure - Failure callback.
* @param {string} xml - The response document from the API call.
* @returns {boolean}
*/
var fnProcessChecks = function(action, onFailure, xml) {
var missing = $(xml).find('page').attr('missing') === '';
Line 3,903 ⟶ 4,074:
/* **************** Morebits.wiki.preview **************** */
/**
* Use the API to parse a fragment of wikitext and render it as HTML.
*
* The suggested implementation pattern (in {@link Morebits.simpleWindow}
* {@link Morebits.quickForm} situations) is to construct a *
* bind the object to an arbitrary property of the form (e.g. |previewer|).
* For an example, see twinklewarn.js. *
* @memberof Morebits.wiki
* @class
* @param {HTMLElement} previewbox - The element that will contain the rendered HTML,
* usually a <div> element.
*/
Morebits.wiki.preview = function(previewbox) {
Line 3,926 ⟶ 4,096:
* Displays the preview box, and begins an asynchronous attempt
* to render the specified wikitext.
*
* @param {string}
* @param {string} [
* @param {string} [sectionTitle] - If provided, render the text as a new section using this as the title.
*/
this.beginRender = function(wikitext, pageTitle, sectionTitle) {
Line 3,971 ⟶ 4,142:
/* **************** Morebits.wikitext **************** */
/**
* Wikitext manipulation.
*
* @namespace Morebits.wikitext
* @memberof Morebits
*/
Morebits.wikitext = {};
/**
* Get the value of every parameter found in the wikitext of a given template.
*
* @memberof Morebits.wikitext
* @param {string} text - Wikitext containing a template.
* @param {number} [start=0] - Index noting where in the text the template begins.
* @returns {object} {name: templateName, parameters: {key: value}}.FIXME TODO
*/
Morebits.wikitext.parseTemplate = function(text, start) {
Line 4,078 ⟶ 4,253:
/**
* Adjust and manipulate the wikitext of a page.
*
* @class
* @memberof Morebits.wikitext
* @param {string} text - Wikitext to be manipulated.
*/
Morebits.wikitext.page = function mediawikiPage(text) {
Line 4,090 ⟶ 4,268:
/**
* Removes links to `link_target` from the page text.
* Files and Categories become links with a leading colon
* (e.g. [[:File:Test.png]]); otherwise, allow for an optional leading
* colon (e.g. [[:User:Test]]).
*
* @param {string} link_target
*
Line 4,098 ⟶ 4,280:
var link_re_string = '[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + Morebits.string.escapeRegExp(link_target.substr(1));
var special_ns_re = /^(?:[Ff]ile|[Ii]mage|[Cc]ategory):/;
var colon = special_ns_re.test(link_target) ? ':' : ':?';
Line 4,110 ⟶ 4,290:
/**
* Comments out images from page text
* If used as a template argument (not necessarily with `File:` prefix), the template parameter is commented out.
*
* @param {string} image - Image name without `File:` prefix.
* @param {string} reason - Reason to be included in comment, alongside the commented-out image. *
* @returns {Morebits.wikitext.page}
Line 4,157 ⟶ 4,338:
/**
* Converts
*
* @param {string}
* @param {string} data - The display options.
*
* @returns {Morebits.wikitext.page}
Line 4,187 ⟶ 4,369:
/**
* Removes transclusions of template from page text.
*
* @param {string} template - Page name whose transclusions are to be removed,
* include namespace prefix only if not in template namespace.
*
* @returns {Morebits.wikitext.page}
Line 4,209 ⟶ 4,392:
* Smartly insert a tag atop page text but after specified templates,
* such as hatnotes, short description, or deletion and protection templates.
* Notably, does *not* insert a newline after the tag.
*
* @param {string} tag - The tag to be inserted.
* @param {string|string[]} regex - Templates after which to insert tag,
* given as either as a (regex-valid) string or an array to be joined by pipes.
* @param {string} [flags=i] - Regex flags to apply.
* @param {string|string[]} preRegex - Optional regex string or array to match
* before any template matches (i.e. before `{{`), such as html comments.
* @returns {Morebits.wikitext.page}
* @throws If no tag or regex are provided.
*/
insertAfterTemplates: function(tag, regex, flags, preRegex) {
Line 4,272 ⟶ 4,455:
},
/**
* Get the manipulated wikitext.
*
* @returns {string}
*/
getText: function() {
return this.text;
Line 4,278 ⟶ 4,465:
};
/* *********** Morebits.userspaceLogger ************ */
/**
* Handles logging actions to a userspace log.
* Used in CSD, PROD, and XFD.
*
* @memberof Morebits
* @class
* @param {string} logPageName - Title of the subpage of the current user's log.
*/
Morebits.userspaceLogger = function(logPageName) {
if (!logPageName) {
throw new Error('no log page name specified');
}
/**
* The text to prefix the log with upon creation, defaults to empty.
*
* @type {string}
*/
this.initialText = '';
/**
* The header level to use for months, defaults to 3 (`===`).
*
* @type {number}
*/
this.headerLevel = 3;
this.changeTags = '';
/**
* Log the entry.
*
* @param {string} logText - Doesn't include leading `#` or `*`.
* @param {string} summaryText - Edit summary.
*/
this.log = function(logText, summaryText) {
if (!logText) {
Line 4,317 ⟶ 4,524:
};
/* **************** Morebits.status **************** */
/**
* Create and show status messages of varying urgency.
* {@link Morebits.status.init|Morebits.status.init()} must be called before
* any status object is created, otherwise those statuses won't be visible.
*
* @memberof Morebits
* @class
* @param {string} text - Text before the the colon `:`.
* @param {string} stat - Text after the colon `:`.
* @param {string} [type=status] - Determine the font color of the status
* line, allowable values are: `status` (blue), `info` (green), `warn` (red),
* or `error` (bold red).
*/
Line 4,343 ⟶ 4,551:
/**
* Specify an area for status message elements to be added to.
*
* @memberof Morebits.status
* @param {HTMLElement} root - Usually a div element.
* @throws If `root` is not an `HTMLElement`.
*/
Morebits.status.init = function(root) {
Line 4,359 ⟶ 4,570:
Morebits.status.root = null;
/**
* @memberof Morebits.status
* @param {Function} handler - Function to execute on error.
* @throws When `handler` is not a function.
*/
Morebits.status.onError = function(handler) {
if (typeof handler === 'function') {
Line 4,377 ⟶ 4,592:
linked: false,
/** Add the status element node to the DOM. */
link: function() {
if (!this.linked && Morebits.status.root) {
Line 4,385 ⟶ 4,600:
},
/** Remove the status element node from the DOM. */
unlink: function() {
if (this.linked) {
Line 4,394 ⟶ 4,609:
/**
* Create a document fragment with the status text.
*
* @param {(string|Element|Array)} obj
* @returns {DocumentFragment}
Line 4,416 ⟶ 4,632:
/**
* Update the status.
*
* @param {
* @param {string} type - 'status' (blue), 'info' (green), 'warn'
* (red), or 'error' (bold red). FIXME TODO possible options
*/
update: function(status, type) {
Line 4,440 ⟶ 4,658:
},
/** Produce the html for first part of the status message. */
generate: function() {
this.node = document.createElement('div');
Line 4,449 ⟶ 4,667:
},
/** Complete the html, for the second part of the status message. */
render: function() {
this.node.className = 'morebits_status_' + this.type;
Line 4,471 ⟶ 4,689:
}
};
Morebits.status.info = function(text, status) {
return new Morebits.status(text, status, 'info');
Line 4,487 ⟶ 4,704:
* For the action complete message at the end, create a status line without
* a colon separator.
*
* @memberof Morebits.status
* @param {string} text
*/
Morebits.status.actionCompleted = function(text) {
Line 4,499 ⟶ 4,718:
/**
* Display the user's rationale, comments, etc.
* so that they may re-use it.
*
* @memberof Morebits.status
* @param {string} comments
* @param {string} message
Line 4,519 ⟶ 4,740:
/**
* Simple helper function to create a simple node.
*
* @param {string} type -
* @param {string}
* @param {string} [color] -
* @returns {HTMLElement}
*/
Line 4,538 ⟶ 4,759:
/**
* Add shift-click support for checkboxes. The wikibits version
* (`window.addCheckboxClickHandlers`) has some restrictions, and doesn't work
* with checkboxes inside a sortable table, so let's build our own.
*
* @param jQuerySelector
* @param jQueryContext
*/
Morebits.checkboxShiftClickSupport = function (jQuerySelector, jQueryContext) {
Line 4,594 ⟶ 4,817:
/
/**
* Iterates over a group of pages (or arbitrary objects) and executes a worker function
* for each.
*
* `setPageList(wikitext)`: Sets the list of pages to work on. It should be an
* array of page names (strings).
*
* `setOption(optionName, optionValue)`: Sets a known option:
* - `chunkSize` (integer): The size of chunks to break the array into (default
* 50). Setting this to a small value (<5) can cause problems.
* - `preserveIndividualStatusLines` (boolean): Keep each page's status element
* visible when worker is complete? See note below.
*
* `run(worker, postFinish)`: Runs the callback `worker` for each page in the
* list. The callback must call `workerSuccess` when succeeding, or
* `workerFailure` when failing. If using {@link Morebits.wiki.api} or {@link
* Morebits.wiki.page}, this is easily done by passing these two functions as
* parameters to the methods on those objects: for instance,
* `page.save(batchOp.workerSuccess, batchOp.workerFailure)`. Make sure the
* methods are called directly if special success/failure cases arise. If you
* omit to call these methods, the batch operation will stall after the first
* chunk! Also ensure that either workerSuccess or workerFailure is called no
* more than once. The second callback `postFinish` is executed when the
* entire batch has been processed.
*
* If using `preserveIndividualStatusLines`, you should try to ensure that the
* `workerSuccess` callback has access to the page title. This is no problem for
* {@link Morebits.wiki.page} objects. But when using the API, please set the
* |pageName| property on the {@link Morebits.wiki.api} object.
*
* There are sample batchOperation implementations using Morebits.wiki.page in
* twinklebatchdelete.js, twinklebatchundelete.js, and twinklebatchprotect.js.
*
* @memberof Morebits
* @class
* @param {string} [currentAction]
*/
Line 4,660 ⟶ 4,882:
/**
* Sets the list of pages to work on.
*
* @param {Array} pageList - Array of objects over which you wish to execute the worker function
* This is usually the list of page names (strings).
*/
Line 4,669 ⟶ 4,892:
/**
* Sets a known option
*
* @param {string} optionName - Name of the option:
* - chunkSize (integer): The size of chunks to break the array into
* (default 50). Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines
* element visible when worker is complete? * @param {number|boolean} optionValue - Value to which the option is
* to be set. Should be an integer for chunkSize and a boolean for
* preserveIndividualStatusLines.
*/
this.setOption = function(optionName, optionValue) {
Line 4,683 ⟶ 4,910:
* Runs the first callback for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* Runs the optional second callback when the whole batch has been processed
*
* @param {Function} worker
* @param {Function} [postFinish]
Line 4,722 ⟶ 4,950:
/**
* To be called by worker before it terminates succesfully.
*
* @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg -
* This should be the `Morebits.wiki.page` or `Morebits.wiki.api` object used by worker
* (for the adjustment of status lines emitted by them).
* If no Morebits.wiki.* object is used (
* `preserveIndividualStatusLines` option is on, give the page name (string) as argument.
*/
Line 4,821 ⟶ 5,050:
};
/**
* Given a set of asynchronous functions to run along with their dependencies,
*
*
*
*
*
*
* @memberof Morebits
* @class
*/
Morebits.taskManager = function() {
Line 4,840 ⟶ 5,071:
* a promise. The function will get the values resolved by the dependency functions
* as arguments.
*
* @param {
* @param {Function[]} deps - Its dependencies.
*/
this.add = function(func, deps) {
Line 4,852 ⟶ 5,084:
/**
* Run all the tasks. Multiple tasks may be run at once.
*
* @returns {promise} - A jQuery promise object that is resolved or rejected with the api object.
*/
this.execute = function() {
Line 4,871 ⟶ 5,105:
/**
* A simple draggable window, now a wrapper for jQuery UI's dialog feature.
*
* @memberof Morebits
* @class
* @requires * @param {number} width
* @param {number} height - The maximum allowable height for the content area.
*/
Morebits.simpleWindow = function SimpleWindow(width, height) {
Line 4,947 ⟶ 5,178:
/**
* Focuses the dialog. This might work, or on the contrary, it might not.
*
* @returns {Morebits.simpleWindow}
*/
Line 4,957 ⟶ 5,189:
* Closes the dialog. If this is set as an event handler, it will stop the event
* from doing anything more.
*
* @param {event} [event]
* @returns {Morebits.simpleWindow}
*/
Line 4,970 ⟶ 5,204:
* Shows the dialog. Calling display() on a dialog that has previously been closed
* might work, but it is not guaranteed.
*
* @returns {Morebits.simpleWindow}
*/
Line 4,993 ⟶ 5,228:
/**
* Sets the dialog title.
*
* @param {string} title
* @returns {Morebits.simpleWindow}
Line 5,004 ⟶ 5,240:
* Sets the script name, appearing as a prefix to the title to help users determine which
* user script is producing which dialog. For instance, Twinkle modules set this to "Twinkle".
*
* @param {string} name
* @returns {Morebits.simpleWindow}
Line 5,014 ⟶ 5,251:
/**
* Sets the dialog width.
*
* @param {number} width
* @returns {Morebits.simpleWindow}
Line 5,025 ⟶ 5,263:
* Sets the dialog's maximum height. The dialog will auto-size to fit its contents,
* but the content area will grow no larger than the height given here.
*
* @param {number} height
* @returns {Morebits.simpleWindow}
Line 5,047 ⟶ 5,286:
/**
* Sets the content of the dialog to the given element node, usually from rendering
* a {@link Morebits.quickForm}.
* Re-enumerates the footer buttons, but leaves the footer links as they are.
* Be sure to call this at least once before the dialog is displayed...
*
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
Line 5,061 ⟶ 5,301:
/**
* Adds the given element node to the dialog content.
*
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
Line 5,090 ⟶ 5,331:
/**
* Removes all contents from the dialog, barring any footer links.
*
* @returns {Morebits.simpleWindow}
*/
Line 5,109 ⟶ 5,351:
* For example, Twinkle's CSD module adds a link to the CSD policy page,
* as well as a link to Twinkle's documentation.
*
* @param {string}
* @param {string} wikiPage - Link target.
* @param {boolean} [prep=false] - Set true to prepend rather than append. * @returns {Morebits.simpleWindow}
*/
Line 5,140 ⟶ 5,383:
/**
*
*
* must be used (if necessary) before calling display().
*
* @param {boolean} [modal=false] - If set to true, other items on the
* page will be disabled, i.e., cannot be interacted with.
* @returns {Morebits.simpleWindow}
*/
Line 5,156 ⟶ 5,398:
/**
* Enables or disables all footer buttons on all {@link Morebits.
* This should be called with `false` when the button(s) become irrelevant (e.g. just before
* {@link Morebits.status.init} is called).
* This is not an instance method so that consumers don't have to keep a reference to the
* original `Morebits.simpleWindow` object sitting around somewhere. Anyway, most of the time
* there will only be one `Morebits.simpleWindow` open, so this shouldn't matter.
*
* @memberof Morebits.simpleWindow
* @param {boolean} enabled
*/
Line 5,174 ⟶ 5,418:
/**
* If this script is being executed outside a ResourceLoader context, we add some
* global assignments for legacy scripts, hopefully these can be removed down the line.
*
* IMPORTANT NOTE:
|