MediaWiki:Gadget-morebits.js: Difference between revisions

Content added Content deleted
imported>Amorymeltzer
(Repo at 53b6bf4: convert to use Morebits.wiki.page.undeletePage; Add undeletePage to morebits.wiki.page; Remove string trimming shims)
(Repo at 769b724: Shift lookupCreator to lookupCreation, also include timestamp; Add documentation for Morebits.status (#645); checkboxShiftClickSupport: trigger click event; Remove Morebits.wikipedia (#600); Add untag functionality (#485); batchOperation: allow post processing function; Remove Morebits.bytes)
Line 908: Line 908:
};
};


/**
* getUnchecked:
* Does the same as getChecked above, but with unchecked elements.
*/

HTMLFormElement.prototype.getUnchecked = function( name, type ) {
var elements = this.elements[name];
if( !elements ) {
// if the element doesn't exists, return null.
return null;
}
var return_array = [];
var i;
if( elements instanceof HTMLSelectElement ) {
var options = elements.options;
for( i = 0; i < options.length; ++i ) {
if( !options[i].selected ) {
if( options[i].values ) {
return_array.push( options[i].values );
} else {
return_array.push( options[i].value );
}

}
}
} else if( elements instanceof HTMLInputElement ) {
if( type && elements.type !== type ) {
return [];
} else if( !elements.checked ) {
return [ elements.value ];
}
} else {
for( i = 0; i < elements.length; ++i ) {
if( !elements[i].checked ) {
if( type && elements[i].type !== type ) {
continue;
}
if( elements[i].values ) {
return_array.push( elements[i].values );
} else {
return_array.push( elements[i].value );
}
}
}
}
return return_array;
};




Line 928: Line 975:
return text;
return text;
};
};



/**
* **************** Morebits.bytes ****************
* Utility object for formatting byte values
*/

Morebits.bytes = function( value ) {
if( typeof value === 'string' ) {
var res = /(\d+) ?(\w?)(i?)B?/.exec( value );
var number = res[1];
var mag = res[2];
var si = res[3];

if( !number ) {
this.number = 0;
return;
}

if( !si ) {
this.value = number * Math.pow( 10, Morebits.bytes.magnitudes[mag] * 3 );
} else {
this.value = number * Math.pow( 2, Morebits.bytes.magnitudes[mag] * 10 );
}
} else {
this.value = value;
}
};

Morebits.bytes.magnitudes = {
'': 0,
'K': 1,
'M': 2,
'G': 3,
'T': 4,
'P': 5,
'E': 6,
'Z': 7,
'Y': 8
};

Morebits.bytes.rmagnitudes = {
0: '',
1: 'K',
2: 'M',
3: 'G',
4: 'T',
5: 'P',
6: 'E',
7: 'Z',
8: 'Y'
};

Morebits.bytes.prototype.valueOf = function() {
return this.value;
};

Morebits.bytes.prototype.toString = function( magnitude ) {
var tmp = this.value;
if( magnitude ) {
var si = /i/.test(magnitude);
var mag = magnitude.replace( /.*?(\w)i?B?.*/g, '$1' );
if( si ) {
tmp /= Math.pow( 2, Morebits.bytes.magnitudes[mag] * 10 );
} else {
tmp /= Math.pow( 10, Morebits.bytes.magnitudes[mag] * 3 );
}
if( parseInt( tmp, 10 ) !== tmp ) {
tmp = Number( tmp ).toPrecision( 4 );
}
return tmp + ' ' + mag + (si?'i':'') + 'B';
} else {
// si per default
var current = 0;
while( tmp >= 1024 ) {
tmp /= 1024;
++current;
}
tmp = this.value / Math.pow( 2, current * 10 );
if( parseInt( tmp, 10 ) !== tmp ) {
tmp = Number( tmp ).toPrecision( 4 );
}
return tmp + ' ' + Morebits.bytes.rmagnitudes[current] + ( current > 0 ? 'iB' : 'B' );
}
};





Line 1,186: Line 1,146:
*/
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
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
*/
Morebits.pageNameRegex = function(pageName) {
return '[' + pageName[0].toUpperCase() + pageName[0].toLowerCase() + ']' + pageName.slice(1);
};




Line 1,289: Line 1,259:




// Morebits.wikipedia.namespaces is deprecated - use mw.config.get('wgFormattedNamespaces') or mw.config.get('wgNamespaceIds') instead

/**
* **************** Morebits.wikipedia ****************
* English Wikipedia-specific objects
*/

Morebits.wikipedia = {};

Morebits.wikipedia.namespaces = {
'-2': 'Media',
'-1': 'Special',
'0': '',
'1': 'Talk',
'2': 'User',
'3': 'User talk',
'4': 'Project',
'5': 'Project talk',
'6': 'File',
'7': 'File talk',
'8': 'MediaWiki',
'9': 'MediaWiki talk',
'10': 'Template',
'11': 'Template talk',
'12': 'Help',
'13': 'Help talk',
'14': 'Category',
'15': 'Category talk',
'100': 'Portal',
'101': 'Portal talk',
'108': 'Book',
'109': 'Book talk',
'118': 'Draft',
'119': 'Draft talk',
'446': 'Education Program',
'447': 'Education Program talk',
'710': 'TimedText',
'711': 'TimedText talk',
'828': 'Module',
'829': 'Module talk'
};

Morebits.wikipedia.namespacesFriendly = {
'0': '(Article)',
'1': 'Talk',
'2': 'User',
'3': 'User talk',
'4': 'Wikipedia',
'5': 'Wikipedia talk',
'6': 'File',
'7': 'File talk',
'8': 'MediaWiki',
'9': 'MediaWiki talk',
'10': 'Template',
'11': 'Template talk',
'12': 'Help',
'13': 'Help talk',
'14': 'Category',
'15': 'Category talk',
'100': 'Portal',
'101': 'Portal talk',
'108': 'Book',
'109': 'Book talk',
'118': 'Draft',
'119': 'Draft talk',
'446': 'Education Program',
'447': 'Education Program talk',
'710': 'TimedText',
'711': 'TimedText talk',
'828': 'Module',
'829': 'Module talk'
};




/**
/**
Line 1,456: Line 1,354:
* @param {string} currentAction - The current action (required)
* @param {string} currentAction - The current action (required)
* @param {Object} query - The query (required)
* @param {Object} query - The query (required)
* @param {Function} onSuccess - The function to call when request gotten
* @param {Function} [onSuccess] - The function to call when request gotten
* @param {Object} [statusElement] - A Morebits.status object to use for status messages (optional)
* @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)
* @param {Function} [onError] - The function to call if an error occurs (optional)
Line 1,635: Line 1,533:
* of the page. Does not require calling load() first.
* of the page. Does not require calling load() first.
*
*
* move(onSuccess, [onFailure]): Moves a page to another title
* move([onSuccess], [onFailure]): Moves a page to another title
*
*
* deletePage(onSuccess, [onFailure]): Deletes a page (for admins only)
* deletePage([onSuccess], [onFailure]): Deletes a page (for admins only)
*
*
* undeletePage(onSuccess, [onFailure]): Undeletes a page (for admins only)
* undeletePage([onSuccess], [onFailure]): Undeletes a page (for admins only)
*
*
* protect(onSuccess, [onFailure]): Protects a page
* protect([onSuccess], [onFailure]): Protects a page
*
*
* getPageName(): returns a string containing the name of the loaded page, including the namespace
* getPageName(): returns a string containing the name of the loaded page, including the namespace
Line 1,668: Line 1,566:
* getCurrentID(): returns a string containing the current revision ID of the page
* getCurrentID(): returns a string containing the current revision ID of the page
*
*
* lookupCreator(onSuccess): Retrieves the username of the user who created the page
* lookupCreation(onSuccess): Retrieves the username and timestamp of page creation
* onSuccess - callback function which is called when the username is found
* onSuccess - callback function which is called when the username and timestamp
* within the callback, the username can be retrieved using the getCreator() function
* 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 lookupCreator()
* getCreator(): returns the user who created the page following lookupCreation()
*
* getCreationTimestamp(): returns an ISOString timestamp of page creation following lookupCreation()
*
*
*/
*/
Line 1,744: Line 1,646:
watchlistOption: 'nochange',
watchlistOption: 'nochange',
creator: null,
creator: null,
timestamp: null,


// - revert
// - revert
Line 1,780: Line 1,683:
onSaveSuccess: null,
onSaveSuccess: null,
onSaveFailure: null,
onSaveFailure: null,
onLookupCreatorSuccess: null,
onLookupCreationSuccess: null,
onMoveSuccess: null,
onMoveSuccess: null,
onMoveFailure: null,
onMoveFailure: null,
Line 1,796: Line 1,699:
loadApi: null,
loadApi: null,
saveApi: null,
saveApi: null,
lookupCreatorApi: null,
lookupCreationApi: null,
moveApi: null,
moveApi: null,
moveProcessApi: null,
moveProcessApi: null,
Line 1,814: Line 1,717:
* Loads the text for the page
* Loads the text for the page
* @param {Function} onSuccess - callback function which is called when the load has succeeded
* @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)
* @param {Function} [onFailure] - callback function which is called when the load fails (optional)
*/
*/
this.load = function(onSuccess, onFailure) {
this.load = function(onSuccess, onFailure) {
Line 1,965: Line 1,868:
* Adds the text provided via setAppendText() to the end of the page.
* Adds the text provided via setAppendText() to the end of the page.
* Does not require calling load() first.
* Does not require calling load() first.
* @param {Function} onSuccess - callback function which is called when the method has succeeded (optional)
* @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)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
*/
this.append = function(onSuccess, onFailure) {
this.append = function(onSuccess, onFailure) {
Line 1,983: Line 1,886:
* Adds the text provided via setPrependText() to the start of the page.
* Adds the text provided via setPrependText() to the start of the page.
* Does not require calling load() first.
* Does not require calling load() first.
* @param {Function} onSuccess - callback function which is called when the method has succeeded (optional)
* @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)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
*/
this.prepend = function(onSuccess, onFailure) {
this.prepend = function(onSuccess, onFailure) {
Line 2,249: Line 2,152:


/**
/**
* @returns {string} the user who created the page following lookupCreator()
* @returns {string} the user who created the page following lookupCreation()
*/
*/
this.getCreator = function() {
this.getCreator = function() {
Line 2,256: Line 2,159:


/**
/**
* @returns {string} the ISOString timestamp of page creation following lookupCreation()
* Retrieves the username of the user who created the page
*/
this.getCreationTimestamp = function() {
return ctx.timestamp;
};

/**
* Retrieves the username of the user who created the page as well as
* the timestamp of creation
* @param {Function} onSuccess - callback function (required) which is
* @param {Function} onSuccess - callback function (required) which is
* called when the username is found within the callback, the username
* called when the username and timestamp are found within the callback.
* can be retrieved using the getCreator() function
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
*/
*/
this.lookupCreator = function(onSuccess) {
this.lookupCreation = function(onSuccess) {
if (!onSuccess) {
if (!onSuccess) {
ctx.statusElement.error("Internal error: no onSuccess callback provided to lookupCreator()!");
ctx.statusElement.error("Internal error: no onSuccess callback provided to lookupCreation()!");
return;
return;
}
}
ctx.onLookupCreatorSuccess = onSuccess;
ctx.onLookupCreationSuccess = onSuccess;


var query = {
var query = {
Line 2,273: Line 2,185:
'titles': ctx.pageName,
'titles': ctx.pageName,
'rvlimit': 1,
'rvlimit': 1,
'rvprop': 'user',
'rvprop': 'user|timestamp',
'rvdir': 'newer'
'rvdir': 'newer'
};
};
Line 2,281: Line 2,193:
}
}


ctx.lookupCreatorApi = new Morebits.wiki.api("Retrieving page creator information", query, fnLookupCreatorSuccess, ctx.statusElement);
ctx.lookupCreationApi = new Morebits.wiki.api("Retrieving page creation information", query, fnLookupCreationSuccess, ctx.statusElement);
ctx.lookupCreatorApi.setParent(this);
ctx.lookupCreationApi.setParent(this);
ctx.lookupCreatorApi.post();
ctx.lookupCreationApi.post();
};
/**
* @deprecated since May/June 2019, renamed to lookupCreation
*/
this.lookupCreator = function(onSuccess) {
console.warn("NOTE: lookupCreator() from Twinkle's Morebits has been deprecated since May/June 2019, please use lookupCreation() instead"); // eslint-disable-line no-console
Morebits.status.warn("NOTE", "lookupCreator() from Twinkle's Morebits has been deprecated since May/June 2019, please use lookupCreation() instead");
return this.lookupCreation(onSuccess);
};
};


Line 2,316: Line 2,236:
/**
/**
* Reverts a page to revertOldID
* Reverts a page to revertOldID
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
*/
Line 2,335: Line 2,255:
/**
/**
* Moves a page to another title
* Moves a page to another title
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
*/
Line 2,374: Line 2,294:
/**
/**
* Deletes a page (for admins only)
* Deletes a page (for admins only)
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
*/
Line 2,415: Line 2,335:
/**
/**
* Undeletes a page (for admins only)
* Undeletes a page (for admins only)
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
*/
Line 2,453: Line 2,373:
/**
/**
* Protects a page (for admins only)
* Protects a page (for admins only)
* @param {Function} onSuccess - callback function to run on success
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
*/
Line 2,797: Line 2,717:
};
};


var fnLookupCreatorSuccess = function() {
var fnLookupCreationSuccess = function() {
var xml = ctx.lookupCreatorApi.getXML();
var xml = ctx.lookupCreationApi.getXML();


if ( !fnCheckPageName(xml) ) {
if ( !fnCheckPageName(xml) ) {
Line 2,809: Line 2,729:
return;
return;
}
}
ctx.timestamp = $(xml).find('rev').attr('timestamp');
ctx.onLookupCreatorSuccess(this);
if (!ctx.timestamp) {
ctx.statusElement.error("Could not find timestamp of page creation");
return;
}

ctx.onLookupCreationSuccess(this);
};
};


Line 3,165: Line 3,091:
/**
/**
* @constructor
* @constructor
* @param {HTMLDivElement} previewbox - the <div> element that will contain the rendered HTML
* @param {HTMLElement} previewbox - the element that will contain the rendered HTML,
* usually a <div> element
*/
*/
Morebits.wiki.preview = function(previewbox) {
Morebits.wiki.preview = function(previewbox) {
Line 3,175: Line 3,102:
* to render the specified wikitext.
* to render the specified wikitext.
* @param {string} wikitext - wikitext to render; most things should work, including subst: and ~~~~
* @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
* @param {string} [pageTitle] - optional parameter for the page this should be rendered as being on, if omitted it is taken as the current page
*/
*/
this.beginRender = function(wikitext, pageTitle) {
this.beginRender = function(wikitext, pageTitle) {
Line 3,581: Line 3,508:
* **************** Morebits.status ****************
* **************** Morebits.status ****************
*/
*/

/**
* @constructor
* Morebits.status.init() must be called before any status object is created, otherwise
* those statuses won't be visible.
* @param {String} text - Text before the the colon `:`
* @param {String} stat - Text after the colon `:`
* @param {String} [type='status'] - This parameter determines the font color of the status line,
* this can be 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
* The default is 'status'
*/


Morebits.status = function Status( text, stat, type ) {
Morebits.status = function Status( text, stat, type ) {
Line 3,592: Line 3,530:
};
};


/**
* Specify an area for status message elements to be added to
* @param {HTMLElement} root - usually a div element
*/
Morebits.status.init = function( root ) {
Morebits.status.init = function( root ) {
if( !( root instanceof Element ) ) {
if( !( root instanceof Element ) ) {
Line 3,621: Line 3,563:
node: null,
node: null,
linked: false,
linked: false,

/**
* Add the status element node to the DOM
*/
link: function() {
link: function() {
if( ! this.linked && Morebits.status.root ) {
if( ! this.linked && Morebits.status.root ) {
Line 3,627: Line 3,573:
}
}
},
},

/**
* Remove the status element node from the DOM
*/
unlink: function() {
unlink: function() {
if( this.linked ) {
if( this.linked ) {
Line 3,633: Line 3,583:
}
}
},
},

/**
* Create a document fragment with the status text
*/
codify: function( obj ) {
codify: function( obj ) {
if ( ! Array.isArray( obj ) ) {
if ( ! Array.isArray( obj ) ) {
Line 3,649: Line 3,603:


},
},

/**
* Update the status
* @param {String} status - Part of status message after colon `:`
* @param {String} type - 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
*/
update: function( status, type ) {
update: function( status, type ) {
this.stat = this.codify( status );
this.stat = this.codify( status );
Line 3,668: Line 3,628:
this.render();
this.render();
},
},

/**
* Produce the html for first part of the status message
*/
generate: function() {
generate: function() {
this.node = document.createElement( 'div' );
this.node = document.createElement( 'div' );
Line 3,675: Line 3,639:
this.target.appendChild( document.createTextNode( '' ) ); // dummy node
this.target.appendChild( document.createTextNode( '' ) ); // dummy node
},
},

/**
* Complete the html, for the second part of the status message
*/
render: function() {
render: function() {
this.node.className = 'tw_status_' + this.type;
this.node.className = 'tw_status_' + this.type;
Line 3,711: Line 3,679:
/**
/**
* 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 that they may re-use it
* @param {string} comments
* @param {string} message
*/
*/
Morebits.status.printUserText = function( comments, message ) {
Morebits.status.printUserText = function( comments, message ) {
Line 3,744: Line 3,714:


/**
/**
* **************** Morebits.checkboxClickHandler() ****************
* **************** Morebits.checkboxShiftClickSupport() ****************
* shift-click-support for checkboxes
* shift-click-support for checkboxes
* wikibits version (window.addCheckboxClickHandlers) has some restrictions, and
* wikibits version (window.addCheckboxClickHandlers) has some restrictions, and
Line 3,784: Line 3,754:


for (i = start; i <= finish; i++) {
for (i = start; i <= finish; i++) {
cbs[i].checked = endState;
if (cbs[i].checked !== endState) {
cbs[i].click();
}
}
}
}
}
Line 3,845: Line 3,817:
// internal counters, etc.
// internal counters, etc.
statusElement: new Morebits.status(currentAction || "Performing batch operation"),
statusElement: new Morebits.status(currentAction || "Performing batch operation"),
worker: null,
worker: null, // function that executes for each item in pageList
postFinish: null, // function that executes when the whole batch has been processed
countStarted: 0,
countStarted: 0,
countFinished: 0,
countFinished: 0,
Line 3,880: Line 3,853:


/**
/**
* Runs the given callback for each page in the list.
* Runs the first callback for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* Runs the second callback when the whole batch has been processed (optional)
* @param {Function} worker
* @param {Function} worker
* @param {Function} [postFinish]
*/
*/
this.run = function(worker) {
this.run = function(worker, postFinish) {
if (ctx.running) {
if (ctx.running) {
ctx.statusElement.error("Batch operation is already running");
ctx.statusElement.error("Batch operation is already running");
Line 3,892: Line 3,867:


ctx.worker = worker;
ctx.worker = worker;
ctx.postFinish = postFinish;
ctx.countStarted = 0;
ctx.countStarted = 0;
ctx.countFinished = 0;
ctx.countFinished = 0;
Line 3,974: Line 3,950:
} else {
} else {
ctx.statusElement.info(statusString);
ctx.statusElement.info(statusString);
}
if (ctx.postFinish) {
ctx.postFinish();
}
}
Morebits.wiki.removeCheckpoint();
Morebits.wiki.removeCheckpoint();