MediaWiki:Gadget-Numerakri.js

/** * Convert Arabic numerals to Devanagari and vice-versa. * * @revision 2.3-2012-12-30 * @dependencies jquery.cookie, mediawiki.util, mediawiki.user, jquery.mwExtension * @source https://www.mediawiki.org/wiki/MediaWiki:Gadget-Numerakri.js * @author Daniel Friesen, 2012 * @author Timo Tijhof, 2012 * @author Siddhartha Ghai, 2012 * @license CC-BY-SA 3.0 * @license MIT */ /*jshint browser:true, boss:true, unused:true, white:true */ /*global jQuery, mediaWiki */ (function (mw, $) {	"use strict";	// Private cache & utilities	var el, w, num,		/**		 * @var {RegExp} Matcher for characters that can be mapped.		 * @example matchers.devanagari[0] matches devanagari numeral for 0		 * can be mapped with e.g. maps.arabic[0].		 */		matchers = {},		msgs = {			'option-default': {				en: 'Default',				hi: 'डिफ़ॉल्ट'			},			'option-arabic': {				en: '123'			},			'option-devanagari': {				en: 'Devanagari',				hi: '१२३'			},			'label-url': {				en: '//www.mediawiki.org/wiki/MediaWiki_talk:Gadget-Numerakri.js',				hi: '//hi.wikipedia.org/wiki/विकिपीडिया:अंक_परिवर्तक'			},			'label-text': {				en: 'Convert numerals',				hi: 'अंक परिवर्तन'			},			'label-tooltip': {				en: 'Convert between Arabic and Devanagari numerals',				hi: 'नागरी और अरबी अंकों में परिवर्तन करें'			}		},		maps = {			// 0 to 9			devanagari: [ '०', '१', '२', '३', '४', '५', '६', '७', '८', '९' ],			arabic:     [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] },		compatCookie = { '-1': 'default', '0' : 'arabic', '1': 'devanagari' },		// For consistency recreate these objects locally in older browsers so that // we can use the same constants in fallback code as well. NodeFilter = window.NodeFilter || { FILTER_ACCEPT: 1, FILTER_REJECT: 2, FILTER_SKIP: 3 },		Node = window.Node || { TEXT_NODE: 3 };

// Fallback for document.createTreeWalker for older browsers (IE6-8) function walkTheDomFallback(node, filter, apply) { var val = filter(node); switch (val) { case NodeFilter.FILTER_ACCEPT: apply(node); node = node.firstChild; break; case NodeFilter.FILTER_REJECT: node = node.nextSibling; break; case NodeFilter.FILTER_SKIP: node = node.firstChild; break; }		while (node) { walkTheDomFallback(node, apply); node = node.nextSibling; }	}

function walkTheDom(filter, apply) { if (document.createTreeWalker) { w = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL, filter, false); while (el = w.nextNode) { apply(el); }		} else { walkTheDomFallback(document.body, filter, apply); }	}

function msg(key) { return msgs[key][mw.config.get('wgUserLanguage')] || msgs[key].en; }

function getMatchers(target) { var rChars; if (!matchers[target]) { rChars = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [] }; $.each(maps, function (type, map) {				if (type !== target) {					for (var i = 0; i <= 9; i++) {						rChars[i].push(map[i]);					}				}			}); rChars = $.map(rChars, function (chars) {				return new RegExp('(' + $.map(chars, $.escapeRE).join('|') + ')', 'g');			}); matchers[target] = rChars; }		return matchers[target]; }

