MediaWiki:Gadget-morebits.js: Difference between revisions

Content deleted Content added
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
Repo at c695df4: Improve and standardize JSDocs
Line 1:
// <nowiki>
/**
* morebits.js
* ===========
* A library full of lots of goodness for user scripts on MediaWiki wikis, including Wikipedia.
*
* The highlights include:
* - -{@link Morebits.quickForm classwiki.api} - generatesmake quickcalls HTMLto formsthe on theMediaWiki flyAPI
* - {@link Morebits.wiki.api classpage} - makesmodify callspages toon the MediaWikiwiki API(edit, revert, delete, etc.)
* - -{@link Morebits.wiki.page classdate} - modifiesenhanced pagesdate onobject theprocessing, wikisort (edit,of revert,a delete,light etcmoment.)js
* - -{@link Morebits.wikitext classquickForm} - containsgenerate somequick utilitiesHTML forforms dealingon withthe wikitextfly
* - -{@link Morebits.status classsimpleWindow} - a rough-and-readywrapper statusfor messagejQuery displayer,UI usedDialog bywith thea Morebits.wikicustom look and extra classesfeatures
* - -{@link Morebits.simpleWindow classstatus} - a wrapperrough-and-ready forstatus jQuerymessage UIdisplayer, Dialogused withby athe custom look and extraMorebits.wiki featuresclasses
* - {@link Morebits.wikitext} - utilities for dealing with wikitext
* - {@link Morebits.string} - utilities for manipulating strings
* - {@link Morebits.array} - utilities for manipulating arrays
*
* Dependencies:
* - The whole thing relies on jQuery. But most wikis should provide this by default.
* - {@link Morebits.quickForm}, {@link Morebits.simpleWindow}, and {@link Morebits.status} rely on the "morebits.css" file for their styling.
* - {@link Morebits.simpleWindow} and {@link Morebits.quickForm} tooltips rely on jqueryjQuery UI Dialog (from ResourceLoader module name 'jquery.ui').
* - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition:
* - `* GadgetName[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui]|morebits.js|morebits.css|GadgetName.js`
* - Alternatively, you can configure morebits.js as a hidden gadget in MediaWiki:Gadgets-definition:
* - `* morebits[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui|hidden]|morebits.js|morebits.css`
* 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 [[Wikipedia talk:Twinkle]] on English Wikipedia [](http://en.wikipedia.org]/wiki/WT:TW).
* The latest development source is available at [{@link https://github.com/azatoth/twinkle/blob/master/morebits.js]|GitHub}.
*
* @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.
* **************** Morebits.userIsInGroup() ****************
*
* Simple helper function to see what groups a user might belong
* @param {string} group - ege.g. `sysop`, `extendedconfirmed`, etc.
* @returns {boolean}
*/
Line 47 ⟶ 50:
return mw.config.get('wgUserGroups').indexOf(group) !== -1;
};
/** Hardcodes whether the user is a sysop, used a lot.
// Used a lot
*
* @constant
* @type {boolean}
*/
Morebits.userIsSysop = Morebits.userIsInGroup('sysop');
 
 
 
/**
* **************** Morebits.sanitizeIPv6() ****************
* JavaScript translation of the MediaWiki core function IP::sanitizeIP() in
* includes/utils/IP.php.
* 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()`}
* @param {string} address - The IPv6 address
* function from the IPUtils library.
*
* @param {string} address - The IPv6 address.
* @returns {string}
*/
Line 103 ⟶ 108:
}
// Remove leading zeros from each bloc as needed
address =return address.replace(/(^|:)0+([0-9A-Fa-f]{1,4})/g, '$1$2');
};
 
