MediaWiki:Gadget-morebits.js: Difference between revisions

Repo at a999149: add documentation; more documentation
imported>MusikAnimal
(v2.0 at 9b61d36: Remove deprecated functions and unneeded shims)
(Repo at a999149: add documentation; more documentation)
Line 40:
* **************** Morebits.userIsInGroup() ****************
* Simple helper function to see what groups a user might belong
* @param {string} group eg. `sysop`, `extendedconfirmed`, etc
* @returns {boolean}
*/
 
Morebits.userIsInGroup = function ( group ) {
return mw.config.get( 'wgUserGroups' ).indexOf( group ) !== -1;
Line 54 ⟶ 55:
* Converts an IPv6 address to the canonical form stored and used by MediaWiki.
*/
 
Morebits.sanitizeIPv6 = function ( address ) {
address = address.trim();
Line 148:
*/
 
/**
* @constructor
* @param {event} event Function to execute when form is submitted
* @param {*} eventType
*/
Morebits.quickForm = function QuickForm( event, eventType ) {
this.root = new Morebits.quickForm.element( { type: 'form', event: event, eventType:eventType } );
};
 
/**
* Renders the HTML output of the quickForm
* @returns {HTMLElement}
*/
Morebits.quickForm.prototype.render = function QuickFormRender() {
var ret = this.root.render();
Line 158 ⟶ 167:
};
 
/**
* Append element to the form
* @param {Morebits.quickForm.element} data
*/
Morebits.quickForm.prototype.append = function QuickFormAppend( data ) {
return this.root.append( data );
};
 
/**
* @constructor
* @param {Object}
*/
Morebits.quickForm.element = function QuickFormElement( data ) {
this.data = data;
Line 170 ⟶ 187:
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 quickForm element
* @returns {Morebits.quickForm.element} The same element passed in
*/
Morebits.quickForm.element.prototype.append = function QuickFormElementAppend( data ) {
var child;
Line 181 ⟶ 204:
};
 
/**
// This should be called without parameters: form.render()
* Renders the HTML output for the quickForm element
* This should be called without parameters: form.render()
* @returns {HTMLElement}
*/
Morebits.quickForm.element.prototype.render = function QuickFormElementRender( internal_subgroup_id ) {
var currentNode = this.compute( this.data, internal_subgroup_id );
Line 649 ⟶ 676:
};
 
/**
* @param {HTMLElement} node
* @param {Object} data
*/
Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip( node, data ) {
$('<span/>', {
Line 661 ⟶ 692:
});
};
 
 
// Some utility methods for manipulating quickForms after their creation:
// (None of these work for "dyninput" type fields at present)
 
 
/**
* Returns all form elements with a given field name or ID
* Some utility methods for manipulating quickForms after their creation
* @returns {HTMLElement[]}
* (None of them work for "dyninput" type fields at present)
*
* Morebits.quickForm.getElements(form, fieldName)
* Returns all form elements with a given field name or ID
*
* Morebits.quickForm.getCheckboxOrRadio(elementArray, value)
* Searches the array of elements for a checkbox or radio button with a certain |value| attribute
*
* Morebits.quickForm.getElementContainer(element)
* Returns the <div> containing the form element, or the form element itself
* May not work as expected on checkboxes or radios
*
* Morebits.quickForm.getElementLabelObject(element)
* Gets the HTML element that contains the label of the given form element (mainly for internal use)
*
* Morebits.quickForm.getElementLabel(element)
* Gets the label text of the element
*
* Morebits.quickForm.setElementLabel(element, labelText)
* Sets the label of the element to the given text
*
* Morebits.quickForm.overrideElementLabel(element, temporaryLabelText)
* Stores the element's current label, and temporarily sets the label to the given text
*
* Morebits.quickForm.resetElementLabel(element)
* Restores the label stored by overrideElementLabel
*
* Morebits.quickForm.setElementVisibility(element, visibility)
* Shows or hides a form element plus its label and tooltip
*
* Morebits.quickForm.setElementTooltipVisibility(element, visibility)
* Shows or hides the "question mark" icon next to a form element
*/
 
Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) {
var $form = $(form);
Line 711 ⟶ 715:
};
 
/**
* 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
* @param {string} value
* @returns {HTMLInputElement}
*/
Morebits.quickForm.getCheckboxOrRadio = function QuickFormGetCheckboxOrRadio(elementArray, value) {
var found = $.grep(elementArray, function(el) {
Line 721 ⟶ 732:
};
 
/**
* Returns the <div> containing the form element, or the form element itself
* May not work as expected on checkboxes or radios
* @param {HTMLElement} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementContainer = function QuickFormGetElementContainer(element) {
// for divs, headings and fieldsets, the container is the element itself
Line 732 ⟶ 749:
};
 
/**
* Gets the HTML element that contains the label of the given form element
* (mainly for internal use)
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementLabelObject = function QuickFormGetElementLabelObject(element) {
// for buttons, divs and headers, the label is on the element itself
Line 752 ⟶ 775:
};
 
/**
* Gets the label text of the element
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {string}
*/
Morebits.quickForm.getElementLabel = function QuickFormGetElementLabel(element) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
Line 761 ⟶ 789:
};
 
/**
* Sets the label of the element to the given text
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} labelText
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.setElementLabel = function QuickFormSetElementLabel(element, labelText) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
Line 771 ⟶ 805:
};
 
/**
* Stores the element's current label, and temporarily sets the label to the given text
* @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) {
if (!element.hasAttribute("data-oldlabel")) {
Line 778 ⟶ 818:
};
 
/**
* Restores the label stored by overrideElementLabel
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) {
if (element.hasAttribute("data-oldlabel")) {
Line 785 ⟶ 830:
};
 
/**
* Shows or hides a form element plus its label and tooltip
* @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) {
$(element).toggle(visibility);
};
 
/**
* Shows or hides the "question mark" icon (which displays the tooltip) next to a form element
* @param {(HTMLElement|jQuery)} element
* @param {boolean} [visibility] Skip this to toggle visibility
*/
Morebits.quickForm.setElementTooltipVisibility = function QuickFormSetElementTooltipVisibility(element, visibility) {
$(Morebits.quickForm.getElementContainer(element)).find(".morebits-tooltip").toggle(visibility);
Line 797 ⟶ 852:
/**
* **************** HTMLFormElement ****************
*
* getChecked:
* XXX Doesn't seem to work reliably across all browsers at the moment. -- see getChecked2 in twinkleunlink.js, which is better
*
* 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 options
* that have selected set to true. (don't try to mix selects with radio/checkboxes, please)
* Type is optional and can specify if either radio or checkbox (for the event
* that both checkboxes and radiobuttons have the same name.
*/
 
/**
* 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
* options that have selected set to true. (don't try to mix selects with radio/checkboxes,
* please)
* Type is optional and can specify if either radio or checkbox (for the event
* that both checkboxes and radiobuttons have the same name.
*
* XXX: Doesn't seem to work reliably across all browsers at the moment. -- see getChecked2
* in twinkleunlink.js, which is better
*/
HTMLFormElement.prototype.getChecked = function( name, type ) {
var elements = this.elements[name];
Line 856 ⟶ 913:
* **************** RegExp ****************
*
* RegExp.escape: Will escapeEscapes a string to be used in a RegExp
* @param {string} text - string to be escaped
* @param {boolean} [space_fix] - Set this true to replace spaces and underscore with `[ _]` as they are often equivalent
*/
 
Line 990 ⟶ 1,049:
return str.substr( 0, 1 ).toLowerCase() + str.substr( 1 );
},
 
splitWeightedByKeys: function( str, start, end, skip ) {
/**
* 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 {String[]}
*/
splitWeightedByKeys: function( str, start, end, skiplist ) {
if( start.length !== end.length ) {
throw new Error( 'start marker and end marker must be of the same length' );
Line 997 ⟶ 1,066:
var initial = null;
var result = [];
if( ! Array.isArray( skipskiplist ) ) {
if( skipskiplist === undefined ) {
skipskiplist = [];
} else if( typeof skipskiplist === 'string' ) {
skipskiplist = [ skipskiplist ];
} else {
throw new Error( "non-applicable skipskiplist parameter" );
}
}
for( var i = 0; i < str.length; ++i ) {
for( var j = 0; j < skipskiplist.length; ++j ) {
if( str.substr( i, skipskiplist[j].length ) === skipskiplist[j] ) {
i += skipskiplist[j].length - 1;
continue;
}
Line 1,031 ⟶ 1,100:
return result;
},
 
// for deletion/other templates taking a freeform "reason" from a textarea (e.g. PROD, XFD, RPP)
/**
* 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}
*/
formatReasonText: function( str ) {
var result = str.toString().trimRight();
Line 1,039 ⟶ 1,114:
return unbinder.rebind();
},
 
// a replacement for String.prototype.replace() when the second parameter (the
/**
// replacement string) is arbitrary, such as a username or freeform user input,
* Replacement for `String.prototype.replace()` when the second parameter
// and may contain dollar signs
* (the replacement string) is arbitrary, such as a username or freeform user input,
* and may contain dollar signs
*/
safeReplace: function morebitsStringSafeReplace(string, pattern, replacement) {
return string.replace(pattern, replacement.replace(/\$/g, "$$$$"));
}
};
 
 
 
/**
* **************** Morebits.array ****************
*
* uniq(arr): returns a copy of the array with duplicates removed
*
* dups(arr): returns a copy of the array with the first instance of each value
* removed; subsequent instances of those values (duplicates) remain
*
* chunk(arr, size): breaks up |arr| into smaller arrays of length |size|, and
* returns an array of these "chunked" arrays
*/
 
Morebits.array = {
/**
* @returns {Array} a copy of the array with duplicates removed
*/
uniq: function(arr) {
if ( ! Array.isArray( arr ) ) {
Line 1,075 ⟶ 1,147:
return result;
},
 
/**
* @returns {Array} a copy of the array with the first instance of each value
* removed; subsequent instances of those values (duplicates) remain
*/
dups: function(arr) {
if ( ! Array.isArray( arr ) ) {
Line 1,091 ⟶ 1,168:
return result;
},
 
 
/**
* breaks up `arr` into smaller arrays of length `size`, and
* @returns an array of these "chunked" arrays
* @param {array} arr
* @param {number} size
* @returns {Array}
*/
chunk: function( arr, size ) {
if ( ! Array.isArray( arr ) ) {
Line 1,110 ⟶ 1,196:
}
};
 
 
 
Line 1,119 ⟶ 1,204:
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
 
 
 
/**
* **************** Morebits.unbinder ****************
* Used for temporarily hiding a part of a string while processing the rest of it.
*
* eg. var u = new Morebits.unbinder("Hello world <!-- world --> world");
* u.unbind('<!--','-->');
* u.content = u.content.replace(/world/g, 'earth');
* u.rebind() // gives "Hello earth <!-- world --> earth"
*
* Text within the 'unbinded' part (in this case, the HTML comment) remains intact
* 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 ) {
if( typeof string !== 'string' ) {
Line 1,139 ⟶ 1,237:
 
Morebits.unbinder.prototype = {
/**
* @param {string} prefix
* @param {string} postfix
*/
unbind: function UnbinderUnbind( prefix, postfix ) {
var re = new RegExp( prefix + '(.*?)' + postfix, 'ggs' );
this.content = this.content.replace( re, Morebits.unbinder.getCallback( this ) );
},
 
/**
* @returns {string} The output
*/
rebind: function UnbinderRebind() {
var content = this.content;
Line 1,179 ⟶ 1,285:
*/
 
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November','December' ];
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
 
Date.monthNamesAbbrev = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
];
 
Date.prototype.getMonthName = function() {
Line 1,304 ⟶ 1,385:
* Various objects for wiki editing and API access
*/
 
Morebits.wiki = {};
 
/**
// Determines whether the current page is a redirect or soft redirect
* Determines whether the current page is a redirect or soft redirect
// (fails to detect soft redirects on edit, history, etc. pages)
* (fails to detect soft redirects on edit, history, etc. pages)
*/
Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() {
return !!(mw.config.get("wgIsRedirect") || document.getElementById("softredirect"));
Line 1,318 ⟶ 1,400:
* **************** Morebits.wiki.actionCompleted ****************
*
* 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
Line 1,386 ⟶ 1,468:
* **************** Morebits.wiki.api ****************
* An easy way to talk to the MediaWiki API.
*/
 
* Constructor parameters:
/**
* currentAction: the current action (required)
* @constructor
* query: the query (required)
* @param {string} currentAction - The current action (required)
* onSuccess: the function to call when request gotten
* @param {Object} query - The query (required)
* statusElement: a Morebits.status object to use for status messages (optional)
* @param {Function} onSuccess onError:- theThe function to call ifwhen anrequest error occurs (optional)gotten
* @param {Object} [statusElement] - A Morebits.status object to use for status messages (optional)
* @param {Function} [onError] - The function to call if an error occurs (optional)
*/
Morebits.wiki.api = function( currentAction, query, onSuccess, statusElement, onError ) {
Line 1,422 ⟶ 1,506:
errorText: null, // full error description, if any
 
/**
// post(): carries out the request
* Carries out the request.
// do not specify a parameter unless you really really want to give jQuery some extra parameters
* @param {Object} callerAjaxParameters Do not specify a parameter unless you really
* really want to give jQuery some extra parameters
*/
post: function( callerAjaxParameters ) {
 
Line 1,514 ⟶ 1,601:
var morebitsWikiApiUserAgent = 'morebits.js/2.0 ([[w:WT:TW]])';
 
/**
// Sets the custom user agent header
* Sets the custom user agent header
* @param {string} ua User agent
*/
Morebits.wiki.api.setApiUserAgent = function( ua ) {
morebitsWikiApiUserAgent = ( ua ? ua + ' ' : '' ) + 'morebits.js/2.0 ([[w:WT:TW]])';
Line 1,536 ⟶ 1,626:
*
*
* HIGHLIGHTS:
* NOTE: This list of member functions is incomplete.
*
* Constructor: Morebits.wiki.page(pageName, currentAction)
Line 1,543 ⟶ 1,633:
* currentAction - a string describing the action about to be undertaken (optional)
*
* load(onSuccess, and onFailure): Loadsare thecallback textfunctions forcalled when the pageoperation is a success or failure
* if enclosed in [brackets], it indicates that it is optional
* onSuccess - callback function which is called when the load has succeeded
* onFailure - callback function which is called when the load fails (optional)
*
* saveload(onSuccess, [onFailure]): SavesLoads the text for the page. Must be preceded by calling load().
*
* onSuccess - callback function which is called when the save has succeeded (optional)
* getPageText(): returns a string containing the text of the page after a successful load()
* onFailure - callback function which is called when the save fails (optional)
*
* 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!
Line 1,555 ⟶ 1,647:
* This behavior can be disabled with setMaxConflictRetries(0).
*
* append([onSuccess], [onFailure]): Adds the text provided via setAppendText() to the end of the page.
* the page. Does not require calling load() first.
* onSuccess - callback function which is called when the method has succeeded (optional)
* onFailure - callback function which is called when the method fails (optional)
*
* prepend([onSuccess], [onFailure]): Adds the text provided via setPrependText() to the start of the page.
* of the page. Does not require calling load() first.
* onSuccess - callback function which is called when the method has succeeded (optional)
* onFailure - callback function which is called when the method fails (optional)
*
* move(onSuccess, [onFailure]): Moves a page to another title
* getPageName(): returns a string containing the name of the loaded page, including the namespace
*
* deletePage(onSuccess, [onFailure]): Deletes a page (for admins only)
* getPageText(): returns a string containing the text of the page after a successful load()
*
* protect(onSuccess, [onFailure]): Protects a page
* setPageText(pageText)
* pageText - string containing the updated page text that will be saved when save() is called
*
* getPageName(): returns a string containing the name of the loaded page, including the namespace
* setAppendText(appendText)
* appendText - string containing the text that will be appended to the page when append() is called
*
* setPrependText(prependText)
* prependText - string containing the text that will be prepended to the page when prepend() is called
*
* setEditSummary(summary)
* summary - string containing the text of the edit summary that will be used when save() is called
*
* setMinorEdit(minorEdit)
* minorEdit is a boolean value:
* true - When save is called, the resulting edit will be marked as "minor".
* false - When save is called, the resulting edit will not be marked as "minor". (default)
*
* setBotEdit(botEdit)
* botEdit is a boolean value:
* true - When save is called, the resulting edit will be marked as "bot".
* false - When save is called, the resulting edit will not be marked as "bot". (default)
*
* setPageText(pageText) sets the updated page text that will be saved when save() is called
* setPageSection(pageSection)
* pageSection - integer specifying the section number to load or save. The default is |null|, which means
* that the entire page will be retrieved.
*
* setAppendText(appendText) sets the text that will be appended to the page when append() is called
* setMaxConflictRetries(maxRetries)
* maxRetries - number of retries for save errors involving an edit conflict or loss of edit token
* default: 2
*
* setPrependText(prependText) sets the text that will be prepended to the page when prepend() is called
* setMaxRetries(maxRetries)
* maxRetries - number of retries for save errors not involving an edit conflict or loss of edit token
* default: 2
*
* setCallbackParameters(callbackParameters)
Line 1,616 ⟶ 1,680:
* getStatusElement(): returns the Status element created by the constructor
*
* exists(): returns true if the page existed on the wiki when it was last loaded
* setFollowRedirect(followRedirect)
* followRedirect is a boolean value:
* true - a maximum of one redirect will be followed.
* In the event of a redirect, a message is displayed to the user and
* the redirect target can be retrieved with getPageName().
* false - the requested pageName will be used without regard to any redirect. (default)
*
* getCurrentID(): returns a string containing the current revision ID of the page
* setWatchlist(watchlistOption)
* watchlistOption is a boolean value:
* 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)
*
* setWatchlistFromPreferences(watchlistOption)
* watchlistOption is a boolean value:
* 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
* user's watchlist, is not used.
* 2. If both setWatchlist() and setWatchlistFromPreferences() are 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() is only needed for the few Twinkle watchlist preferences
* that accept a string value of 'default'.
*
* setCreateOption(createOption)
* createOption is a string value:
* '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
* 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
* between retrieving the edit token and saving the edit (default)
*
* exists(): returns true if the page existed on the wiki when it was last loaded
*
* lookupCreator(onSuccess): Retrieves the username of the user who created the page
Line 1,660 ⟶ 1,689:
*
* getCreator(): returns the user who created the page following lookupCreator()
*
* getCurrentID(): returns a string containing the current revision ID of the page
*
* patrol(): marks the page as patrolled, if possible
*
* move(onSuccess, onFailure): Moves a page to another title
*
* deletePage(onSuccess, onFailure): Deletes a page (for admins only)
*
*/
Line 1,699 ⟶ 1,720:
*/
 
/**
* @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 1,797 ⟶ 1,824:
 
/**
* Loads the text for the page
* Public interface accessors
* @param {Function} onSuccess - callback function which is called when the load has succeeded
* @param {Function} onFailure - callback function which is called when the load fails (optional)
*/
this.getPageName = function() {
return ctx.pageName;
};
 
this.getPageText = function() {
return ctx.pageText;
};
 
this.setPageText = function(pageText) {
ctx.editMode = 'all';
ctx.pageText = pageText;
};
 
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
ctx.appendText = appendText;
};
 
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
ctx.prependText = prependText;
};
 
this.setEditSummary = function(summary) {
ctx.editSummary = summary;
};
 
this.setCreateOption = function(createOption) {
ctx.createOption = createOption;
};
 
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
 
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
};
 
this.setPageSection = function(pageSection) {
ctx.pageSection = pageSection;
};
 
this.setMaxConflictRetries = function(maxRetries) {
ctx.maxConflictRetries = maxRetries;
};
 
this.setMaxRetries = function(maxRetries) {
ctx.maxRetries = maxRetries;
};
 
this.setCallbackParameters = function(callbackParameters) {
ctx.callbackParameters = callbackParameters;
};
 
this.getCallbackParameters = function() {
return ctx.callbackParameters;
};
 
this.getCreator = function() {
return ctx.creator;
};
 
this.setOldID = function(oldID) {
ctx.revertOldID = oldID;
};
 
this.getCurrentID = function() {
return ctx.revertCurID;
};
 
this.getRevisionUser = function() {
return ctx.revertUser;
};
 
this.setMoveDestination = function(destination) {
ctx.moveDestination = destination;
};
 
this.setMoveTalkPage = function(flag) {
ctx.moveTalkPage = !!flag;
};
 
this.setMoveSubpages = function(flag) {
ctx.moveSubpages = !!flag;
};
 
this.setMoveSuppressRedirect = function(flag) {
ctx.moveSuppressRedirect = !!flag;
};
 
this.setEditProtection = function(level, expiry) {
ctx.protectEdit = { level: level, expiry: expiry };
};
 
this.setMoveProtection = function(level, expiry) {
ctx.protectMove = { level: level, expiry: expiry };
};
 
this.setCreateProtection = function(level, expiry) {
ctx.protectCreate = { level: level, expiry: expiry };
};
 
this.setCascadingProtection = function(flag) {
ctx.protectCascade = !!flag;
};
 
this.setFlaggedRevs = function(level, expiry) {
ctx.flaggedRevs = { level: level, expiry: expiry };
};
 
this.getStatusElement = function() {
return ctx.statusElement;
};
 
this.setFollowRedirect = function(followRedirect) {
if (ctx.pageLoaded) {
ctx.statusElement.error("Internal error: cannot change redirect setting after the page has been loaded!");
return;
}
ctx.followRedirect = followRedirect;
};
 
this.setWatchlist = function(flag) {
if (flag) {
ctx.watchlistOption = 'watch';
} else {
ctx.watchlistOption = 'nochange';
}
};
 
this.setWatchlistFromPreferences = function(flag) {
if (flag) {
ctx.watchlistOption = 'preferences';
} else {
ctx.watchlistOption = 'nochange';
}
};
 
this.suppressProtectWarning = function() {
ctx.suppressProtectWarning = true;
};
 
this.exists = function() {
return ctx.pageExists;
};
 
this.load = function(onSuccess, onFailure) {
ctx.onLoadSuccess = onSuccess;
Line 1,988 ⟶ 1,870:
};
 
/**
// Save updated .pageText to Wikipedia
* Saves the text for the page to Wikipedia
// Only valid after successful .load()
* 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 (optional)
* @param {Function} [onFailure] - callback function which is called when the save fails
* (optional)
*/
this.save = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
Line 2,081 ⟶ 1,974:
};
 
/**
* 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} onFailure - callback function which is called when the method fails (optional)
*/
this.append = function(onSuccess, onFailure) {
ctx.editMode = 'append';
Line 2,093 ⟶ 1,992:
};
 
/**
* 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} onFailure - callback function which is called when the method fails (optional)
*/
this.prepend = function(onSuccess, onFailure) {
ctx.editMode = 'prepend';
Line 2,105 ⟶ 2,010:
};
 
/**
* @returns {string} string containing the name of the loaded page, including the namespace
*/
this.getPageName = function() {
return ctx.pageName;
};
 
/**
* @returns {string} string containing the text of the page after a successful load()
*/
this.getPageText = function() {
return ctx.pageText;
};
 
/**
* `pageText` - string containing the updated page text that will be saved when save() is called
*/
this.setPageText = function(pageText) {
ctx.editMode = 'all';
ctx.pageText = pageText;
};
 
/**
* `appendText` - string containing the text that will be appended to the page when append() is called
*/
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
ctx.appendText = appendText;
};
 
/**
* `prependText` - string containing the text that will be prepended to the page when prepend() is called
*/
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
ctx.prependText = prependText;
};
 
 
 
// Edit-related setter methods:
/**
* `summary` - string containing the text of the edit summary that will be used when save() is called
*/
this.setEditSummary = function(summary) {
ctx.editSummary = summary;
};
 
/**
* `createOption` is a string value:
* '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
* 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
* between retrieving the edit token and saving the edit (default)
*
*/
this.setCreateOption = function(createOption) {
ctx.createOption = createOption;
};
 
/**
* `minorEdit` - boolean value:
* True - When save is called, the resulting edit will be marked as "minor".
* False - When save is called, the resulting edit will not be marked as "minor". (default)
*/
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
 
/**
* `botEdit` is a boolean value:
* True - When save is called, the resulting edit will be marked as "bot".
* False - When save is called, the resulting edit will not be marked as "bot". (default)
*/
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
};
 
/**
* `pageSection` - integer specifying the section number to load or save.
* The default is `null`, which means that the entire page will be retrieved.
*/
this.setPageSection = function(pageSection) {
ctx.pageSection = pageSection;
};
 
/**
* `maxRetries` - number of retries for save errors involving an edit conflict or loss of edit token.Default: 2
*/
this.setMaxConflictRetries = function(maxRetries) {
ctx.maxConflictRetries = maxRetries;
};
 
/**
* `maxRetries` - number of retries for save errors not involving an edit conflict or loss of edit token
* Default: 2
*/
this.setMaxRetries = function(maxRetries) {
ctx.maxRetries = maxRetries;
};
 
/**
* `watchlistOption` is a boolean value:
* 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) {
if (watchlistOption) {
ctx.watchlistOption = 'watch';
} else {
ctx.watchlistOption = 'nochange';
}
};
 
/**
* `watchlistOption` is a boolean value:
* 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
* user's watchlist, is not used.
* 2. If both setWatchlist() and setWatchlistFromPreferences() are 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() is only needed for the few Twinkle watchlist preferences
* that accept a string value of 'default'.
*/
this.setWatchlistFromPreferences = function(watchlistOption) {
if (watchlistOption) {
ctx.watchlistOption = 'preferences';
} else {
ctx.watchlistOption = 'nochange';
}
};
 
/**
* `followRedirect` is a boolean value:
* True - a maximum of one redirect will be followed.
* In the event of a redirect, a message is displayed to the user and
* the redirect target can be retrieved with getPageName().
* False - the requested pageName will be used without regard to any redirect. (default)
*/
this.setFollowRedirect = function(followRedirect) {
if (ctx.pageLoaded) {
ctx.statusElement.error("Internal error: cannot change redirect setting after the page has been loaded!");
return;
}
ctx.followRedirect = followRedirect;
};
 
// Move-related setter functions
this.setMoveDestination = function(destination) {
ctx.moveDestination = destination;
};
 
this.setMoveTalkPage = function(flag) {
ctx.moveTalkPage = !!flag;
};
 
this.setMoveSubpages = function(flag) {
ctx.moveSubpages = !!flag;
};
 
this.setMoveSuppressRedirect = function(flag) {
ctx.moveSuppressRedirect = !!flag;
};
 
// Protect-related setter functions
this.setEditProtection = function(level, expiry) {
ctx.protectEdit = { level: level, expiry: expiry };
};
 
this.setMoveProtection = function(level, expiry) {
ctx.protectMove = { level: level, expiry: expiry };
};
 
this.setCreateProtection = function(level, expiry) {
ctx.protectCreate = { level: level, expiry: expiry };
};
 
this.setCascadingProtection = function(flag) {
ctx.protectCascade = !!flag;
};
 
// Revert-related getters/setters:
this.setOldID = function(oldID) {
ctx.revertOldID = oldID;
};
 
/**
* @returns {string} string containing the current revision ID of the page
*/
this.getCurrentID = function() {
return ctx.revertCurID;
};
 
this.getRevisionUser = function() {
return ctx.revertUser;
};
 
// Miscellaneous getters/setters:
 
/**
* `callbackParameters` - 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.
* 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().
*/
this.setCallbackParameters = function(callbackParameters) {
ctx.callbackParameters = callbackParameters;
};
 
/**
* @returns the object previous set by setCallbackParameters()
*/
this.getCallbackParameters = function() {
return ctx.callbackParameters;
};
 
/**
* @returns {Morebits.status} Status element created by the constructor
*/
this.getStatusElement = function() {
return ctx.statusElement;
};
 
 
this.setFlaggedRevs = function(level, expiry) {
ctx.flaggedRevs = { level: level, expiry: expiry };
};
 
/**
* @returns {boolean} true if the page existed on the wiki when it was last loaded
*/
this.exists = function() {
return ctx.pageExists;
};
 
/**
* @returns {string} the user who created the page following lookupCreator()
*/
this.getCreator = function() {
return ctx.creator;
};
 
/**
* Retrieves the username of the user who created the page
* @param {Function} onSuccess - callback function (required) which is
* called when the username is found within the callback, the username
* can be retrieved using the getCreator() function
*/
this.lookupCreator = function(onSuccess) {
if (!onSuccess) {
Line 2,130 ⟶ 2,294:
};
 
/**
* marks the page as patrolled, if possible
*/
this.patrol = function() {
// There's no patrol link on page, so we can't patrol
Line 2,155 ⟶ 2,322:
};
 
/**
* Reverts a page to revertOldID
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.revert = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
Line 2,169 ⟶ 2,341:
};
 
/**
* Moves a page to another title
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.move = function(onSuccess, onFailure) {
ctx.onMoveSuccess = onSuccess;
Line 2,203 ⟶ 2,380:
 
// |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
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.deletePage = function(onSuccess, onFailure) {
ctx.onDeleteSuccess = onSuccess;
Line 2,239 ⟶ 2,421:
};
 
/**
* Protects a page (for admins only)
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.protect = function(onSuccess, onFailure) {
ctx.onProtectSuccess = onSuccess;
Line 2,318 ⟶ 2,505:
};
 
/*
/* Private member functions
* Private member functions
*
* These are not exposed outside
*/
Line 2,330 ⟶ 2,517:
*
* @param {string} action The action being undertaken, e.g. "edit", "delete".
* @returns {boolean}
*/
var fnCanUseMwUserToken = function(action) {
Line 2,848 ⟶ 3,036:
}; // end Morebits.wiki.page
 
/** Morebits.wiki.page TODO: (XXX)
* - Should we retry loads also?
* - Need to reset current action before the save?
* - Deal with action.completed stuff
* - Need to reset all parameters once done (e.g. edit summary, move destination, etc.)
*/
 
 
Line 2,860 ⟶ 3,048:
* **************** Morebits.wiki.preview ****************
* Uses the API to parse a fragment of wikitext and render it as HTML.
*
* Constructor: Morebits.wiki.preview(previewbox, currentAction)
* previewbox - the <div> element that will contain the rendered HTML
*
* beginRender(wikitext): Displays the preview box, and begins an asynchronous attempt
* to render the specified wikitext.
* wikitext - wikitext to render; most things should work, including subst: and ~~~~
* pageTitle - optional parameter for the page this should be rendered as being on
*
* closePreview(): Hides the preview box and clears it.
*
* The suggested implementation pattern (in Morebits.simpleWindow + Morebits.quickForm situations) is to
Line 2,877 ⟶ 3,055:
*/
 
/**
* @constructor
* @param {HTMLDivElement} previewbox - the <div> element that will contain the rendered HTML
*/
Morebits.wiki.preview = function(previewbox) {
this.previewbox = previewbox;
$(previewbox).addClass("morebits-previewbox").hide();
 
/**
* 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] - optional parameter for the page this should be rendered as being on
*/
this.beginRender = function(wikitext, pageTitle) {
$(previewbox).show();
Line 2,907 ⟶ 3,095:
}
previewbox.innerHTML = html;
$(previewbox).find("a").attr("target", "_blank"); // this makes links open in new tab
};
 
/**
* Hides the preview box and clears it.
*/
this.closePreview = function() {
$(previewbox).empty().hide();
Line 2,958 ⟶ 3,149:
}
if( test2 === ']]' ) {
current += test2']]';
++i;
--level;
Line 3,016 ⟶ 3,207:
};
 
/**
* @constructor
* @param {string} text
*/
Morebits.wikitext.page = function mediawikiPage( text ) {
this.text = text;
Line 3,022 ⟶ 3,217:
Morebits.wikitext.page.prototype = {
text: '',
 
/**
* Removes links to `link_target` from the page text.
* @param {string} link_target
*/
removeLink: function( link_target ) {
var first_char = link_target.substr( 0, 1 );
Line 3,035 ⟶ 3,235:
this.text = this.text.replace( link_simple_re, "$1" ).replace( link_named_re, "$1" );
},
 
/**
* Comments out images from page text. If 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} reason - Reason to be included in comment, alongside the commented-out image
*/
commentOutImage: function( image, reason ) {
var unbinder = new Morebits.unbinder( this.text );
Line 3,043 ⟶ 3,250:
var image_re_string = "[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( image.substr( 1 ), true );
 
// Check for normal image links, i.e. [[File:Foobar.png|...]]
/*
// Will eat the whole link
* Check for normal image links, i.e. [[Image:Foobar.png|...]]
* Will eat the whole link
*/
var links_re = new RegExp( "\\[\\[(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string );
var allLinks = Morebits.array.uniq(Morebits.string.splitWeightedByKeys( unbinder.content, '[[', ']]' ));
Line 3,058 ⟶ 3,263:
unbinder.unbind( '<!--', '-->' );
 
// Check for gallery images, i.e. instances that must start on a new line,
/*
* Check for gallery images, i.e. instances that must start on a new line,// eventually preceded with some space, and must include ImageFile: prefix
*// Will eat the whole line.
*/
var gallery_image_re = new RegExp( "(^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string + ".*?$)", 'mg' );
unbinder.content = unbinder.content.replace( gallery_image_re, "<!-- " + reason + "$1 -->" );
Line 3,067 ⟶ 3,271:
// unbind the newly created comments
unbinder.unbind( '<!--', '-->' );
 
/*
*// Check free image usages, for example as template arguments, might have the ImageFile: prefix excluded, but must be preceeded by an |
*// Will only eat the image name and the preceeding bar and an eventual named parameter
*/
var free_image_re = new RegExp( "(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" + image_re_string + ")", 'mg' );
unbinder.content = unbinder.content.replace( free_image_re, "<!-- " + reason + "$1 -->" );
 
// Rebind the content now, we are done!
this.text = unbinder.rebind();
},
 
/**
* Converts first usage of [[File:`image`]] to [[File:`image`|`data`]]
* @param {string} image - Image name without File: prefix
* @param {string} data
*/
addToImageComment: function( image, data ) {
var first_char = image.substr( 0, 1 );
Line 3,098 ⟶ 3,306:
this.text = this.text.replace( gallery_re, newtext );
},
 
/**
* 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
*/
removeTemplate: function( template ) {
var first_char = template.substr( 0, 1 );
Line 3,109 ⟶ 3,323:
}
},
 
getText: function() {
return this.text;
Line 3,118 ⟶ 3,333:
/**
* **************** Morebits.queryString ****************
* Maps the querystring to an JS object
*
* In static context, the value of location.search.substring(1), else the value given
* Functions:
* to the constructor is going to be used. The mapped hash is saved in the object.
*
* Morebits.queryString.exists(key)
* returns true if the particular key is set
* Morebits.queryString.get(key)
* returns the value associated to the key
* Morebits.queryString.equals(key, value)
* returns true if the value associated with given key equals given value
* Morebits.queryString.toString()
* returns the query string as a string
* Morebits.queryString.create( hash )
* creates an querystring and encodes strings via encodeURIComponent and joins arrays with |
*
* In static context, the value of location.search.substring(1), else the value given to the constructor is going to be used. The mapped hash is saved in the object.
*
* Example:
Line 3,140 ⟶ 3,343:
* var obj = new Morebits.queryString('foo=bar&baz=quux');
* value = obj.get('foo');
*/
 
/**
* @constructor
* @param {string} qString The query string
*/
Morebits.queryString = function QueryString(qString) {
Line 3,175 ⟶ 3,383:
Morebits.queryString.staticInit();
return Morebits.queryString.staticstr.get(key);
};
 
Morebits.queryString.prototype.get = function(key) {
return this.params[key] ? this.params[key] : null;
};
 
Line 3,184 ⟶ 3,388:
Morebits.queryString.staticInit();
return Morebits.queryString.staticstr.exists(key);
};
 
Morebits.queryString.prototype.exists = function(key) {
return this.params[key] ? true : false;
};
 
Line 3,193 ⟶ 3,393:
Morebits.queryString.staticInit();
return Morebits.queryString.staticstr.equals(key, value);
};
 
Morebits.queryString.prototype.equals = function(key, value) {
return this.params[key] === value ? true : false;
};
 
Line 3,202 ⟶ 3,398:
Morebits.queryString.staticInit();
return Morebits.queryString.staticstr.toString();
};
 
Morebits.queryString.prototype.toString = function() {
return this.string ? this.string : null;
};
 
Line 3,236 ⟶ 3,428:
return resarr.join('&');
};
 
/**
* @returns {string} the value associated to the given `key`
* @param {string} key
*/
Morebits.queryString.prototype.get = function(key) {
return this.params[key] || null;
};
 
/**
* @returns {boolean} true if the given `key` is set
* @param {string} key
*/
Morebits.queryString.prototype.exists = function(key) {
return !!this.params[key];
};
 
/**
* @returns {boolean} true if the value associated with given `key` equals given `value`
* @param {string} key
* @param {string} value
*/
Morebits.queryString.prototype.equals = function(key, value) {
return this.params[key] === value;
};
 
/**
* @returns {string}
*/
Morebits.queryString.prototype.toString = function() {
return this.string || null;
};
 
/**
* Creates a querystring and encodes strings via `encodeURIComponent` and joins arrays with `|`
* @param {Array} arr
* @returns {string}
*/
Morebits.queryString.prototype.create = Morebits.queryString.create;
 
Line 3,371 ⟶ 3,601:
};
 
/**
// display the user's rationale, comments, etc. back to them after a failure,
* Display the user's rationale, comments, etc. back to them after a failure,
// so they don't use it
* so they don't use it
*/
Morebits.status.printUserText = function( comments, message ) {
var p = document.createElement( 'p' );
Line 3,452 ⟶ 3,684:
}
 
$(jQuerySelector, jQueryContext).click(clickHandler);
};
 
Line 3,490 ⟶ 3,722:
*/
 
/**
* @constructor
* @param {string} [currentAction]
*/
Morebits.batchOperation = function(currentAction) {
var ctx = {
Line 3,515 ⟶ 3,751:
};
 
/**
* Sets the list of pages to work on
* @param {String[]} pageList Array of page names (strings)
*/
this.setPageList = function(pageList) {
ctx.pageList = pageList;
};
 
/**
* 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?
*/
this.setOption = function(optionName, optionValue) {
ctx.options[optionName] = optionValue;
};
 
/**
* Runs the given callback for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* @param {Function} worker
*/
this.run = function(worker) {
if (ctx.running) {
Line 3,646 ⟶ 3,899:
*/
 
/**
// The height passed in here is the maximum allowable height for the content area.
* @constructor
* @param {number} width
* @param {number} height The maximum allowable height for the content area.
*/
Morebits.simpleWindow = function SimpleWindow( width, height ) {
var content = document.createElement( 'div' );
Line 3,656 ⟶ 3,913:
 
$(this.content).dialog({
autoOpen: false,
buttons: { "Placeholder button": function() {} },
dialogClass: 'morebits-dialog',
width: Math.min(parseInt(window.innerWidth, 10), parseInt(width ? width : 800, 10)),
// give jQuery the given height value (which represents the anticipated height of the dialog) here, so
// it can position the dialog appropriately
// the 20 pixels represents adjustment for the extra height of the jQuery dialog "chrome", compared
// to that of the old SimpleWindow
height: height + 20,
close: function(event) {
// dialogs and their content can be destroyed once closed
$(event.target).dialog("destroy").remove();
},
resizeStart: function() {
this.scrollbox = $(this).find(".morebits-scrollbox")[0];
if (this.scrollbox) {
this.scrollbox.style.maxHeight = "none";
}
},
resizeEnd: function() {
this.scrollbox = null;
},
resize: function() {
this.style.maxHeight = "";
if (this.scrollbox) {
this.scrollbox.style.width = "";
}
}
});,
resizeEnd: function() {
this.scrollbox = null;
},
resize: function() {
this.style.maxHeight = "";
if (this.scrollbox) {
this.scrollbox.style.width = "";
}
}
});
 
var $widget = $(this.content).dialog("widget");
Line 3,715 ⟶ 3,972:
scriptName: null,
 
/**
// Focuses the dialog. This might work, or on the contrary, it might not.
* Focuses the dialog. This might work, or on the contrary, it might not.
* @returns {Morebits.simpleWindow}
*/
focus: function() {
$(this.content).dialog("moveToTop");
 
return this;
},
 
// Closes the dialog. If this is set as an event handler, it will stop the event from doing anything more.
/**
* Closes the dialog. If this is set as an event handler, it will stop the event
* from doing anything more.
* @returns {Morebits.simpleWindow}
*/
close: function(event) {
if (event) {
Line 3,727 ⟶ 3,991:
}
$(this.content).dialog("close");
 
return this;
},
 
// Shows the dialog. Calling display() on a dialog that has previously been closed might work, but it is not guaranteed.
/**
* Shows the dialog. Calling display() on a dialog that has previously been closed
* might work, but it is not guaranteed.
* @returns {Morebits.simpleWindow}
*/
display: function() {
if (this.scriptName) {
Line 3,747 ⟶ 4,015:
}
this.setHeight( this.height ); // init height algorithm
 
return this;
},
 
// Sets the dialog title.
/**
* Sets the dialog title.
* @param {string} title
* @returns {Morebits.simpleWindow}
*/
setTitle: function( title ) {
$(this.content).dialog("option", "title", title);
 
return this;
},
 
// 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".
* 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}
*/
setScriptName: function( name ) {
this.scriptName = name;
 
return this;
},
 
// Sets the dialog width.
/**
* Sets the dialog width.
* @param {number} width
* @returns {Morebits.simpleWindow}
*/
setWidth: function( width ) {
$(this.content).dialog("option", "width", width);
 
return this;
},
 
// 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.
* 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}
*/
setHeight: function( height ) {
this.height = height;
 
// from display time onwards, let the browser determine the optimum height, and instead limit the height at the given value
// and instead limit the height at the given value
// note that the given height will exclude the approx. 20px that the jQuery UI chrome has in height in addition to the height
// note that the given height will exclude the approx. 20px that the jQuery UI
// of an equivalent "classic" Morebits.simpleWindow
// chrome has in height in addition to the height of an equivalent "classic"
// Morebits.simpleWindow
if (parseInt(getComputedStyle($(this.content).dialog("widget")[0], null).height, 10) > window.innerHeight) {
$(this.content).dialog("option", "height", window.innerHeight - 2).dialog("option", "position", "top");
Line 3,783 ⟶ 4,069:
}
$(this.content).dialog("widget").find(".morebits-dialog-content")[0].style.maxHeight = parseInt(this.height - 30, 10) + "px";
 
return this;
},
 
// Sets the content of the dialog to the given element node, usually from rendering a Morebits.quickForm.
/**
// Re-enumerates the footer buttons, but leaves the footer links as they are.
* Sets the content of the dialog to the given element node, usually from rendering
// Be sure to call this at least once before the dialog is displayed...
* a 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}
*/
setContent: function( content ) {
this.purgeContent();
this.addContent( content );
 
return this;
},
 
/**
* Adds the given element node to the dialog content.
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
*/
addContent: function( content ) {
this.content.appendChild( content );
Line 3,801 ⟶ 4,097:
var thisproxy = this;
$(this.content).find('input[type="submit"], button[type="submit"]').each(function(key, value) {
value.style.display = "none";
var button = document.createElement("button");
button.textContent = (value.hasAttribute("value") ? value.getAttribute("value") : (value.textContent ? value.textContent : "Submit Query"));
// here is an instance of cheap coding, probably a memory-usage hit in using a closure here
button.addEventListener("click", function() { value.click(); }, false);
thisproxy.buttons.push(button);
});
// remove all buttons from the button pane and re-add them
if (this.buttons.length > 0) {
Line 3,814 ⟶ 4,110:
$(this.content).dialog("widget").find(".morebits-dialog-buttons")[0].setAttribute("data-empty", "data-empty"); // used by CSS
}
 
return this;
},
 
/**
* Removes all contents from the dialog, barring any footer links
* @returns {Morebits.simpleWindow}
*/
purgeContent: function() {
this.buttons = [];
Line 3,825 ⟶ 4,125:
this.content.removeChild( this.content.firstChild );
}
 
return this;
},
 
// Adds a link in the bottom-right corner of the dialog.
/**
// This can be used to provide help or policy links.
* Adds a link in the bottom-right corner of the dialog.
// For example, Twinkle's CSD module adds a link to the CSD policy page,
* This can be used to provide help or policy links.
// as well as a link to Twinkle's documentation.
* 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} wikiPage Link target
* @returns {Morebits.simpleWindow}
*/
addFooterLink: function( text, wikiPage ) {
var $footerlinks = $(this.content).dialog("widget").find(".morebits-dialog-footerlinks");
Line 3,846 ⟶ 4,151:
$footerlinks.append(link);
this.hasFooterLinks = true;
 
return this;
},
 
/**
* Set whether the window should be modal or not.
* If set to true, other items on the page will be disabled, i.e., cannot be
* interacted with. Modal dialogs create an overlay below the dialog but above
* other page elements.
* This must be used (if necessary) before calling display()
* Default: false
* @param {boolean} modal
* @returns {Morebits.simpleWindow}
*/
setModality: function( modal ) {
$(this.content).dialog("option", "modal", modal);
 
return this;
}
};
 
/**
// Enables or disables all footer buttons on all Morebits.simpleWindows in the current page.
* Enables or disables all footer buttons on all Morebits.simpleWindows in the current page.
// This should be called with |false| when the button(s) become irrelevant (e.g. just before Morebits.status.init is called).
* This should be called with `false` when the button(s) become irrelevant (e.g. just before
// This is not an instance method so that consumers don't have to keep a reference to the original
* Morebits.status.init is called).
// Morebits.simpleWindow object sitting around somewhere. Anyway, most of the time there will only be one
* This is not an instance method so that consumers don't have to keep a reference to the
// Morebits.simpleWindow open, so this shouldn't matter.
* 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.
* @param {boolean} enabled
*/
Morebits.simpleWindow.setButtonsEnabled = function( enabled ) {
$(".morebits-dialog-buttons button").prop("disabled", !enabled);
Anonymous user