/**	 * @singleton */	num = window.Numerakri = { matchers: matchers,

/**		 * @property {string} One of 'default', 'arabic' or 'devanagari'. * default leaves the page unchanged (default). */		type: 'default',

/**		 * @property {jQuery|null} The HTML interface. May or may not be * attached to the document yet. */		$int: null,

/**		 * @param {HTMLElement|TextNode} el		 */ filterNode: function (el) { var n = el.nodeName && el.nodeName.toLowerCase; if (n === 'input' || n === 'textarea' || $(el).hasClass('mw-numerakri-skip')) { return NodeFilter.FILTER_REJECT; }			if (el.nodeType === Node.TEXT_NODE) { return NodeFilter.FILTER_ACCEPT; }			return NodeFilter.FILTER_SKIP; },

/**		 * @method * @param {TextNode} el		 */ handleTextNode: function (el) { var live = el.nodeValue, fix = live, matchers = getMatchers(num.type), i = 0; for (i <= 9; i++) { fix = fix.replace(matchers[i], maps[num.type][i]); }			if (live !== fix) { el.nodeValue = fix; }		},

isValidType: function (type) { return type === 'default' || !!maps[type]; },

/**		 * Register an additional type. *		 * @example * num.addType('arabic', { map: [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] }); * @param {string} type ID		 * @param {Object} data Contents: * - {Object} map * - {string|Object} msg [optional] Label for this type or translations of labels, *  keyed by language code. Defaults to ucFirst transformation of ID. */		addType: function (type, data) { if (type && data && $.isArray(data.map)) { // Map maps[type] = data.map;

// Messages if (data.msg) { if ($.isPlainObject(data.msg)) { if (!data.msg.en) { data.msg.en = $.ucFirst(type); }					} else { data.msg = { en: String(data.msg) }; }				} else { data.msg = { en: $.ucFirst(type) }; }				msgs['option-' + type] = data.msg;

if (this.$select) { this.$select.append(						$(' ').val(type)							.text(msg('option-' + type))							.prop('selected', this.getStoredType === type)					); }				return true; }			return false; },

/**		 * @method * @param {string} type One of 'arabic' or 'devanagari'. * @throws Error */		setType: function (type) { if (!this.isValidType(type)) { mw.log('Unknown Numerakri type: ' + type); return false; }

this.type = type;

// Remember for 30 days $.cookie('mw-numerakri-type', type, { expires: 30, path: '/' });

this.convertPage; },

/**		 * @return {string|undefined} */		getStoredType: function { // From cookie var stored = $.cookie('mw-numerakri-type');

// From cookie (old version) if (!stored) { stored = compatCookie[$.cookie('numconvert')]; }

return stored; },

/**		 * Do the conversion. * @method */		convertPage: function { if (this.type === 'default') { // Type 'default' means "don't change the page". return; }

switch (this.type) { case 'arabic': $('ol:lang(hi) li, ol.references, li.references').css('list-style-type', 'decimal'); break; case 'devanagari': $('ol:lang(hi) li, ol.references, li.references').css('list-style-type', 'devanagari'); break; }

walkTheDom(this.filterNode, this.handleTextNode); },

setupInterface: function { var $select, stored; $select = $(' ').addClass('mw-numerakri-skip').append(				$(' ').val('default').text(msg('option-default')),				// $.map returns an array				$.map(maps, function (map, type) { return $(' ').val(type).text(msg('option-' + type)); })			);

stored = num.getStoredType; if (stored) { // Set initial value from storage $select.val(stored); num.setType(stored); }

$select.change(function {				num.setType(this.value);			});

if (num.$select) { num.$select.replaceWith($select); } else { num.$select = $select; }		},

attachInterface: function { var potlet, $menu;

if (num.$select === null) { num.setupInterface; }

switch (mw.config.get('skin')) { case 'vector': case 'chick': case 'myskin': case 'simple': case 'monobook': case 'modern': $('#pt-numconvert').remove; // Just in case potlet = mw.util.addPortletLink(					'p-personal',					msg('label-url'),					msg('label-text'),					'pt-numconvert',					msg('label-tooltip'),					null,					mw.user.isAnon ? '#pt-createaccount' : '#pt-userpage'				); break; default: // Skin unsupported return; }

$menu = $(' ').addClass('mw-numerakri-menu').append(num.$select);

$(potlet).append($menu); }	};

num.setupInterface;

mw.loader.using('mediawiki.user', function {		$(document).ready(num.attachInterface);	});

})(mediaWiki, jQuery);