MediaWiki:Gadget-WiktGadgetPrefs.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

Provides a framework for gadgets to store additional preferences, both for registered and anonymous users.

MediaWiki:Gadget-WiktGadgetPrefsPage.js implements Wiktionary:Gadget preferences, which is where these preferences can be modified.

See also: Special:Gadgets.


/* global mw */
// <nowiki>
/*jshint shadow:true, undef:true, latedef:true, unused:true, esversion:3 */

/**
 * WiktGadgetPrefs -- developed by [[wikt:en:User:Surjection]]
 *
 * Example usage (remember to add ext.gadget.WiktGadgetPrefs as a dependency):

	var preferences = mw.wiktGadgetPrefs.get("gadgetfoobar", 
		{
			"label": {
				"en": "Foobar Gadget"
			}
		},
		{
			"frobulate": {
				"type": "boolean",
				"default": false,
				"label": {
					"en": "Whether this gadget should frobulate"
				}
			}
		});
	var shouldFrobulate = preferences.frobulate;

    // strenum example
	var preferences = mw.wiktGadgetPrefs.get("gadgetfoobar", 
		{
			"label": {
				"en": "Foobar Gadget"
			}
		},
		{
			"frobulate": {
				"type": "strenum",
				"default": "yes",
				"label": {
					"en": "Whether this gadget should frobulate"
				},
				"choices": [
					"yes",
					"no",
					"maybe"
				],
				"choiceLabels": {
					"en": {
						"yes": "Yes!",
						"no": "No!!",
						"maybe": "Maybe?"
					}
				}
			}
		});
	var shouldFrobulate = preferences.frobulate;

 * Dependencies needed: mw.user, user.options
 */

