MediaWiki:Gadget-GadgetsPreferences.js

Note : après avoir enregistré la page, vous devrez forcer le rechargement complet du cache de votre navigateur pour voir les changements.

Mozilla / Firefox / Konqueror / Safari : maintenez la touche Majuscule (Shift) en cliquant sur le bouton Actualiser (Reload) ou pressez Maj-Ctrl-R (Cmd-R sur Apple Mac) ;

Chrome / Internet Explorer / Opera : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5.
/**
 * GadgetsPreferences
 *
 * Library to make a gadget easily configurable by its users:
 * - Declaration of preferences from within the code;
 * - Storage in the user preferences (no edits are made);
 * - Easy to use interface and common to all gadgets.
 * 
 * It is not made to be activated directly by the user, but rather loaded
 * as a dependency by other gadgets and/or user scripts.
 *
 * {{Projet:JavaScript/Script|GadgetsPreferences}}
 * <nowiki>
 */

/* globals mw, OO, $ */

mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ], function () {
	'use strict';

	// Site-related parameters
	const EDITPAGE = 'MediaWiki:Préférences_des_Gadgets';
	const PREFIX = 'gprefs';

	// I18N messages
	const messages = {
		'fr': {
			'gprefs-portlet-label': '(gadgets)',
			'gprefs-portlet-tooltip': 'Accéder aux préférences des gadgets',
			'gprefs-savebutton-label': 'Enregistrer',
			'gprefs-notif-success': 'Les préférences des gadgets ont été sauvegardées. La page va être rechargée pour les appliquer.',
			'gprefs-notif-error': '',
		}
	};
	mw.messages.set( messages.fr );
	var lang = mw.config.get( 'wgUserLanguage' );
	if ( lang !== 'fr' && lang in messages ) {
		mw.messages.set( messages[ lang ] );
	}
	
	/**
	 * Widget displaying a color picker.
	 * 
	 * @constructor
	 * @inherit
	 */
	var ColorInputWidget = function ( config ) {
		// Configuration initialization
		config = Object.assign( {
			type: 'color',
		}, config);

		// Parent constructor
		ColorInputWidget.parent.call( this, config );

		// Properties
		this.type = 'color';
		this.readOnly = false;
		this.required = false;
		this.validate = null;

		// Initialization
		this.$element
			.addClass( 'oo-ui-textInputWidget oo-ui-textInputWidget-type-' + this.type )
			.append( this.$icon, this.$indicator );
		this.$input.attr( 'type', config.type );
	};

	
	/**
	 * Manage user-configurable preferences.
	 * 
	 * @constructor
	 * @param {boolean} loadUI Set to true to display the configuration interface on the current page.
	 */
	var GadgetsPreferences = function ( loadUI ) {
		// Properties
		this.loadUI = loadUI || false;
		this.api = new mw.Api( { timeout: 7000 } );
		this.config = {};
		this.tabs = {};
		
		// Graphical properties
		this.layout;
		this.saveButton;
		this.ui;
		
		if ( this.loadUI ) {
			OO.inheritClass( ColorInputWidget, OO.ui.InputWidget );
			this.createUI();
		}
	};

	/**
	 * Allows a gadget to register preferences that should be user-configurable.
	 * 
	 * The 'prefs' object should look like this:
	 * {
	 *		pref1: { label: 'Preference 1', type: 'string', default: 'This is the default value' },
	 *		secondpref: { label: 'Second preference', type: 'boolean', default: true, help: 'This is an optional help message.' },
	 *		lastone: { label: 'Last one', type: 'number', default: 42, min: 0, max: 101 }
	 * }
	 * 
	 * Type should be one of those: string, number, boolean
	 * 
	 * @param {string} gadgetID Internal identifier used to identify the gadget.
	 * @param {string} label Text displayed in the UI to identify the gadget.
	 * @param {Object} prefs Object listing all the preferences of the gadget.
	 * @return {Object} Object binding prefIDs to their configured values.
	 */
	GadgetsPreferences.prototype.register = function ( gadgetID, label, prefs ) {
		var prefID, pref,
			result = {};
		
		this.config[ gadgetID ] = prefs;
		
		if ( this.loadUI ) {
			this.createTab( gadgetID, label );
		}
		
		for ( prefID in this.config[ gadgetID ] ) {
			pref = this.config[ gadgetID ][ prefID ];
			
			pref.value = mw.user.options.get( 'userjs-' + PREFIX + '-' + gadgetID + '-' + prefID );
			switch( pref.type ) {
				case 'boolean':
					if ( pref.value === 'true' ) {
						pref.value = true;
					} else if ( pref.value === 'false' ) {
						pref.value = false;
					} else {
						pref.value = null;
					}
					break;

				case 'number':
					pref.value = parseFloat( pref.value );
					break;
			}
			if ( pref.value === null || pref.value === undefined || Number.isNaN( pref.value ) ) {
				pref.value = pref.default;
			}
			result[ prefID ] = pref.value;
			
			if ( this.loadUI ) {
				this.createField( gadgetID, prefID );
			}
		}
		
		return result;
	};

	/**
	 * Get the value of a preference, previously registred.
	 * 
	 * This will either be the value configured by the user, or the default one
	 * if not.
	 * 
	 * @param {string} gadgetID Internal identifier of the current gadget.
	 * @param {string} prefID Internal identifier of the preference to get.
	 * @return {Mixed} the value of the given preference.
	 */
	GadgetsPreferences.prototype.get = function ( gadgetID, prefID ) {
		if ( this.config[ gadgetID ] === undefined || this.config[ gadgetID ][ prefID ] === undefined ) {
			return undefined;
		}
		
		return this.config[ gadgetID ][ prefID ].value;
	};

	/**
	 * Set the value of a preference, previously registred, and save it.
	 * 
	 * @param {string} gadgetID Internal identifier of the current gadget.
	 * @param {string} prefID Internal identifier of the preference to set.
	 * @param {Mixed} value New value for the given preference.
	 * @return {jQuery.Promise} the value of the given preference.
	 */
	GadgetsPreferences.prototype.set = function ( gadgetID, prefID, value ) {
		if ( this.config[ gadgetID ] === undefined || this.config[ gadgetID ][ prefID ] === undefined ) {
			return false;
		}
		
		this.config[ gadgetID ][ prefID ].value = value;
		
		return this.api.saveOption( 'userjs-' + PREFIX + '-' + gadgetID + '-' + prefID, value );
	};

	/**
	 * Create an empty IndexLayout and a save button as the bases of the UI.
	 * 
	 * @private
	 */
	GadgetsPreferences.prototype.createUI = function () {		
		this.layout = new OO.ui.IndexLayout( {
			expanded: false,
			framed: false
		} );
		
		this.saveButton = new OO.ui.ButtonWidget( {
			label: mw.msg( 'gprefs-savebutton-label' ),
			flags: [ 'primary', 'progressive' ]
		} );
		this.saveButton.on( 'click', this.saveAll.bind( this ) );
		
		this.ui = new OO.ui.Widget( {
			content: [
				new OO.ui.PanelLayout( {
					expanded: false,
					framed: true,
					content: [ this.layout ],
					classes: [ 'gprefs-panel' ]
				} ),
				this.saveButton
			]
		} );
		this.ui.$element.hide(); // Hide it until a first tab is added
		$( '#mw-content-text' ).append( this.ui.$element );
	};

	/**
	 * Add a new empty tab to the UI.
	 * 
	 * @private
	 */
	GadgetsPreferences.prototype.createTab = function ( gadgetID, label ) {
		this.tabs[ gadgetID ] = new OO.ui.TabPanelLayout( gadgetID, { label: label } );
		this.layout.addTabPanels( [ this.tabs[ gadgetID ] ] );
		this.ui.$element.show();
	};
			
	/**
	 * Add a new input field to the tab of the corresponding gadget.
	 * 
	 * The field varies depending of the type of the preference.
	 * 
	 * @private
	 */
	GadgetsPreferences.prototype.createField = function ( gadgetID, prefID ) {
		var field, layout,
			pref = this.config[ gadgetID ][ prefID ];
		
		switch( pref.type ) {
			case 'boolean':
				field = new OO.ui.CheckboxInputWidget( {
					selected: pref.value
				} );
				break;
				
			case 'number':
				field = new OO.ui.NumberInputWidget( {
					value: pref.value,
					min: pref.min,
					max: pref.max,
				} );
				break;
				
			case 'color':
				field = new ColorInputWidget( {
					value: pref.value
				} );
				break;

			case 'string':
			default:
				field = new OO.ui.TextInputWidget( {
					value: pref.value
				} );
				break;
		}
		
		pref.__field = field;
		
		layout = new OO.ui.FieldLayout( field, {
			align: 'left',
			label: pref.label,
			help: pref.help
		} );
		this.tabs[ gadgetID ].$element.append( layout.$element );
	};

	/**
	 * Fetch all values of the preferences from the UI and save them.
	 * 
	 * This method is called when the user clicks on the save button on the UI.
	 * 
	 * @private
	 */
	GadgetsPreferences.prototype.saveAll = function () {
		var gadgetID, prefID, pref, key,
			options = {};
		
		this.ui.setDisabled( true );
		
		for ( gadgetID in this.config ) {
			for ( prefID in this.config[ gadgetID ] ) {
				pref = this.config[ gadgetID ][ prefID ];
				key = 'userjs-' + PREFIX + '-' + gadgetID + '-' + prefID;
				
				switch( pref.type ) {
					case 'boolean':
						pref.value = pref.__field.isSelected();
						break;
						
					case 'number':
						pref.value = pref.__field.getNumericValue();
						break;
					
					case 'color':
					case 'string':
					default:
						pref.value = pref.__field.getValue();
						break;
				}
				
				options[ key ] = pref.value;
			}
		}
		
		return this.api.saveOptions( options ).then( function() {
			this.ui.setDisabled( false );
			mw.notification.notify( mw.msg( 'gprefs-notif-success' ), { autoHide: true } );
			setTimeout( function() {
				document.location.href = mw.util.getUrl();
			}, 1250 );
		}.bind( this ), function() {
			this.ui.setDisabled( false );
			mw.notification.notify( mw.msg( 'gprefs-notif-error' ), { type: 'error', autoHide: true } );
		}.bind( this ) );
	};

	
	/**
	 * Main function.
	 * 
	 * It instanciate the GadgetsPreferences object as mw.gadgetsPreferences
	 * and fires the 'gadgets-preferences.ready' hook.
	 */
	if ( mw.config.get( 'wgPageName' ) === EDITPAGE && mw.config.get( 'wgAction' ) === 'view' ) {
		mw.loader.using( [ 'oojs-ui', 'mediawiki.notification' ], function() {
			$( function () {
				mw.gadgetsPreferences = new GadgetsPreferences( true );
				mw.hook( 'gadgets-preferences.ready' ).fire();
			} );
		} );
	} else {
		mw.gadgetsPreferences = new GadgetsPreferences( false );
		mw.hook( 'gadgets-preferences.ready' ).fire();
	}

	$( function () {
		mw.util.addPortletLink( 'p-personal', mw.util.getUrl( EDITPAGE ), mw.msg( 'gprefs-portlet-label' ), 'pt-gadgetpreferences', mw.msg( 'gprefs-portlet-tooltip' ), undefined, '#pt-betafeatures' );
	} );
} );

/* </nowiki> */