/**
return address;
* 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
* **************** Morebits.quickForm ****************
* leading character's capitalization.
* Morebits.quickForm is a class for creation of simple and standard forms without much
* specific coding.
*
* @param {string} pageName - Page name without namespace.
* Index to Morebits.quickForm element types:
* @returns {string} - For a page name `Foo bar`, returns the string `[Ff]oo bar`.
*
* 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
*
* Global attributes: id, className, style, tooltip, extra, adminonly
*/
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.
* @constructor
*
* @param {event} event - Function to execute when form is submitted
* @namespace Morebits.quickForm
* @param {string} [eventType=submit] - Type of the event (default: submit)
* @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.
*
* @param {(Object|Morebits.quickForm.element)} data - a quickform element, or the object with which
* @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} - sameSame as what is passed to the function.
*/
Morebits.quickForm.prototype.append = function QuickFormAppend(data) {
Line 183 ⟶ 182:
 
/**
* Create a new element for the the form.
* @constructor
*
* @param {Object} data - Object representing the quickform element. See class documentation
* Index to Morebits.quickForm.element types:
* comment for available types and attributes for each.
* - 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.
*
* @param {Morebits.quickForm.element} data A quickForm element or the object required to
* create the@memberof Morebits.quickForm .element
* @returnsparam {Morebits.quickForm.element} Thedata same- A quickForm element passedor the object required into
* 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
* This should be called without parameters: `form.render()`.
*
* @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 jquery.uijQuery UI-based tooltip.
*
* @memberof Morebits.quickForm.element
* @requires jquery.ui
* @param {HTMLElement} node - theThe HTML element beside which a tooltip is to be generated.
* @param {Objectobject} data - tooltipTooltip-related configuration data.
*/
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 {Objectobject} withWith field names as keys, input data as values.
*/
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 - theThe name or id of the fields.
* @returns {HTMLElement[]} - arrayArray of matching form elements.
*/
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.
*
* @param {HTMLInputElement[]} elementArray - array of checkbox or radio elements
* @memberof Morebits.quickForm
* @param {string} value - value to search for
* @param {HTMLInputElement[]} elementArray - Array of checkbox or radio elements.
* @param {string} value - Value to search for.
* @returns {HTMLInputElement}
*/
Line 812 ⟶ 874:
 
/**
* Returns the <&lt;div> containing the form element, or the form element itself
* 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} trueTrue if succeeded, false if the label element is unavailable.
*/
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} trueTrue if succeeded, false if the label element is unavailable.
*/
Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) {
Line 906 ⟶ 980:
 
/**
* Shows or hides a form element plus its label and tooltip.
*
* @param {(HTMLElement|jQuery|string)} element HTML/jQuery element, or jQuery selector string
* @memberof Morebits.quickForm
* @param {boolean} [visibility] Skip this to toggle visibility
* @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 "question mark" icon (which displays the tooltip) next to a form element.
*
* @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:
 
/**
* ****************@external HTMLFormElement ****************
*/
 
/**
* Get checked items in the form.
* Returns an array containing the values of elements with the given name, that has it's
*
* checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select
* @function external:HTMLFormElement.getChecked
* options that have selected set to true. (don't try to mix selects with radio/checkboxes,
* @param {string} name - Find checked property of elements (i.e. a checkbox
* please)
* or a radiobutton) with the given name, or select options that have selected
* Type is optional and can specify if either radio or checkbox (for the event
* set to true (don't try to mix selects with radio/checkboxes).
* that both checkboxes and radiobuttons have the same name.
* @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.
* getUnchecked:
*
* Does the same as getChecked above, 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 Morebits.string.escapeRegExp or
*/
* mw.util.escapeRegExp
/**
* 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.
* **************** String; Morebits.string ****************
*
* @namespace Morebits.string
* @memberof Morebits
*/
 
Morebits.string = {
/**
// Helper functions to change case of a 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 {Stringstring[]}
* @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.
* the replacement string. Useful when the the replacement string is
* arbitrary, such as a username or freeform user input, and could
* and could contain dollar signs.
*
* @param {string} string - text in which to replace
* @param {string} string - Text in which to replace.
* @param {(string|RegExp)} pattern
* @param {string} replacement
Line 1,149 ⟶ 1,262:
 
/**
* Determine if the user input-provided expiration will be translated toconsidered an
* 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.
*
* @param {string} text - string to be escaped
* @returnsparam {string} text - theString escapedto textbe escaped.
* @returns {string} - The escaped text.
*/
escapeRegExp: function(text) {
Line 1,172 ⟶ 1,289:
 
/**
* Helper functions to manipulate arrays.
* **************** Morebits.array ****************
*
* @namespace Morebits.array
* @memberof Morebits
*/
 
Morebits.array = {
/**
* Remove duplicated items from an array.
* @returns {Array} a copy of the array with duplicates removed
*
* @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.
* @returns {Array} a copy of the array with the first instance of each value
*
* removed; subsequent instances of those values (duplicates) remain
* @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[]} anAn array ofcontaining thesethe smaller, chunked arrays.
* @throws When provided a non-array.
*/
chunk: function(arr, size) {
Line 1,225 ⟶ 1,354:
 
/**
* Utilities to enhance select2 menus. See twinklewarn, twinklexfd,
* ************ Morebits.select2 ***************
* twinkleblock for sample usages.
* 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.
*
* https://github.com/select2/select2/issues/3279#issuecomment-442524147
* @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.
* Determines whether the current page is a redirect or soft redirect
* Used by {@link Morebits.wikitext.page#commentOutImage|Morebits.wikitext.page.commentOutImage}.
* (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);
};
 
/**
* **************** Morebits.pageNameNorm ****************
* Stores a normalized version of the wgPageName variable (underscores converted to spaces).
* For queen/king/whatever and country!
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
 
 
/**
* *************** Morebits.pageNameRegex *****************
* For a page name 'Foo bar', returns the string '[Ff]oo bar'
* @param {string} pageName - page name without namespace
* @returns {string}
*/
Morebits.pageNameRegex = function(pageName) {
return '[' + pageName[0].toUpperCase() + pageName[0].toLowerCase() + ']' + pageName.slice(1);
};
 
 
/**
* **************** Morebits.unbinder ****************
* Used for temporarily hiding a part of a string while processing the rest of it.
*
* @memberof Morebits
* eg. var u = new Morebits.unbinder("Hello world <!-- world --> world");
* @class
* u.unbind('<!--','-->');
* @param {string} string - The initial text to process.
* u.content = u.content.replace(/world/g, 'earth');
* @example var u = new uMorebits.rebindunbinder(); // gives "'Hello earthworld <!-- world --> earth"world');
* u.unbind('<!--', '-->'); // text inside comment remains intact
*
* u.content = u.content.replace(/world/g, 'earth');
* Text within the 'unbinded' part (in this case, the HTML comment) remains intact
* u.rebind(); // gives 'Hello earth <!-- world --> earth'
* unbind() can be called multiple times to unbind multiple parts of the string.
*
* Used by Morebits.wikitext.page.commentOutImage
*/
 
/**
* @constructor
* @param {string} string
*/
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:
},
 
/**
/** @returns {string} The output */
* 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
* **************** Morebits.date ****************
* https://momentjs.com/|moment.js}. MediaWiki timestamp format is also
*/
* acceptable, in addition to everything that JS Date() accepts.
 
/* *
* @memberof Morebits
* @constructor
* @class
* Create a date object. MediaWiki timestamp format is also acceptable,
* in addition to everything that JS Date() accepts.
*/
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 = {
// Allow native Date.prototype methods to be used on Morebits.date objects
/** @returns {boolean} */
Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
Morebits.date.prototype[func] = function() {
return this._d[func].apply(this._d, Array.prototype.slice.call(arguments));
};
});
 
$.extend(Morebits.date.prototype, {
 
isValid: function() {
return !isNaN(this.getTime());
},
 
/**
/** @param {(Date|Morebits.date)} date */
* @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();
},
 
/** @returnreturns {string} */
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 {number} number - Should be an integer.
* @param {string} unit
* @throws {Error} ifIf invalid or unsupported unit is given.
* @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 {number} number - Should be an integer.
* @param {string} unit
* @throws {Error} ifIf invalid or unsupported unit is given.
* @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.:
*
* @param {string} formatstr
* | Syntax | Output |
* @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.
* | 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:
 
/**
* @returnsGet {RegExp}a regular expression that matches wikitext section titles, such as ==December 2019== or
* 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 (default 2). Pass 0 for
* @param {number} [level=2] - Header level. Pass 0 for just the text
* just the text with no wikitext markers (==)
* 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 ****************
* Morebits.wiki.api} and {@link Morebits.wiki.page}.
* Various objects for wiki editing and API access
*
* @namespace Morebits.wiki
* @memberof Morebits
*/
Morebits.wiki = {};
 
/**
/** @deprecated in favor of Morebits.isPageRedirect */
* @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 **************** */
/**
* ****************@memberof Morebits.wiki.actionCompleted ****************
* @type {number}
*
* Use of Morebits.wiki.actionCompleted():
* 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().
*/
 
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 **************** */
/**
* **************** 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
* In new code, the use of the last 3 parameters should be avoided, instead use setStatusElement() to bind the
* `onFailure` callbacks.
* 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
* @constructor
* @class
* @param {string} currentAction - The current action (required)
* @param {Objectstring} querycurrentAction - The querycurrent action (required).
* @param {Functionobject} [onSuccess]query - The functionquery to call when request gotten(required).
* @param {Morebits.statusFunction} [statusElementonSuccess] - AThe Morebits.status objectfunction to usecall forwhen statusrequest messagesis (optional)successful.
* @param {FunctionMorebits.status} [onErrorstatusElement] - TheA functionMorebits.status object to call if anuse errorfor occursstatus (optional)messages.
* @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:
 
/**
* CarriesCarry out the request.
*
* @param {Object} callerAjaxParameters Do not specify a parameter unless you really
* @param {object} callerAjaxParameters - Do not specify a parameter unless you really
* really want to give jQuery some extra parameters
* really want to give jQuery some extra parameters.
* @returns {promise} - aA jQuery promise object that is resolved or rejected with the api object.
*/
post: function(callerAjaxParameters) {
Line 1,937 ⟶ 2,128:
};
 
/**
// Custom user agent header, used by WMF for server-side logging
* Custom user agent header, used by WMF for server-side logging. Set via
// See https://lists.wikimedia.org/pipermail/mediawiki-api-announce/2014-November/000075.html
* {@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.
*
* @param {string} ua User agent
* @memberof Morebits.wiki.api
* @param {string} [ua] - User agent.
*/
Morebits.wiki.api.setApiUserAgent = function(ua) {
Line 1,949 ⟶ 2,150:
};
 
 
// Default change/revision tag applied to Morebits actions when no other tags are specified
 
// Off by default per [[Special:Permalink/970618849#Adding tags to Twinkle edits and actions]]
/**
* 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 */
* 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.
* **************** Morebits.wiki.page ****************
* Uses 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):
* HIGHLIGHTS:
*
* - Edit current contents of a page (no edit conflict):
* Constructor: Morebits.wiki.page(pageName, currentAction)
* `.load(userTextEditCallback) -> ctx.loadApi.post() ->
* pageName - the name of the page, prefixed by the namespace (if any)
* ctx.loadApi.post.success() -> ctx.fnLoadSuccess() -> userTextEditCallback() ->
* (for the current page, use mw.config.get('wgPageName'))
* .save() -> ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()`
* currentAction - a string describing the action about to be undertaken (optional)
*
* - Edit current contents of a page (with edit conflict):
* onSuccess and onFailure are callback functions called when the operation is a success or failure
* `.load(userTextEditCallback) -> ctx.loadApi.post() ->
* if enclosed in [brackets], it indicates that it is optional
* 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):
* load(onSuccess, [onFailure]): Loads the text for the page
* `.append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() -> ctx.saveApi.post() ->
* ctx.loadApi.post.success() -> ctx.fnSaveSuccess()`
*
* Notes:
* getPageText(): returns a string containing the text of the page after a successful load()
* 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.
*
* save([onSuccess], [onFailure]): Saves the text set via setPageText() for the page.
* Must be preceded by 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).
*
* @memberof Morebits.wiki
* append([onSuccess], [onFailure]): Adds the text provided via setAppendText() to the end of
* @class
* the page. Does not require calling load() first.
* @param {string} pageName - The name of the page, prefixed by the namespace (if any).
*
* For the current page, use `mw.config.get('wgPageName')`.
* prepend([onSuccess], [onFailure]): Adds the text provided via setPrependText() to the start
* @param {string} [currentAction] - A string describing the action about to be undertaken.
* of the page. Does not require calling load() first.
*
* newSection([onSuccess], [onFailure]): Creates a new section with the text provided via setNewSectionText()
* and section title via setNewSetionTitle(). Does not require calling load() first.
*
* move([onSuccess], [onFailure]): Moves a page to another title
*
* patrol(): Patrols a page; ignores errors
*
* triage(): Marks page as reviewed using PageTriage, which implies patrolled; ignores most errors
*
* deletePage([onSuccess], [onFailure]): Deletes a page (for admins only)
*
* undeletePage([onSuccess], [onFailure]): Undeletes a page (for admins only)
*
* protect([onSuccess], [onFailure]): Protects a page
*
* getPageName(): returns a string containing the name of the loaded page, including the namespace
*
* setPageText(pageText) sets the updated page text that will be saved when save() is called
*
* setAppendText(appendText) sets the text that will be appended to the page when append() is called
*
* setPrependText(prependText) sets the text that will be prepended to the page when prepend() is called
*
* setNewSectionText(newSectionText) sets the text that will be added in a new section when newSection() is called
*
* setNewSectionTitle(newSectionTitle) sets the title for the new section when newSection() is called
*
* setCallbackParameters(callbackParameters)
* callbackParameters - an object for use in a callback function
*
* getCallbackParameters(): returns the object previous set by setCallbackParameters()
*
* Callback notes: callbackParameters is for use by the caller only. The parameters
* 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().
*
* getStatusElement(): returns the Status element created by the constructor
*
* exists(): returns true if the page existed on the wiki when it was last loaded
*
* getCurrentID(): returns a string containing the current revision ID of the page
*
* lookupCreation(onSuccess): Retrieves the username and timestamp of page creation
* onSuccess - callback function which is called when the username and timestamp
* are found within the callback.
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
*
* getCreator(): returns the user who created the page following lookupCreation()
*
* getCreationTimestamp(): returns an ISOString timestamp of page creation following lookupCreation()
*
*/
 
/**
* 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.
*/
 
/**
* @constructor
* @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 (optional)
*/
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} onSuccess - callback function which is called when the load has succeeded
* @param {Function} [onFailure]onSuccess - callbackCallback function which is called when the load failshas (optional)succeeded.
* @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.
* Warning: Calling save() can result in additional calls to the previous load() callbacks
* @param {Function} [onFailure] - Callback function which is called when the save fails.
* 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 (optional)
* @param {Function} [onFailure] - callback function which is called when the save fails
* (optional)
*/
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} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function which is called when the method failshas (optional)succeeded.
* @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} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function which is called when the method failshas (optional)succeeded.
* @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} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function which is called when the method failshas (optional)succeeded.
* @param {Function} [onFailure] - Callback function which is called when the method fails.
*/
this.newSection = function(onSuccess, onFailure) {
Line 2,457 ⟶ 2,592:
};
 
/** @returns {string} string containing theThe name of the loaded page, including the namespace */
this.getPageName = function() {
return ctx.pageName;
};
 
/** @returns {string} string containing theThe text of the page after a successful load() */
this.getPageText = function() {
return ctx.pageText;
};
 
/** @param {string} pageText - updatedUpdated page text that will be saved when `save()` is called */
this.setPageText = function(pageText) {
ctx.editMode = 'all';
Line 2,473 ⟶ 2,608:
};
 
/** @param {string} appendText - textText that will be appended to the page when `append()` is called */
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
Line 2,479 ⟶ 2,614:
};
 
/** @param {string} prependText - textText that will be prepended to the page when `prepend()` is called */
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
Line 2,485 ⟶ 2,620:
};
 
/** @param {string} newSectionText - textText that will be added in a new section on the page when `newSection()` is called */
this.setNewSectionText = function(newSectionText) {
ctx.editMode = 'new';
Line 2,492 ⟶ 2,627:
 
/**
* @param {string} newSectionTitle - titleTitle for the new section created when `newSection()` is called
* 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:
/**
* @param {string} summary - text ofSet the edit summary that will be used when `save()` is called.
* 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 (T252980)
* 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] - canCan take the following four values:
* - `recreate` -: create the page if it does not exist, or edit it if it exists.
* - `createonly` -: create the page if it does not exist, but return an error if it
* error if it already exists.
* - `nocreate` -: don't create the page, only edit it if it already exists.
* - `null -`: create the page if it does not exist, unless it was deleted in the moment
* in the moment between loading the page and saving the edit (default).
*
*/
Line 2,537 ⟶ 2,675:
};
 
/** @param {boolean} minorEdit - setSet true to mark the edit as a minor edit. */
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
 
/** @param {boolean} botEdit - setSet true to mark the edit as a bot edit */
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
Line 2,548 ⟶ 2,686:
 
/**
* @param {number} pageSection - integerInteger specifying the section number to load or save.
* If specified as `null`, the entire page will be retrieved.
*/
Line 2,556 ⟶ 2,694:
 
/**
* @param {number} maxConflictRetries - numberNumber of retries for save errors involving an edit conflict or
* loss of token. Default: 2.
*/
this.setMaxConflictRetries = function(maxConflictRetries) {
Line 2,564 ⟶ 2,702:
 
/**
* @param {number} maxRetries - numberNumber of retries for save errors not involving an edit conflict or
* loss of token. Default: 2.
*/
this.setMaxRetries = function(maxRetries) {
Line 2,572 ⟶ 2,710:
 
/**
* @param {boolean} [watchlistOption=false] -
* - `True -`: page will be added to the user's watchlist when `save()` is called
* - `False -`: watchlist status of the page will not be changed (default).
*/
this.setWatchlist = function(watchlistOption) {
Line 2,585 ⟶ 2,723:
 
/**
* @param {boolean} [watchlistOption=false] -
* - `True -`: page watchlist status will be set based on the user's
* preference settings when `save()` is called.
* - `False -`: watchlist status of the page will not be changed (default).
*
* Watchlist notes:
* 1. The MediaWiki API value of 'unwatch', which explicitly removes the page from the
* the page from the user's watchlist, is not used.
* 2. If both `setWatchlist()` and `setWatchlistFromPreferences()` are called,
* called, the last call takes priority.
* 3. Twinkle modules should use the appropriate preference to set the watchlist options.
* 4. Most Twinkle modules use `setWatchlist()`. `setWatchlistFromPreferences()`
* setWatchlistFromPreferences() is only needed for the few Twinkle watchlist preferences that
* that accept a string value of '`default'`.
*/
this.setWatchlistFromPreferences = function(watchlistOption) {
Line 2,609 ⟶ 2,747:
 
/**
* @param {boolean} [followRedirect=false] -
* - `true -`: a maximum of one redirect will be followed. In the event
* In the event of a redirect, a message is displayed to the user and the redirect
* the redirect target can be retrieved with getPageName().
* - `false -`: (default) the requested pageName will be used without regard to any redirect.
* @param {boolean} [followCrossNsRedirect=true] - Not applicable if `followRedirect` is not set true.
* redirect.
* - `true`: (default) follow redirect even if it is a cross-namespace redirect
* @param {boolean} followCrossNsRedirect
* - `false`: don't follow redirect if it is cross-namespace, edit the redirect itself.
* 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 - ifIf set true, the author and timestamp of the first non-redirect
* 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
* less 50 revisions and all are redirects, the original creation is retrived.
* 2. Revisions that the user is not privileged to access
* (revdeled/suppressed) will be treated as non-redirects.
* as non-redirects.
* 3. Must not be used when the page has a non-wikitext contentmodel
* such as Modulespace Lua or user JavaScript/CSS.
*/
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} string containing theThe current revision ID of the page */
this.getCurrentID = function() {
return ctx.revertCurID;
};
 
/** @returns {string} lastLast editor of the page */
this.getRevisionUser = function() {
return ctx.revertUser;
Line 2,716 ⟶ 2,854:
 
/**
* `callbackParameters` -Define an object for use in a callback function.
*
* Callback notes: `callbackParameters` is for use by the caller only. The parameters
* allow a caller to pass the proper context into its callback function.
* function. Callers must ensure that any changes to the callbackParameters object
* callbackParameters object within a `load()` callback still permit a proper re-entry into the
* 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 the{object} - The object previouspreviously set by `setCallbackParameters()`.
*/
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} trueTrue if the page existed on the wiki when it was last loaded.
*/
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} theThe user who created the page following `lookupCreation()`.
*/
this.getCreator = function() {
Line 2,781 ⟶ 2,922:
 
/**
* @returns {string} theThe ISOString timestamp of page creation following `lookupCreation()`.
*/
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
* @param {Function} onSuccess - callback function (required) which is
* `getCreationTimestamp()` function.
* called when the username and timestamp are found within the callback.
* Prior to June 2019 known as `lookupCreator()`.
* The username can be retrieved using the getCreator() function;
*
* the timestamp can be retrieved using the getCreationTimestamp() function
* @param {Function} onSuccess - Callback function to be called when
* Prior to June 2019 known as lookupCreator
* 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} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function to run on failure (optional)success.
* @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} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function to run on failure (optional)success.
* @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.
* https://www.mediawiki.org/wiki/Extension:PageTriage
*
* Referred to as "review" on-wiki
*
* 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.
* Will first check if the page is queued via fnProcessTriageList
*
* @see {@link https://www.mediawiki.org/wiki/Extension:PageTriage} Referred to as "review" on-wiki.
* No error handling since we don't actually care about the errors
*/
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} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function to run on failure (optional)success.
* @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} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function to run on failure (optional)success.
* @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} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailureonSuccess] - callbackCallback function to run on failure (optional)success.
* @param {Function} [onFailure] - Callback function to run on failure.
*/
this.protect = function(onSuccess, onFailure) {
Line 3,042 ⟶ 3,189:
 
/**
* Apply FlaggedRevs protection-style settings. Only works on wikis where
* onlythe worksextension whereis installed (`$wgFlaggedRevsProtection = true (i.e. where FlaggedRevs`
* i.e. where FlaggedRevs settings appear on the wiki's "protect" tab).
*
* @param {function} [onSuccess]
* @see {@link https://www.mediawiki.org/wiki/Extension:FlaggedRevs}
* @param {function} [onFailure]
* 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`,
* `stabilize`, `deletePage`, and `undeletePage`. Can't use for protect since it always
* `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 fnCanUseMwUserToken or require checking{@link
* Morebits.wiki.page~fnCanUseMwUserToken|fnCanUseMwUserToken} or
* protection, maintain the query in one place. Used for delete,
* require checking protection, maintain the query in one place. Used
* undelete, protect, stabilize, and move (basically, just not load)
* 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:
};
 
/**
// Common checks for action methods
// * Common checks for action methods. Used for move, undelete, delete, protect, stabilize
* 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.)
* Common checks for fnProcess functions (`fnProcessDelete`, `fnProcessMove`, etc.
// Used for move, undelete, delete, protect, stabilize
* 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.
* **************** Morebits.wiki.preview ****************
* Uses the API to parse a fragment of wikitext and render it as HTML.
*
* The suggested implementation pattern (in {@link Morebits.simpleWindow} +and
* {@link Morebits.quickForm} situations) is to construct a
* construct a `Morebits.wiki.preview` object after rendering a `Morebits.quickForm`, and bind the object
* bind the object to an arbitrary property of the form (e.g. |previewer|).
* For an example, see twinklewarn.js.
*
* twinklewarn.js.
* @memberof Morebits.wiki
*/
* @class
 
* @param {HTMLElement} previewbox - The element that will contain the rendered HTML,
/**
* usually a <div> element.
* @constructor
* @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} wikitext - wikitext to render; most things should work, including subst: and ~~~~
* @param {string} [pageTitle]wikitext - optionalWikitext parameterto forrender; themost page thisthings should be rendered as being onwork, if omitted it is taken asincluding the`subst:` currentand page`~~~~`.
* @param {string} [sectionTitlepageTitle] - ifOptional provided,parameter renderfor the textpage this should be rendered as abeing newon, sectionif usingomitted thisit is taken as the titlecurrent page.
* @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.
* **************** 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.
*
* @param {string} text Wikitext containing a template
* @memberof Morebits.wikitext
* @param {number} [start=0] Index noting where in the text the template begins
* @param {string} text - Wikitext containing a template.
* @returns {Object} {name: templateName, parameters: {key: value}}
* @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.
* @constructor
*
* @param {string} text
* @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));
 
// 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]]
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.; Ifif used in a gallery, deletes the whole line.
* 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} 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 first usageuses of [[File:`image`]] to [[File:`image`|`data`]].
*
* @param {string} image - Image name without File: prefix
* @param {string} dataimage - Image name without File: prefix.
* @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. Optional, defaults to /i
* @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:
},
 
/**
/** @returns {string} */
* 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.
* *********** Morebits.userspaceLogger ************
* Used in CSD, PROD, and XFD.
* Handles logging actions to a userspace log, used in
*
* twinklespeedy and twinkleprod.
* @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 ****************
*/
 
/* **************** Morebits.status **************** */
/**
* Create and show status messages of varying urgency.
* @constructor
* {@link Morebits.status.init|Morebits.status.init()} must be called before any status object is created, otherwise
* any status object is created, otherwise those statuses won't be visible.
*
* @param {String} text - Text before the the colon `:`
* @memberof Morebits
* @param {String} stat - Text after the colon `:`
* @class
* @param {String} [type=status] - This parameter determines the font color of the status line,
* @param {string} text - Text before the the colon `:`.
* this can be 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
* @param {string} stat - Text after the colon `:`.
* The default is 'status'
* @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.
*
* @param {HTMLElement} root - usually a div element
* @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;
 
/**
/** @param {Function} handler - function to execute on error */
* @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 {String} status - Part of status message after colon `:`
* @param {Stringstring} typestatus - 'status'Part (blue),of 'info' (green), 'warn' (red), orstatus 'error'message (boldafter red)colon.
* @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.
*
* @param {String} text
* @memberof Morebits.status
* @param {string} text
*/
Morebits.status.actionCompleted = function(text) {
Line 4,499 ⟶ 4,718:
 
/**
* Display the user's rationale, comments, etc. backBack to them after a failure,
* 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.
* **************** Morebits.htmlNode() ****************
*
* Simple helper function to create a simple node
* @param {string} type - typeType of HTML element.
* @param {string} textcontent - textText content.
* @param {string} [color] - fontFont color.
* @returns {HTMLElement}
*/
Line 4,538 ⟶ 4,759:
 
/**
* Add shift-click support for checkboxes. The wikibits version
* **************** Morebits.checkboxShiftClickSupport() ****************
* (`window.addCheckboxClickHandlers`) has some restrictions, and doesn't work
* shift-click-support for checkboxes
* with checkboxes inside a sortable table, so let's build our own.
* 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:
 
 
/** **************** Morebits.batchOperation **************** */
/**
* 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
* Constructor: Morebits.batchOperation(currentAction)
* array of page names (strings).
*
* `setOption(optionName, optionValue)`: Sets a known option:
* setPageList(wikitext): Sets the list of pages to work on.
* - `chunkSize` (integer): The size of chunks to break the array into (default
* It should be an array of page names (strings).
* 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
* setOption(optionName, optionValue): Sets a known option:
* list. The callback must call `workerSuccess` when succeeding, or
* - chunkSize (integer): the size of chunks to break the array into (default 50).
* `workerFailure` when failing. If using {@link Morebits.wiki.api} or {@link
* Setting this to a small value (<5) can cause problems.
* Morebits.wiki.page}, this is easily done by passing these two functions as
* - preserveIndividualStatusLines (boolean): keep each page's status element visible
* parameters to the methods on those objects: for instance,
* when worker is complete? See note below
* `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
* run(worker, postFinish): Runs the callback `worker` for each page in the list.
* `workerSuccess` callback has access to the page title. This is no problem for
* The callback must call workerSuccess when succeeding, or workerFailure
* {@link Morebits.wiki.page} objects. But when using the API, please set the
* when failing. If using Morebits.wiki.api or Morebits.wiki.page, this is easily
* |pageName| property on the {@link Morebits.wiki.api} object.
* 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
* Morebits.wiki.page objects. But when using the API, please set the
* |pageName| property on the Morebits.wiki.api object.
*
* There are sample batchOperation implementations using Morebits.wiki.page in
* twinklebatchdelete.js, twinklebatchundelete.js, and twinklebatchprotect.js.
*/
* @memberof Morebits
 
* @class
/**
* @constructor
* @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
* @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:.
*
* - chunkSize (integer):
* @param {string} optionName - Name of the option:
* The size of chunks to break the array into (default 50).
* - chunkSize (integer): The size of chunks to break the array into
* Setting this to a small value (<5) can cause problems.
* (default 50). Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines (boolean):
* - preserveIndividualStatusLines (boolean): Keep each page's status
* 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 (optional).
*
* @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
* @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 (ege.g. you're using `mw.Api()` or something else), and
* `preserveIndividualStatusLines` option is on, give the page name (string) as argument.
*/
Line 4,821 ⟶ 5,050:
};
 
/**
/** ********** Morebits.taskManager ****************
*
* Given a set of asynchronous functions to run along with their dependencies,
* Morebits.taskManager figuresfigure out an efficient sequence of running them so that multiple functions
* that multiple functions that don't depend on each other are triggered simultaneously. Where
* simultaneously. Where dependencies exist, it ensures that the dependency functions finish running
* functions finish running before the dependent function runs. The values resolved by the dependencies
* resolved by the dependencies are made available to the dependant as arguments.
*
* @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 {function} func - a task
* @param {function[]Function} depsfunc - itsA dependenciestask.
* @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.
* **************** Morebits.simpleWindow ****************
*
* A simple draggable window
* @memberof Morebits
* now a wrapper for jQuery UI's dialog feature
* @class
* @requires {jquery.ui.dialog}
*/
 
/**
* @constructor
* @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} text Link's text content
* @param {string} wikiPagetext - LinkDisplay targettext.
* @param {string} wikiPage - Link target.
* @param {boolean} [prep=false] - Set true to prepend rather than append.
* @returns {Morebits.simpleWindow}
*/
Line 5,140 ⟶ 5,383:
 
/**
* SetSets whether the window should be modal or not. Modal dialogs create
* Ifan setoverlay tobelow true,the otherdialog itemsbut onabove theother page will be disabled, ielements.e., cannot beThis
* must be used (if necessary) before calling display().
* interacted with. Modal dialogs create an overlay below the dialog but above
*
* other page elements.
* @param {boolean} [modal=false] - If set to true, other items on the
* This must be used (if necessary) before calling display()
* page will be disabled, i.e., cannot be interacted with.
* Default: false
* @param {boolean} modal
* @returns {Morebits.simpleWindow}
*/
Line 5,156 ⟶ 5,398:
 
/**
* Enables or disables all footer buttons on all {@link Morebits.simpleWindowssimpleWindow}s in the current page.
* 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: