Bahaipedia
Bahaipedia
Menu
About Bahaipedia
Ask a question
General help
Random page
Recent changes
In other projects
Tools
What links here
Related changes
Upload file
Special pages
Printable version
Permanent link
Page information
Message
Discussion
View source
View history
Talk
Contributions
Create account
Log in
Navigation
About Bahaipedia
Ask a question
General help
Random page
Recent changes
In other projects
Learn more
Core topics
Bahá’í Faith
Central Figures
Teachings
Practices
Tools
What links here
Related changes
Upload file
Special pages
Printable version
Permanent link
Page information
Translations

MediaWiki:Gadget-SettingsManager.js

From Bahaipedia
Jump to:navigation, search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * [[:commons:MediaWiki:Gadget-SettingsManager.js]]
 * Managing user preferences of scripts
 * Managing gadgets and gadget preferences
 *
 * Use it for good, not for evil.
 *
 * @author Rillke, 2012
 * @license GPL v.3
 * <nowiki>
 */
 
// List the global variables for jsHint-Validation.
// Scheme: globalVariable:allowOverwriting[, globalVariable:allowOverwriting][, globalVariable:allowOverwriting]
/*global jQuery:false, mediaWiki:false*/

// Set jsHint-options.
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:false, browser:true*/

( function ( $, mw ) {
"use strict";

// Different tokes exist only to confuse the user (at least in 2012)
// All of them carry the same value except the watchlist token
if (!mw.user.tokens.exists('preferencesToken')) mw.user.tokens.set('preferencesToken', mw.user.tokens.get('editToken'));

/**
* Refresh preferences-token
*
* @example
*      refreshToken( function() { doGoodStuff.retry(); } );
*
* @param cb {Function} Callback function. The first argument supplied is whether the operation succeeded.
* @context {closure} private function
* @return {Object} a jQuery deferred-object-queue
*/
var refreshToken = function(cb) {
	var mwa = new mw.Api(),
		apiDef = mwa.get( {
			meta: 'userinfo',
			uiprop: 'preferencestoken'
		} );
		
	apiDef.done(function(result) {
		if (!result.query || !result.query.userinfo) return cb( false, 'wrong-response' );
		mw.user.tokens.set( 'preferencesToken', result.query.userinfo.preferencestoken );
		cb( true );
	});
	apiDef.fail(function(code, result) {
		if (!result.query || !result.query.userinfo) return cb( false, code );
	});
	return apiDef;
};

var firstItem = function(o) { 
	for (var i in o) {
		if (o.hasOwnProperty( i )) { 
			return o[i]; 
		} 
	} 
};

var valByString = function(identifier) {
	var arr = identifier.split( '.' ),
		lenArr = arr.length,
		i,
		elemArr,
		objCurrent = window;
		
	for (i = 0; i < lenArr; i++) {
		elemArr = arr[i];
		objCurrent = objCurrent[elemArr];
	}
	return objCurrent;
};

var mwPrefPrefix = 'userjs-sm-';

var sm = {
	version: '0.1.0.1',
	errorPrefix: "SettingsWizard encountered a problem. We regret the inconvenience. ",
	/**
	* Constructor-method. Returns an option-object you should perform the actions on.
	*
	* @example
	*      var opt = mw.libs.settingsManager.option( { optionName: 'CatALotOptions', value: { watchCopy: false, watchRemove: false } } );
	*      // Save the options we've set before and make the script triggering events on the document, you can listen to
	*      opt.save( $(document), 'CatALotSaveProgress' );
	*      // or, use the deferred-object returned:
	*      opt.save().done(function(msg, status, jsFile) { 
	*          alert( msg );
	*      }).status(function(msg, status, jsFile) { 
	*          console.log( 'settings progress>' + msg );
	*      });
	*
	* @param specsIn {Object} specifications passed in. List of defaults (cf. specs) follows.
	* @context {mw.libs.settingsManager}
	* @return {Object} option-object you can use for performing actions on.
	*/
	option: function(specsIn) {
		// List of defaults:
		var specs = {
			// The global name the option will be saved under
			// This can be also something like "mw.settingsOfToolX"
			optionName: '',
			// The position where to save them. By default options are
			// saved at the user's common.js or <skin>.js (e.g. vector.js)
			// specify other locations like "settingsOfToolX" --> "User:Example/prefs/settingsOfToolX.js"
			saveAt: false,
			// By default the option is not enclosed in a comment-block
			// Comment-blocks are recommended for larger configurations
			// specifies the signature that will be used for the enclosing comments
			// blockSettingsOfToolX --> //blockSettingsOfToolX///////////////////////
			// (ignored when saveAt is set)
			encloseSignature: false,
			// Specify additional block comments added below the signature
			// Should be something that explains what the following JSON does or is good for
			// Recommended line-length for consistent alignment: 48 chars
			encloseBlock: false,
			// If no own location for saving the option is used (options that must be
			// available while loading the script should not be saved to a separate file while 
			// complex options should), if the RegExp will have a match on either the common.js
			// or the <skin>.js, and this option is not saved yet to another .js, the option
			// will be saved to this js-file. In case the option is not saved yet and there is
			// no RegExp supplied or it did not match, the option will be saved to the larger
			// JavaScript
			triggerSaveAt: false,
			// Should the new content be insered in front of the match by triggerSaveAt
			// If none of the following options is specified, the new content 
			// will be appended to the script
			insertBeforeTrigger: false,
			insertAfterTrigger: false,
			replaceTrigger: false,
			// Finally the option's value. Objects are possible. They will be automatically
			// transformed into a JSON-string
			value: undefined,
			// Edit summary to use while saving the JavaScript
			editSummary: ""
		};
		if (!specsIn) throw new Error(sm.errorPrefix + "Data to save or retrieve was not supplied by the script using SettingsWizard.");
		if (!specsIn.optionName && !specsIn.saveAt) throw new Error(sm.errorPrefix + "The options\'s name was not supplied by the script using SettingsWizard.");
		$.extend( true, specs, specsIn );
		
		// Prepare variables we need later
		var nsUser   = mw.config.get('wgFormattedNamespaces')[2],
			skin     = mw.config.get('skin'),
			user     = mw.config.get('wgUserName'),
			skinJS   = [nsUser, ':', user, '/', skin, '.js'].join(''),
			commonJS = [nsUser, ':', user, '/','common', '.js'].join('');
		
		// Event-handler system
		var $el, evt, jsFiles, process, $progress = new $.Deferred(), customJS;
		var triggerEvt = function(any) {
			return (evt && $el && $el instanceof jQuery && $el.triggerHandler( evt, Array.prototype.slice.call( arguments, 0 ) ));
		};
		
		process = {
			updateVars: function() {
				// Reset variables that could be polluted
				jsFiles = [];
				$progress = new $.Deferred();
				customJS = [nsUser, ':', user, '/prefs/', specs.saveAt, '.js'].join('');
			},
			start: function() {
				this.updateVars();
				
				// Subscribe to any event: We want to know everything :-)
				$progress.then( triggerEvt, triggerEvt, triggerEvt );
				
				// Always async
				setTimeout( $.proxy( this.getScripts, this ), 1 );
				
				return $progress;
			},
			getScripts: function() {
				var i, len;
				
				$progress.notify( "Preparing", 1 );
				
				// First, we need something to work on/ edit token, etc. - request the JavaScript(s)
				if (specs.saveAt) {
					jsFiles.push( sm.script( customJS ) );
				} else {
					jsFiles.push( sm.script( skinJS ) );
					jsFiles.push( sm.script( commonJS ) );
				}
				len = jsFiles.length;
				for (i = 0; i < len; i++) {
					var jsFile = jsFiles[i];
					jsFile.fetchText( process.gotJS, process.gotJSErr );
					$progress.notify( "Requesting " + jsFile.getSource(), Math.round( (i+1)*(9/len) ) + 1, jsFile );
				}
				return $progress;
			},
			gotJS: function(jsFile, r){
				jsFile.gotContent = true;
				
				var i, len = jsFiles.length, pendings = 0;
				for (i = 0; i < len; i++) {
					if (!jsFiles[i].gotContent) {
						pendings++;
					}
				}
				$progress.notify( "Got " + jsFile.getSource() + '. File length: ' + jsFile.get().length + ' characters.' , Math.round( (len - pendings)*(9/len) ) + 10, jsFile );
				
				if (pendings) return;
				process.process();
			},
			gotJSErr: function(jsFile) {
				$progress.reject( "Failed. Could not retrieve " + jsFile.getSource(), -1, jsFile );
			},
			getStartBlock: function(sig) {
				// String concat is sloooow
				return '//' + sig + new Array(48 - 2 - sig.length + 1).join('/');
			},
			getEndBlock: function(sig) {
				return new Array(48 - 2 - 3 - sig.length + 1).join('/') + sig + 'End' + '//';
			},
			getBlockRegExp: function(sig) {
				var escSig = process.escapeRE(sig);
				return new RegExp('\\n?\\n?\\/\\/' + escSig + '(?:.|\\n)*' + escSig + 'End\\/\\/', 'g');
			},
			escapeRE: function(string) {
				string = mw.RegExp.escape(string);
				
				var specials = ['t', 'n', 'v', '0', 'f'];
				$.each(specials, function(i, s) {
					var rx = new RegExp('\\'+s, 'g');
					string = string.replace(rx, '\\'+s);
				});
				return string;
			},
			getVariableRegExp: function(varName) {
				var escVar = process.escapeRE(varName);
				return {
					varRE: new RegExp('\\s*(?:var\\s+|window\\.)?' + escVar + '\\s*=.+', 'g'),
					// Throw a warning if the last char of the line is a "+" , "{", "(" or ","
					varWarnRE: new RegExp('\\s*(?:var\\s+|window\\.)?' + escVar + '\\s*=.+(?:\\n?\\s*[\\,\\+\\{\\(])\\s*\\n')
				};
			},
			process: function() {
				var JSONVal = JSON.stringify( specs.value ),
					sig = specs.encloseSignature,
					tsa = specs.triggerSaveAt,
					opn = specs.optionName,
					jsFile, i, len = jsFiles.length,
					plainJSON = !opn && !!jsFile,
					oldText, newText, hadMatch;
				
				if (opn) {
					// No semicolon for valid JSON!
					JSONVal = 'window.' + opn + ' = ' + JSONVal + ';';
				}
				
				if (!plainJSON) JSONVal = ((specs.encloseBlock && ('\n' + specs.encloseBlock)) || '') + JSONVal;
				
				if (sig && !plainJSON) JSONVal = process.getStartBlock( sig ) + JSONVal + '\n' + process.getEndBlock( sig );
				
				JSONVal = '\n\n' + JSONVal;
				
				// Fine, we've constructed everything we'll need. Now look up where to insert.
				// Looking for signature
				if (sig) {
					var reBl = process.getBlockRegExp( sig );
						
					for (i = 0; i < len; i++) {
						jsFile = jsFiles[i];
						oldText = jsFile.get();
						newText = oldText.replace( reBl, JSONVal );
						if (reBl.test( oldText )) {
							$progress.notify( "Replacing text enclosed by signature " + jsFile.getSource(), 25, jsFile );
							process.save( jsFile.set( newText ) );
							hadMatch = true;
						}
					}
				}
				if (hadMatch) return;
				
				// Looking for variable-name
				if (opn) {
					var vre = process.getVariableRegExp( opn ),
						warnFile;
					
					for (i = 0; i < len; i++) {
						jsFile = jsFiles[i];
						oldText = jsFile.get();
						
						if (vre.varWarnRE.test(oldText)) {
							// WARNING!!!
							$progress.notify( "Unable to remove config from " + jsFile.getSource(), -2, jsFile );
							warnFile = jsFile;
						} else {
							newText = oldText.replace( vre.varRE, JSONVal );
							if (vre.varRE.test( oldText )) {
								$progress.notify( "Replacing variable " + jsFile.getSource(), 25, jsFile );
								process.save( jsFile.set( newText ) );
								hadMatch = true;
							}
						}
						// Only append in case of warning if it was not added to another file
						if (warnFile && !hadMatch) {
							$progress.notify( "Appending variable after warning to " + jsFile.getSource(), 25, jsFile );
							process.save( warnFile.set( oldText + JSONVal ) );
							hadMatch = true;
						}
					}
				}
				if (hadMatch) return;
				
				// If it's just JSON, replace the whole thingy
				if (!opn && specs.saveAt) {
					jsFile = jsFiles[0];
					$progress.notify( "Replacing whole content of " + jsFile.getSource(), 25, jsFile );
					process.save( jsFile.set( JSONVal ) );
					hadMatch = true;
				}
				if (hadMatch) return;
				
				// Looking whether supplied RegExp can find something
				if (tsa) {
					var searchMatch,
						triggerLen = 0;
					
					for (i = 0; i < len; i++) {
						jsFile = jsFiles[i];
						oldText = jsFile.get();
						
						searchMatch = oldText.search( tsa );
						if (-1 !== searchMatch) {
							if (specs.insertBeforeTrigger) {
								$progress.notify( "Inserting before pattern in " + jsFile.getSource(), 25, jsFile );
								jsFile.set( oldText.slice( 0, searchMatch ) + JSONVal + oldText.slice( searchMatch ) );
							} else if (specs.insertAfterTrigger) {
								triggerLen = oldText.match( tsa )[0].length;
								$progress.notify( "Inserting after pattern in " + jsFile.getSource(), 25, jsFile );
								jsFile.set( oldText.slice( 0, searchMatch + triggerLen ) + JSONVal + oldText.slice( searchMatch + triggerLen ) );
							} else if (specs.replaceTrigger) {
								$progress.notify( "Replacing pattern with new content in " + jsFile.getSource(), 25, jsFile );
								jsFile.set( oldText.replace( tsa, JSONVal ) );
							} else {
								$progress.notify( "Found pattern, appending to " + jsFile.getSource(), 25, jsFile );
								jsFile.set( oldText + '\n//<nowiki>' + JSONVal + '\n//<\/nowiki>' );
							}
							process.save( jsFile );
							hadMatch = true;
							break;
						}
					}
				}
				if (hadMatch) return;
				
				// Finally compare file size
				var biggest = { size: 0, jsFile: null };
					
				for (i = 0; i < len; i++) {
					jsFile = jsFiles[i];
					oldText = jsFile.get();
					var oldTextLen = oldText.length;
					
					if (oldTextLen >= biggest.size) biggest = {
						size: oldTextLen,
						jsFile: jsFile
					};
				}
				$progress.notify( "Appending to bigger file: " + biggest.jsFile.getSource(), 25, biggest.jsFile );
				biggest.jsFile.set( biggest.jsFile.get() + '\n//<nowiki>' + JSONVal + '\n//<\/nowiki>' );
				process.save( biggest.jsFile );
			},
			save: function(jsFile) {
				jsFile.saving = true;
				$progress.notify( "Saving " + jsFile.getSource(), 30, jsFile );
				jsFile.save( process.saved, process.savedErr, "[[MediaWiki:Gadget-SettingsManager.js|SettingsManager]]: " + specs.editSummary );
			},
			saved: function(jsFile) {
				var i, len = jsFiles.length, jsf, waitingFor = [];
				
				jsFile.saving = false;
				
				for (i = 0; i < len; i++) {
					jsf = jsFiles[i];
					if (jsf.saving) {
						waitingFor.push(jsf.getSource());
					}
				}
				$progress.notify( "Saved " + jsFile.getSource() + ". Waiting for " + (waitingFor.join(', ') || '-'), Math.round( (len - waitingFor.length)*(20/len) ) + 50,  jsFile );
				
				if (waitingFor.length) return;
				$progress.resolve( "Success!", 100, jsFile );
			},
			savedErr: function(jsFile, code, errObj) {
				$progress.reject( "Error saving " + jsFile.getSource() + ". Code is " + code + ".\n", -1, errObj );
			}
		};
		
		return {
			getSpecs: function() {
				return specs;
			},
			setSpecs: function(specsIn) {
				specs = specsIn;
				return this;
			},
			// Warning: If you specified a different save-position ("saveAt")
			// and also an optionName, the script has to be fetched and evaluated
			// We recommend omitting setting "optionName" when using "saveAt"
			fetchValue: function(cb, errCb) {
				process.updateVars();
				
				if (specs.saveAt) {
					var s = sm.script( customJS );
					if (specs.optionName) {
						s.fetchText(function() {
							s.doEval();
							cb( valByString( specs.optionName ) );
						}, errCb);
					} else {
						s.fetchJSON(function(scriptObj, JSON) {
							cb( JSON );
						}, errCb);
					}
					return this;
				}
				cb( valByString( specs.optionName ) );
				return this;
			},
			getValue: function() {
				return specs.value;
			},
			setValue: function(val) {
				specs.value = val;
				return this;
			},
			save: function($elem, event) {
				// We won't check whether the value is undefined. This is your task.
				$el = $elem;
				evt = event;
				return process.start();
			},
			getProgress: function() {
				return $progress;
			}
		};
	},
	/**
	* Constructor-method. Returns a script-object you should perform the actions on.
	*
	* @example
	*      var commonJS = mw.libs.settingsManager.script( 'User:Example/common.js' );
	*      commonJS.set( '// empty!' ).setSummary( 'Removing Content' ).save( function() { console.log( 'Successfully removed content from ' + commonJS.getSource() ) } )
	*
	* @param source {String} The name of the JavaScript file with namespace.
	* @context {mw.libs.settingsManager}
	* @return {Object} script-object you can use for performing actions on.
	*/
	script: function(source) {
		var content,
			page,
			summary = "Changing configuration using [[:commons:MediaWiki:Gadget-SettingsManager.js]]",
			minor = 1,
			exists,
			fetch,
			save;

		fetch = function() {
			var mwa = new mw.Api();
			return mwa.get( {
				prop: 'info|revisions',
				titles: source,
				rvprop: 'timestamp|content',
				intoken: 'edit'
			} );
		};
		
		save = function() {
			var mwa = new mw.Api(),
				edit = {
					action: 'edit',
					title: source,
					text: 'object' === typeof content ? JSON.stringify(content) : content,
					summary: summary,
					watchlist: 'nochange',
					recreate: 1
				};
				
			if (minor) edit.minor = 1;
			if (exists) {
				edit.basetimestamp = page.revisions[0].timestamp;
			} else {
				edit.starttimestamp = page.starttimestamp;
			}
			
			edit.token = page.edittoken;
			return mwa.post( edit );
		};
			
		return {
			get: function() {
				return content;
			},
			getSource: function() {
				return source;
			},
			doEval: function() {
				/*jshint evil:true */
				return eval(content);
			},
			parseJSON: function() {
				return ('string' === typeof content && content !== '') ? JSON.parse( content ) : '';
			},
			// Supplied callback called with a string as second argument
			fetchText: function(cb, errCb) {
				var pgs, pg, scriptObj = this;
				
				fetch().done( function(result) {
					pgs = result.query.pages;
					page = firstItem( pgs );
					exists = !!(page.revisions && page.revisions[0]);
					content = (exists && page.revisions[0]['*']) || '';
					cb( scriptObj, content );
				} ).fail( function( status, errObj ) {
					errCb( scriptObj, status, errObj );
				} );
				return this;
			},
			// Supplied callback called with parsed JSON-data as second argument
			fetchJSON: function(cb, errCb) {
				this.fetchText( function(scriptObj, content) {
					cb( scriptObj, scriptObj.parseJSON() );
				}, function(scriptObj, status, errObj) {
					errCb( scriptObj, status, errObj );
				} );
				return this;
			},
			set: function(newContent) {
				content = newContent;
				return this;
			},
			setMinor: function(newMinor) {
				minor = !!newMinor;
			},
			setSummary: function(newSummary) {
				summary = newSummary;
			},
			save: function(cb, errCb, newSummary, newContent, newMinor) {
				var scriptObj = this;
				if (newContent !== undefined) content = newContent;
				if (newSummary !== undefined) summary = newSummary;
				if (newMinor !== undefined) minor = !!newMinor;
				save().done( function(result) {
					cb( scriptObj, result );
				} ).fail( function(status, errObj) {
					errCb( scriptObj, status, errObj );
				} );
				return this;
			}
		};
	},
	/**
	* Switch a user preference using Ajax!
	*
	* @example
	*      mw.libs.settingsManager.switchPref( 'myOption', 'new value' );
	*
	* @param prefName {String} The name of the preference.
	* @param prefVal {String} The new value the preference should set to.
	* @param cb {Function} Callback in case of success.
	* @param cb {Function} Callback in case of an error.
	* @context {mw.libs.settingsManager}
	* @return {Object} a jQuery deferred-object-queue. Don't use it for error-handling - Done by this method.
	*/
	switchPref: function(prefName, prefVal, cb, errCb) {
		var mwa = new mw.Api(),
			args = arguments,
			prefString = (typeof prefVal === 'object') ? JSON.stringify(prefVal) : prefVal,
			apiDef = mwa.post( {
				action: 'options',
				token: mw.user.tokens.get('preferencesToken'),
				optionname: prefName,
				optionvalue: prefString || 0
			} );

		// If we changed a preference successfully, update user.options reflecting the change
		apiDef.done( function() {
			mw.user.options.set( prefName, prefString );
		} );
		if (cb) apiDef.done( cb );
		// Catch badtoken and some other common errors
		apiDef.fail( function(code, result) {
			switch (code) {
				case 'badtoken':
					refreshToken(function (gotANewToken) {
						if (gotANewToken) return sm.switchPref.apply( sm, Array.prototype.slice.call( args, 0 ) );
					} );
					// Stop the propagation of 
					return false;
				case 'http':
				case 'ok-but-empty':
					setTimeout( function() {
						return sm.switchPref.apply( sm, Array.prototype.slice.call(args, 0) );
					}, 2500 );
					return false;
				default:
					return (errCb && errCb(code, result) && false);
			}
		} );
		return apiDef;
	},
	/**
	* Switch a gadget preference using Ajax!
	*
	* @example
	*      mw.libs.settingsManager.switchGadgetPref( 'myOption', 'new value' ).done(function() { console.log("DONE!") });
	*
	* @param prefName {String} The name of the preference.
	* @param prefVal {String} The new value the preference should set to.
	* @context {mw.libs.settingsManager}
	* @return {Object} $.Deferred; a jQuery deferred-object-queue.
	*/
	switchGadgetPref: function(prefName, prefVal) {
		var $def = $.Deferred();

		sm.switchPref( mwPrefPrefix + prefName, prefVal, $.proxy( $def.resolve, $def ), $.proxy( $def.reject, $def ) );
		return $def;
	},
	
	/**
	* Fetch a Gadget preference from various sources!
	*
	* @example
	*      mw.libs.settingsManager.fetchGadgetSetting( 'mySetting', ['storage', 'option'] ).done(function(prefName, settingValue) { console.log("DONE!") });
	*
	* @param prefName {String} The name of the preference.
	* @param prefSources {Array} One or more of the following values 'storage', 'cookie', 'option', 'window'. Default (if not passed): 
	*                             All in the order listed there. Note that they are processed in the order you pass them in and as soon as one is found,
	*                             Script will return.
	*                             IMPORTANT: The Array is changed while processing. So make a copy if you need it again before passing it.
	*
	* @context {mw.libs.settingsManager}
	* @return {Object} $.Deferred; a jQuery deferred-object-queue.
	*/
	fetchGadgetSetting: function(prefName, prefSources) {
		var $def = $.Deferred(), requires = [], options = {
			'storage': {
				requires: ['jquery.jStorage'],
				fetch: function() {
					var v = $.jStorage.get( prefName );
					return (null === v || undefined === v) ? undefined : v;
				}
			},
			'cookie': {
				requires: ['jquery.cookie'],
				fetch: function() {
					var v = $.cookie( prefName );
					try {
						v = JSON.parse( v );
					} catch(invalidJSON) {}
					return (null === v || undefined === v) ? undefined : v;
				}
			},
			'option': {
				requires: ['mediawiki.user', 'user.options'],
				fetch: function() {
					var v = mw.user.options.get( mwPrefPrefix + prefName );
					try {
						v = JSON.parse( v );
					} catch(invalidJSON) {}
					return (null === v || undefined === v) ? undefined : v;
				}
			},
			'window': {
				requires: [],
				fetch: function() {
					return window[prefName];
				}
			}
		};
		if (!prefSources) prefSources = [];
		if (!prefSources.length) prefSources = ['storage', 'cookie', 'option', 'window'];
		
		var _fetch = function(s) {
				var so = options[s];
				if (so) {
					mw.loader.using( so.requires, function() {
						var v = so.fetch();
						if (undefined === v) {
							_fetched();
						} else {
							$def.resolve( prefName, v );
						}
					} );
				} else {
					// Security guard: Don't load settings from unprotected pages
					if (!/^(?:User\:|MediaWiki\:).+\.js$/.test( s )) _fetched();
					sm.script( s ).fetchJSON( function(me, jsonData) {
						if (jsonData) {
							$def.resolve( prefName, jsonData );
						} else {
							_fetched();
						}
					}, $.proxy( $def.reject, $def ) );
				}
			},
			_fetched = function() {
				prefSources.shift();
				if (prefSources.length) {
					_fetch( prefSources[0] );
				} else {
					$def.resolve( prefName /* no pref found */ );
				}
			};
			
		$.each( prefSources, function(i, s) {
			var so = options[s];
			if (so) requires = requires.concat( options[s].requires );
		} );
		mw.loader.load( requires );
			
		// ensure async
		setTimeout( function() {
			_fetch(prefSources[0]);
		}, 10 );
		
		return $def;
	},
	
	/**
	* Constructor-method. Returns an option-object you should perform the actions on.
	*
	* @example
	*      var slideshowGadget = mw.libs.settingsManager.gadget( 'Slideshow' );
	*      if (slideshowGadget.isEnabled()) { slideshowGadget.disable( myCallback ) }
	*
	*      // Enable a gadget and load it:
	*      mw.libs.settingsManager.gadget( 'Slideshow' ).load().enable();
	*
	* @param gadgetName {Object} The name of the gadget. (Not the script file; without Gadget- prefix or other decoration)
	* @context {mw.libs.settingsManager}
	* @return {Object} gadget-object you can use for performing actions on.
	*/
	gadget: function(gadgetName) {
		var optGadget = 'gadget-' + gadgetName,
			rlGadget = 'ext.gadget.' + gadgetName;
			
		return {
			getName: function() {
				return gadgetName;
			},
			isDefault: function() {
				var opt = mw.user.options.get( optGadget );
				return ('number' === typeof opt || '' === opt);
			},
			isEnabled: function() {
				var opt = mw.user.options.get( optGadget );
				return !!opt;
			},
			getState: function() {
				return mw.loader.getState( rlGadget );
			},
			isLoaded: function() {
				return ('ready' === this.getState());
			},
			load: function(cb, errCb) {
				// Always async
				if (this.isLoaded && cb) return setTimeout( function() {
					cb( gadgetName, true );
				}, 1 );
				mw.loader.using( rlGadget, 
					cb ? function() { 
						cb( gadgetName ); 
					} : undefined, 
					errCb ? function() { 
						errCb( gadgetName ); 
					} : undefined 
				);
				return this;
			},
			enable: function(cb, errCb) {
				// Type wouldn't matter due to URL-encoding but we also want to update
				// the user.options object
				sm.switchPref( optGadget, this.isDefault() ? 1 : '1', cb, errCb );
				return this;
			},
			disable: function(cb, errCb) {
				sm.switchPref( optGadget, '', cb, errCb );
				return this;
			}
		};
	}
};


mw.libs.settingsManager = sm;

// TODO add to gadget-def
// mw.loader.load(['json', 'mediawiki.user', 'user.options', 'user.tokens']);
}( jQuery, mediaWiki ));
Retrieved from "https://bahaipedia.org/index.php?title=MediaWiki:Gadget-SettingsManager.js&oldid=53948"
This page was last edited on 6 August 2017, at 17:54.
Text is available under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.
Privacy policy
About Bahaipedia
Disclaimers
Powered by MediaWiki