(function() {
	"use strict";
	/**
	 * @typedef WiktGadgetOptions
	 * - Represents gadget options.
	 * @property {{[languageCode: string]: string}} [label]
	 * - The label to display on the preferences page for this
	 *   gadget/namespace. The language code should be a MediaWiki
	 *   language code, e.g. "en" for English.
	 */

	/**
	 * @typedef WiktGadgetPreference
	 * - Represents a single preference.
	 * @property {{"boolean"|"integer"|"string"|"strenum"|"object"}} type
	 * - Must be one of the following:
	 * - "boolean" for boolean values, either true or false.
	 * - "integer" for an integer value.
	 * - "string" for a string value.
	 * - "strenum" for a string value that must be one of the options.
	 * - "object" for an arbitrary JSON value, including null.
	 *   No type checks are done.
	 * @property {any} default
	 * - The default value for this preference if missing, or if
	 *   the value is invalid.
	 * - The developer is responsible for ensuring that this value is
	 *   given, and that it meets the preconditions defined below.
	 * @property {string[]} [choices]
	 * - If the type is "strenum", then this must be an array of
	 *   allowed options. If the value is not in this list, the default
	 *   value will be returned.
	 * @property {number} [minimum]
	 * - If the type is "integer", this is the minimum allowed value.
	 * @property {number} [maximum]
	 * - If the type is "integer", this is the maximum allowed value.
	 * @property {number} [maximumLength]
	 * - If the type is "string", this is the maximum allowed length.
	 * @property {{[languageCode: string]: string}} [label]
	 * - The label to display on the preferences page for this
	 *   preference. The language code should be a MediaWiki
	 *   language code, e.g. "en" for English.
	 * @property {{[languageCode: string]: {[choice: string]: string}}} [choiceLabels]
	 * - The label to display on the preferences page for the choices
	 *   of this preference (if it is an enum type). The language code
	 *   should be a MediaWiki language code, e.g. "en" for English.
	 */

	var wiktGadgetPrefs__registeredPrefs = {};

	var wiktGadgetPrefs__error = (Error ?
		function (message) {
			return new Error(message);
		} :
		function (message) {
			return message;
		});

	var wiktGadgetPrefs__asciify = function (text) {
		return text.replace(/[^A-Za-z0-9-]/g, function (match) {
			if (match == "_") {
				return "__";
			} else {
				return "_" + match.charCodeAt(0).toString(16) + "_";
			}
		});
	};

	var wiktGadgetPrefs__namespace_to_key = function (namespace) {
		return "wiktgadgetprefs-" + wiktGadgetPrefs__asciify(namespace);
	};
  
	var wiktGadgetPrefs__is_integer = function (num) {
		return num == (num | 0);
	};

	var wiktGadgetPrefs__parse_one = function (value, preference, key) {
		var defaultValue = preference["default"];
		switch (preference.type) {
			case "boolean":
				// data type
				if (typeof value != "boolean") return defaultValue;
				return value;
			case "integer":
				// data type
				if (typeof value != "number") return defaultValue;
				// integer check
				if (!wiktGadgetPrefs__is_integer(value)) return defaultValue;
				// constraints
				if ((preference.minimum != null && value < preference.minimum) ||
					(preference.maximum != null && value > preference.maximum))
					return defaultValue;
				return value;
			case "string":
				// data type
				if (typeof value != "string") return defaultValue;
				// constraints
				if (preference.maximumLength != null && value.length > preference.maximumLength)
					return defaultValue;
				return value;
			case "strenum":
				// data type
				if (typeof value != "string") return defaultValue;
				// constraints
				if (preference.choices.indexOf(value) < 0)
					return defaultValue;
				return value;
			case "object":
				if (typeof value == "undefined") return defaultValue;
				return value;
		}

		throw wiktGadgetPrefs__error("Invalid preference specification '" + key + "'");
	};

	var wiktGadgetPrefs__parse_all = function (values, preferences) {
		var parsed = {};
		if (!values) values = {};

		for (var key in preferences) {
			if (preferences.hasOwnProperty(key)) {
				parsed[key] = wiktGadgetPrefs__parse_one(values[key], preferences[key], key);
			}
		}
		return parsed;
	};

	var wiktGadgetPrefs__resolve = function (namespaceKey, preferences) {
		var resolved;
		var mwOptions;
		try {
			mwOptions = mw.user.options;
		} catch (e) {
		}

		if (!resolved && window.localStorage && window.localStorage.getItem("anon" + namespaceKey) != null) {
			try {
				resolved = JSON.parse(window.localStorage.getItem("anon" + namespaceKey));
			} catch (e) { }
		}

		if (!resolved && mwOptions && mwOptions.exists("userjs-" + namespaceKey)) {
			try {
				resolved = JSON.parse(mwOptions.get("userjs-" + namespaceKey));
			} catch (e) { }
		}

		return wiktGadgetPrefs__parse_all(resolved || {}, preferences);
	};

	/**
	 * Resolve preferences.
	 *
	 * Note that there is a maximum length imposed by MediaWiki
	 * on the total size of the preferences for each namespace.
	 *
	 * @param {string} namespace
	 * - An arbitrary string. This should be unique to each gadget.
	 * @param {WiktGadgetOptions} options
	 * - Options.
	 * @param {{[key: string]: WiktGadgetPreference}} preferences
	 * - Requested preferences, indexed by name.
	 * @returns {{[key: string]: any}}
	 */
	var wiktGadgetPrefs_get = function (namespace, options, preferences) {
		if (wiktGadgetPrefs__registeredPrefs[namespace]) {
			throw wiktGadgetPrefs__error("wiktGadgetPrefs_get has already been called with the namespace '" + namespace + "'.");
		}
		wiktGadgetPrefs__registeredPrefs[namespace] = {
			options: options,
			preferences: preferences
		};

		var namespaceKey = wiktGadgetPrefs__namespace_to_key(namespace);
		if (namespaceKey.length >= 240) {
			throw wiktGadgetPrefs__error("Namespace key '" + namespace + "' is too long. " +
					"Please prefer ASCII characters A-Z, a-z, 0-9 and the hyphen, " +
					"as other characters need to be converted, which increases their size.");
		}

		return wiktGadgetPrefs__resolve(namespaceKey, preferences);
	};

	mw.wiktGadgetPrefs = {};
	mw.wiktGadgetPrefs.get = wiktGadgetPrefs_get;

	// Only to be used by the preferences page.
	mw.wiktGadgetPrefs._namespaceToKey = wiktGadgetPrefs__namespace_to_key;
	mw.wiktGadgetPrefs._resolve = wiktGadgetPrefs__resolve;
	mw.wiktGadgetPrefs._registeredPrefs = wiktGadgetPrefs__registeredPrefs;
})();

// </nowiki>