User:Yair rand/adddefinition.js

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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// <nowiki>
// This script contains parts of en:User:Conrad.Irwin/editor.js and en:User:Yair rand/adddefinition.js
// To try out this script, add importScript('User:Yair rand/adddefinition.js') to Special:MyPage/monobook.js or Special:MyPage/vector.js

// WT:EDIT by Conrad.Irwin, see http://en.wiktionary.org/w/index.php?title=User:Conrad.Irwin/editor.js&action=history for history

//JsMwApi documentation is at http://en.wiktionary.org/wiki/User_talk:Conrad.Irwin/Api.js
function JsMwApi (api_url, request_type) {

	if (!api_url) 
	{
		if (typeof(true) === 'undefined' || true == false)
			throw "Local API is not usable.";

		api_url = wgScriptPath + "/api.php";
	}

	if (!request_type)
	{
		if (api_url.indexOf('http://') == 0 || api_url.indexOf('https://') == 0)
			request_type = "remote";
		else
			request_type = "local";
	}
	function call_api (query, callback)
	{
		if(!query || !callback)
			throw "Insufficient parameters for API call";

		query = serialise_query(query);

		if(request_type == "remote")
			request_remote(api_url, query, callback, call_api.on_error || default_on_error);
		else
			request_local(api_url, query, callback, call_api.on_error || default_on_error);

	}

	var default_on_error = JsMwApi.prototype.on_error || function (xhr, callback, res)
	{
		if (typeof(console) != 'undefined')
			console.log([xhr, res]);

		callback(null);
	}

	function get_xhr () 
	{
		try{
			return new XMLHttpRequest();
		}catch(e){ try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		}catch(e){ try {
			return new ActiveXObject("Microsoft.XMLHTTP");
		}catch(e){
			throw "Could not create an XmlHttpRequest";
		}}}
	}

	function request_local (url, query, callback, on_error)
	{
		var xhr = get_xhr();

		xhr.open('POST', url + '?format=json', true);
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");                  
		xhr.send(query);
		xhr.onreadystatechange = function ()
		{
			if (xhr.readyState == 4)
			{
				var res;
				if (xhr.status != 200)
					res = {error: {
						code: '_badresponse', 
						info: xhr.status + " " + xhr.statusText
					}};
				else
				{
					try
					{
						res = JSON.parse("("+xhr.responseText+")");
					}
					catch(e)
					{
						res = {error: {
							code: '_badresult',
							info: "The server returned an incorrectly formatted response"
						}};
					}
				}
				if (!res || res.error || res.warnings)
					on_error(xhr, callback, res);
				else
					callback(res);
			}
		}
	}

	function request_remote (url, query, callback, on_error)
	{
		if(! window.__JsMwApi__counter)
			window.__JsMwApi__counter = 0;

		var cbname = '__JsMwApi__callback' + window.__JsMwApi__counter++; 

		window[cbname] = function (res)
		{
			if (res.error || res.warnings)
				on_error(null, callback, res);
			else
				callback(res);
		}

		var script = document.createElement('script');
		script.setAttribute('type', 'text/javascript');
		script.setAttribute('src', url + '?format=json&callback=window.' + cbname + '&' + query);
		document.getElementsByTagName('head')[0].appendChild(script);
	}

	function serialise_query (obj)
	{
		var amp = "";
		var out = "";
		if (String(obj) === obj)
		{
			out = obj;
		}
		else if (obj instanceof Array)
		{
			for (var i=0; i < obj.length; i++)
			{
				out += amp + serialise_query(obj[i]);
				amp = (out == '' || out.charAt(out.length-1) == '&') ? '' : '&';
			}
		}
		else if (obj instanceof Object)
		{
			for (var k in obj)
			{
				if (obj[k] === true)
					out += amp + encodeURIComponent(k) + '=1';
				else if (obj[k] === false)
					continue;
				else if (obj[k] instanceof Array)
					out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k].join('|'));
				else if (obj[k] instanceof Object)
					throw "API parameters may not be objects";
				else
					out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]);
				amp = '&';
			}
		}
		else if (typeof(obj) !== 'undefined' && obj !== null)
		{
			throw "An API query can only be a string or an object";
		}
		return out;
	}

	// Make JSON.parse work
	var JSON = (typeof JSON == 'undefined' ? new Object() : JSON);

	if (typeof JSON.parse != 'function')
		JSON.parse = function (json) { return eval('(' + json + ')'); };

	// Allow .prototype. extensions
	if (JsMwApi.prototype)
	{
		for (var i in JsMwApi.prototype)
		{
			call_api[i] = JsMwApi.prototype[i];
		}
	}
	return call_api;
}

JsMwApi.prototype.page = function (title) {

	function call_with_page (params, callback)
	{
		call_with_page.api([params, {title: title, titles: title}], callback);
	}

	call_with_page.api = this;

	call_with_page.edit = function (params, edit_function)
	{
		if (typeof(params) == 'function')
		{
			edit_function = params;
			params = null;
		}
		params = [params, {
			action: "query", 
			prop: ["info", "revisions"], 
			intoken: "edit", 
			rvprop: ["content", "timestamp"]
		}];

		call_with_page(params, function (res)
		{
			if (!res || !res.query || !res.query.pages)
				return edit_function(null);

			// Get the first (and only) page from res.query.pages
			for (var pageid in res.query.pages) break;
			var page = res.query.pages[pageid];

			var text = page.revisions ? page.revisions[0]['*'] : '';

			function save_function (ntext, params, post_save)
			{
				if (typeof(params) == 'function')
				{
					post_save = params;
					params = null;
				}
				params = [params, {
					action: "edit",
					text: ntext,
					token: page.edittoken,
					starttimestamp: page.starttimestamp,
					basetimestamp: (page.revisions ? page.revisions[0].timestamp : false)
				}];

				call_with_page(params, post_save);
			}

			edit_function(text, save_function, res);

		});
	}

	call_with_page.parse = function (to_parse, callback)
	{
		if (typeof to_parse == "function")
		{
			callback = to_parse;
			to_parse = null;
		}
		var params = (to_parse == null ? {page: title} : {title: title, text: to_parse});

		call_with_page.api([{action: "parse", pst: true}, params], function (res)
		{
			if (!res || !res.parse || !res.parse.text)
				callback(null, res);
			else
				callback(res.parse.text['*'], res);
		})
	}

	call_with_page.parseFragment = function (to_parse, callback)
	{
		call_with_page.parse("<div>\n" + to_parse + "</div>", function (parsed, res)
		{
			callback(parsed ? parsed.replace(/^<div>\n?/,'').replace(/(\s*\n)?<\/div>\n*(<!--[^>]*-->\s*)?$/,'') : parsed, res);
		})
	}

	return call_with_page;
}
/**
 * Storage of "string" preferences.
 */
function CookiePreferences (context)
{
	//Repeated calls with the same context should get the same preferences object.
	if (arguments.callee[context])
		return arguments.callee[context];
	else
		arguments.callee[context] = this;

	/**
	 * Change the value of a preference and store into a cookie.
	 */
	this.set = function (name, value)
	{
		if (value === null || storage[name] === value)
			return;
		storage[name] = value;
		updateCookie();
	}

	/**
	 * Get the value of a preference from the cookie or default
	 *
	 * If the preference isn't set, return the second argument or undefined.
	 */
	this.get = function (name, def)
	{
		if (storage[name])
			return storage[name];
		else if (defaults[name])
			return defaults[name];
		else
			return def;
	}

	/**
	 * let the default for get(name) be value for this session
	 */
	this.setDefault = function(name, value)
	{
		defaults[name] = value;
	}

	var storage = {};
	var defaults = {};

	// Save storage into the cookie.
	function updateCookie ()
	{
		var value = "";
		for (var name in storage)
		{
			value += '&' + encodeURIComponent(name) + "=" + encodeURIComponent(storage[name]);
		}

		$.cookie('preferences' + context, value)
	}
	
	// Load storage from the cookie.
	// NOTE: If you wish to update the cookie format, both loading and storing
	// must continue to work for 30 days.
	function updateStorage ()
	{
		var value = $.cookie('preferences' + context, value) || '';
		var pairs = value.split('&');

		for (var i=1; i < pairs.length; i++)
		{
			var val = pairs[i].split('=');

			if (storage[val[0]] === val[1])
				continue;

			storage[val[0]] = val[1];
		}
	}

	//__init__
	updateStorage();
}
/**
 * A generic page editor for the current page.
 *
 * This is a singleton and it displays a small interface in the top left after
 * the first edit has been registered.
 *
 * @public
 * this.page
 * this.addEdit
 * this.error
 *
 */
function Editor ()
{
	//Singleton
	if (arguments.callee.instance)
		return arguments.callee.instance
	else
		arguments.callee.instance = this;

	// @public - the JsMwApi object for the current page
	this.page = JsMwApi().page(wgPageName);

	// get the current text of the article and call the callback with it
	// NOTE: This function also acts as a loose non-re-entrant lock to protect currentText.
	this.withCurrentText = function(callback)
	{
		if (callbacks.length == 0)
		{
			callbacks = [callback];
			for (var i=0; i<callbacks.length; i++)
			{
				callbacks[i](currentText);
			}
			return callbacks = [];
		} 

		if (callbacks.length > 0) 
		{
			return callbacks.push(callback);
		}

		callbacks = [callback];
		thiz.page.edit(function (text, _save)
		{
			if (text === null)
				return thiz.error("Could not connect to server");

			currentText = originalText = text;
			saveCallback = _save;

			for (var i=0; i<callbacks.length; i++)
			{
				callbacks[i](currentText);
			}
			callbacks = [];

		});
	}
	// A decorator for withCurrentText
	function performSequentially(f)
	{
		return (function () 
		{
			var the_arguments = arguments;
			thiz.withCurrentText(function ()
			{
				f.apply(thiz, the_arguments);
			});
		});
	}

	// add an edit to the editstack
	function addEdit(edit, node, fromRedo)
	{
		withPresenceShowing(false, function ()
		{
			if (node)
			{
				nodestack.push(node);
				node.style.cssText = "border: 2px #00FF00 dashed;"
			}

			if (! fromRedo)
				redostack = [];

			var ntext = false;
			try
			{
				ntext = edit.edit(currentText);

				if (ntext && ntext != currentText)
				{
					edit.redo();
					currentText = ntext;
				}
				else
					return false;
			}
			catch (e)
			{
				this.error("ERROR:" + e);
			}

			editstack.push(edit);
		});
	}
	this.addEdit = performSequentially(addEdit);

	// display an error to the user
	this.error = function (message)
	{ 
		if (!errorlog)
		{
			errorlog = newNode('ul',{style: "background-color: #FFDDDD; margin: 0px -10px -10px -10px; padding: 10px;"});
			withPresenceShowing(true, function (presence)
			{
				presence.appendChild(errorlog);
			});
		}
		errorlog.appendChild(newNode('li', message));
	}


	var thiz = this; // this is set incorrectly when private functions are used as callbacks.

	var editstack = []; // A list of the edits that have been applied to get currentText
	var redostack = []; // A list of the edits that have been recently undone.
	var nodestack = []; // A lst of nodes to which we have added highlighting
	var callbacks = {}; // A list of onload callbacks (initially .length == undefined)

	var originalText = ""; // What was the contents of the page before we fiddled?
	var currentText = ""; // What is the contents now?

	var saveCallback; // The callback returned by the api's edit function to save.

	var errorlog; // The ul for sticking errors in.
	var savelog; // The ul for save messages.

	//Move an edit from the editstack to the redostack 
	function undo ()
	{
		if (editstack.length == 0)
			return false;
		var edit = editstack.pop();
		redostack.push(edit);
		edit.undo();

		var text = originalText;
		for (var i=0; i < editstack.length; i++)
		{
			var ntext = false;
			try
			{
				ntext = editstack[i].edit(text);
			}
			catch (e)
			{
				thiz.error("ERROR:" + e);
			}
			if (ntext && ntext != text)
			{
				text = ntext;
			}
			else
			{
				editstack[i].undo();
				editstack = editstack.splice(0, i);
				break;
			}
		}
		currentText = text;
		return true;
	}
	this.undo = performSequentially(undo);

	//Move an edit from the redostack to the editstack
	function redo ()
	{
		if (redostack.length == 0)
			return;
		var edit = redostack.pop();
		addEdit(edit, null, true);
	}
	this.redo = performSequentially(redo);

	function withPresenceShowing(broken, callback)
	{
		if (arguments.callee.presence)
		{
			arguments.callee.presence.style.display = "block";
			return callback(arguments.callee.presence);
		}

		var presence = newNode('div',{'style':"position: fixed; top:0px; left: 0px; background-color: #00FF00; z-index: 10;padding: 30px;"})
		//Fix fixed positioning for IE6/
		/*@cc_on
			@if (@_jscript_version <= 5.6)
				presence.style.cssText = "position: absolute; top: expression((dummy = (document.documentElement.scrollTop || document.body.scrollTop || 0)) + 'px'); background-color: #00FF00; z-index: 10000; padding: 30px;"
			@end
		@*/
		window.setTimeout(function () {
			presence.style.backgroundColor = "#CCCCFF";
			presence.style.padding = "10px";
		}, 400);

		presence.appendChild(newNode('div',{'style':"position: relative; top:0px; left:0px; margin: -10px; color: #0000FF;cursor:pointer;", click:performSequentially(close)},"X"))
		document.body.insertBefore(presence, document.body.firstChild);

		var contents = newNode('p', {style: 'text-align: center'},
			newNode('b', "Page Editing"), newNode('br'));

		if (!broken)
		{
			contents.appendChild(newNode('button',"Save Changes", {'click': save}));
			contents.appendChild(newNode('br'));
			contents.appendChild(newNode('button',"Undo", {'click': thiz.undo}));
			contents.appendChild(newNode('button', "Redo", {'click':thiz.redo}));
		}
		presence.appendChild(contents);

		arguments.callee.presence = presence;
		callback(presence);
	}

	// Remove the button
	function close ()
	{
		while (undo())
			;

		withPresenceShowing(true, function (presence)
		{
			presence.style.display = "none";
			if (errorlog)
			{
				errorlog.parentNode.removeChild(errorlog);
				errorlog = false;
			}
		});
	}

	//Send the currentText back to the server to save.
	function save ()
	{
		thiz.withCurrentText(function ()
		{
			if (editstack.length == 0)
				return;

			var cleanup_callbacks = callbacks;
			callbacks = [];
			var sum = {};
			for (var i=0; i<editstack.length; i++)
			{
				sum[editstack[i].summary] = true;
			}
			var summary = "";
			for (var name in sum)
			{
				summary += name + " ";
			}
			editstack = [];
			redostack = [];
			var saveLi = newNode('li', 'Saving:' + summary + "...");
			withPresenceShowing(false, function (presence)
			{
				if (! savelog)
				{
					savelog = newNode('ul', {style: "background-color: #DDFFDD; margin: 0px -10px -10px -10px; padding: 10px;"});
					presence.appendChild(savelog);
				}
				savelog.appendChild(saveLi);

				if (originalText == currentText)
					return thiz.error("No changes were made to the page.");

				else if (!currentText)
					return thiz.error("ERROR: page has become blank.");
			});

			originalText = currentText;
			var nst = []
			var node;
			while (node = nodestack.pop())
			{
				nst.push(node);
			}
			saveCallback(currentText, {summary: summary + "([[WT:EDIT|Assisted]])"}, function (res)
			{
				if (res == null)
					return thiz.error("An error occurred while saving.");

				try {
					saveLi.appendChild(newNode('span', newNode('b', " Saved "),
						newNode('a', {'href': wgScript + 
						'?title=' + encodeURIComponent(mw.config.get('wgPageName')) + 
						'&diff=' + encodeURIComponent(res.edit.newrevid) +
						'&oldid=' + encodeURIComponent(res.edit.oldrevid)}, "(Show changes)")));
				}catch(e){
					if (res.error)
					{
						thiz.error("Not saved: " + String(res.error.info));
					}
					else
					{
						thiz.error(newNode('p',String(e)));
					}
				}

				for (var i=0; i < nst.length; i++)
					nst[i].style.cssText = "background-color: #0F0;border: 2px #0F0 solid;";

				window.setTimeout(function () {
					var node;
					while (node = nst.pop())
						node.style.cssText = "";
				}, 400);

				// restore any callbacks that were waiting for currentText before we started
				for (var i=0; i < cleanup_callbacks.length; i++)
					thiz.withCurrentText(cleaup_callbacks[i]);
			
			});
		});
	}
}

var util = {
	
	getVanillaIndexOf: function (str, text, pos)
	{
		if (!pos)
			pos = 0;
		var cpos = 0, tpos = 0, wpos = 0, spos = 0;
		do
		{
			cpos = text.indexOf('<!--', pos);
			tpos = text.indexOf('{'+'{', pos);
			wpos = text.indexOf('<nowiki>', pos);
			spos = text.indexOf(str, pos);

			pos = Math.min(
				Math.min(
					cpos == -1 ? Infinity : cpos , 
					tpos == -1 ? Infinity : tpos
				), 
				Math.min(
					wpos == -1 ? Infinity : wpos,
					spos == -1 ? Infinity : spos
				)
			)

			if (pos == spos)
				return pos == Infinity ? -1 : pos;

			else if (pos == cpos)
				pos = text.indexOf('-->', pos) + 3;

			else if (pos == wpos)
				pos = text.indexOf('</nowiki>', pos) + 9;

			else if (pos == tpos) //FIXME
				pos = text.indexOf('}}', pos) + 2;


		} while (pos < Infinity)
		return -1;
	},

	validateNoWikisyntax: function(field, nonempty)
	{
		return function(txt, error)
		{
			if(/[\[\{\|#\}\]]/.test(txt))
				return error("Please don't use wiki markup ([]{}#|) in the " + field +".");
			if(nonempty && !txt)
				return error("Please specify a " + field + ".");
			return txt;
		}
	},

	escapeRe: function(txt)
	{
		return txt.replace(/([\\{}(\|)[\].?*+])/g, "\\$1");
	},

	// pos is a position in the line containing the gloss
	getWikitextGloss: function (txt, pos)
	{
		var g_start = txt.lastIndexOf('\n{'+'{trans-top', pos) + 1; 
		var g_end = txt.indexOf('\n', pos);
		var g_line = txt.substr(g_start, g_end - g_start);
		g_line = g_line.replace("{"+"{trans-top}}", "{"+"{trans-top|Translations}}");
		return g_line.replace(/\{\{trans-top\|(.*)\}\}/, "$1");
	},

	// get [start_pos, end_pos] of position of wikitext for trans_table containing node in text
	getTransTable: function (text, node, recursive)
	{
		var gloss = util.getTransGloss(node);
		var pos = 0;
		var transect = [];
		while(pos > -1)
		{
			pos = util.getVanillaIndexOf('{'+'{trans-top', text, pos+1)	// }}
			if (pos > -1 && util.matchGloss(util.getWikitextGloss(text, pos), gloss))
			{
				transect.push(pos);
			}
		}
		if (transect.length > 1)
		{
			var poss = transect;
			transect = [];
			for (var i=0; i<poss.length; i++)
			{
				pos = poss[i];
				if (util.matchGloss(gloss, util.getWikitextGloss(text, pos)))
				{
					transect.push(pos);
				}
			}

			if (transect.length > 1 && !recursive)
				transect = util.tieBreakTransTable(text, transect, node);
		}
		if (transect.length == 1)
		{
			pos = transect[0];
			pos = util.getVanillaIndexOf("\n", text, pos) + 1;
			var endpos = text.indexOf('{'+'{trans-bottom}}', pos); 
			if (endpos > -1 && pos > 0)
				return [pos, endpos];
		}

		return false;
	},

	// try to narrow down the correct poss if multiple matching trans tables
	tieBreakTransTable: function (text, poss, node)
	{
		if (node.nodeName.toLowerCase() == 'div')
		{
			while (node && !(node.className && node.className.indexOf('NavFrame') > -1))
				node = node.parentNode;

			var nodes = node.getElementsByTagName('table');
			if (! nodes.length)
				return poss;

			node = nodes[0];
		}
		else
		{
			while (node && node.nodeName.toLowerCase() != 'table')
				node = node.parentNode;
		}

		var tables = document.getElementsByTagName('table');
		var before_count = 0;
		var after_count = 0;
		var is_found = false;
		for (var i=0; i < tables.length; i++)
		{
			if (tables[i].className.indexOf('translations') >= 0)
			{
				var gloss = util.getTransGloss(tables[i]);
				if (gloss == "Translations to be checked")
					continue;

				if (tables[i] == node)
				{
					is_found = true;
					continue;
				}

				var pos = util.getTransTable(text, tables[i], true);

				if (pos)
				{
					for (var j=0; j < poss.length; j++)
					{
						if (poss[j] == pos)
							return util.tieBreakTransTable(poss.splice(j, 1), node);
					}
				}
				else
				{
					var matched = 0;
					for (var j=0; j < poss.length; j++)
					{
						if (util.matchGloss(util.getWikitextGloss(text, poss[j]), gloss) &&
							 util.matchGloss(gloss, util.getWikitextGloss(text, poss[j])))
						{
							matched++;
						}
					}
					if (matched == poss.length) 
					{
						if (is_found)
							after_count++;
						else
							before_count++;
					}
				}
			}
		}

		if (before_count + 1 + after_count == poss.length)
			return [poss[before_count]];
		else
			return poss;
	},

	matchGloss: function (line, gloss)
	{
		if (gloss.match(/^ *$/))
			return !!(line.match(/\{\{trans-top\| *\}\}/) || line.match(/^ *$/));
			
		var words = gloss.split(/\W+/);
		var pos = 0;
		for (var i=0; i < words.length; i++)
		{
			pos = line.indexOf(words[i], pos);
			if (pos == -1)
				return false;
		}
		return pos > -1;
	},

	//User:Karelklic
	getTransGlossText: function (node) {
		var ret = '';
		var children = node.childNodes;
		for (var i=0; i<children.length; i++)
		{
			if (children[i].nodeType == 3)
				ret += children[i].nodeValue;
			else if (children[i].nodeName.match(/^(i|b)$/i) || children[i].className.indexOf('wt-edit-recurse') > -1)
				ret += util.getTransGlossText(children[i]);
			else if (ret.match(/\w$/)) //Prevent new words from being created across node boundaries
				ret += " ";
		}
		// all characters except a-zA-Z0-9 are changed to spaces
		return ret.replace(/\W/g, ' '); 
	},

	getTransGloss: function (ul)
	{
		var node = ul;
		while (node && node.className.indexOf('NavFrame') == -1)
			node = node.parentNode;

		if (!node) return ''; 

		var children = node.childNodes;
		for (var i=0; i< children.length; i++)
		{
			if(children[i].className && children[i].className.indexOf('NavHead') > -1)
				return util.getTransGlossText(children[i]);
			
		}
		return '';
	},

	isTrreq: function (li)
	{
		var spans = li.getElementsByTagName('span');
		return (spans && spans.length > 0 && spans[0].className.indexOf("trreq") > -1)
	}
};

/**
 * A small amount of common code that can be usefully applied to adder forms.
 *
 * An adder is assumed to be an object that has:
 *
 * .fields  A object mapping field names to either validation functions used
 *          for text fields, or the word 'checkbox'
 * 
 * .createForm  A function () that returns a newNode('form') to be added to the
 *              document (by appending to insertNode)
 * 
 * .onsubmit  A function (values, register (wikitext, callback)) that accepts 
 *            the validated set of values and processes them, the register
 *            function accepts wikitext and a continuation function to be 
 *            called with the result of rendering it.
 *
 * Before onsubmit or any validation functions are called, but after running
 * createForm, a new property .elements will be added to the adder which is a
 * dictionary mapping field names to HTML input elements.
 *
 * @param {editor}  The current editor.
 * @param {adder}  The relevant adder.
 * @param {insertNode}  Where to insert this in the document.
 * @param {insertSibling} Where to insert this within insertNode.
 */
function AdderWrapper (editor, adder, insertNode, insertSibling)
{
	var form = adder.createForm()
	var status = newNode('span');

	form.appendChild(status);
	if (insertSibling)
		insertNode.insertBefore(form, insertSibling);
	else
		insertNode.appendChild(form);

	adder.elements = {};

	//This is all because IE doesn't reliably allow form.elements['name']
	for (var i=0; i< form.elements.length; i++)
	{
		adder.elements[form.elements[i].name] = form.elements[i];
	}

	form.onsubmit = function ()
	{
		try
		{
			var submit = true;
			var values = {}

			status.innerHTML = "";
			for (var name in adder.fields)
			{
				if (adder.fields[name] == 'checkbox')
				{
					values[name] = adder.elements[name].checked ? name : false;
				}
				else
				{
					values[name] = adder.fields[name](adder.elements[name].value || '', function (msg) 
					{
						status.appendChild(newNode('span',{style:'color: red'}, msg, newNode('br'))); 
						return false
					});
					
					if (values[name] === false)
						submit = false;
				}
			}
			if (!submit)
				return false;

			var loading = newNode('span', 'Loading...');
			status.appendChild(loading);
			
			adder.onsubmit(values, function (text, callback)
			{
				editor.page.parseFragment(text, function (res)
				{
					if (!res) 
						return loading.appendChild(newNode('p', {style: 'color: red'}, "Could not connect to the server."));

					callback(res);
					status.removeChild(loading);
				});
			});   
		}
		catch(e)
		{
			status.innerHTML = "ERROR:" + e.description; 
			return false;
		}

		return false;
	}

}

// End of editor script

// Definition adder and image adder by Yair rand. Originally at http://en.wiktionary.org/wiki/User:Yair_rand/adddefinition.js
// This script adds "Add definition" and "Add image" buttons to the toolbox section of the sidebar.
// Still some bugs with the undo button.
var bodyContent
if(wgNamespaceNumber==0&&wgAction=="view")
{
$(function()
{
    bodyContent=document.getElementById('bodyContent');
    mw.util.addPortletLink('p-tb', 'javascript:adddefinition()', 'Add definition');
    mw.util.addPortletLink('p-tb', 'javascript:addimage()', 'Add image','newimagebutton');
});
}
var currentboxtobeadded
var ccc
var addingimage=0

function setupboxtobeadded()
{
document.body.appendChild(currentboxtobeadded)
document.onmousemove=function(e){e=e||event;currentboxtobeadded.style.left=e.clientX+2+'px';currentboxtobeadded.style.top=e.clientY+2+'px'}
document.body.style.cursor='move'
}

function adddefinition()
{
if(!document.onmousemove)
{
currentboxtobeadded=newNode('div',{style:'border:1px solid #000000;position:fixed;left:200px;top:500px;z-index:5;padding:10px;background-color:#FFFFFF;'},newNode('nobr','Definition: ',newNode('input',{size:100,onblur:'setTimeout("currentboxtobeadded.lastChild.lastChild.focus()")'})))
setupboxtobeadded()
currentboxtobeadded.lastChild.lastChild.focus()

var ols=document.getElementsByTagName('ol')
for(i=0;i<ols.length;i++)
{
ols[i].onmouseover=function(){this.style.borderBottom='1px solid #000000'}
ols[i].onmouseout=function(){this.style.borderBottom=''}
ols[i].onclick=function(){adddefinition2(this,currentboxtobeadded.lastChild.lastChild.value)}
}

}
}

function adddefinition2(q,newdef)
{
document.onmousemove=null
var ols=document.getElementsByTagName('ol')
for(i=0;i<ols.length;i++)
{
ols[i].onmouseover=null
ols[i].onmouseout=null
ols[i].onclick=null
}
q.style.borderBottom=''
document.body.style.cursor=''

var qq=newNode('li')
JsMwApi().page(wgPageName).parseFragment(newdef,function (res){qq.innerHTML=res})
function adddefinition3(wikitext)
{

prevheader=q
while(!prevheader.nodeName.match(/^h\d$/i))
{prevheader=prevheader.previousSibling}

findnumberofheaders=Number(prevheader.firstChild.getElementsByTagName('a')[0].href.match(/\d*$/))
wikitext=wikitext.replace(new RegExp("((?:(^|\n)=[\\s\\S]*?){"+findnumberofheaders+"}[\\s\\S]*?\n#[\\s\\S]*?\n(?!#))\n"),'$1# '+newdef+'\n\n');

ccc=wikitext
return wikitext
}

var editor=new Editor()
editor.addEdit({
 
            edit: adddefinition3,
            redo: function () { q.appendChild(qq) },
            undo: function () { q.removeChild(qq) },
            summary: "+def: " +newdef
        },qq);

document.body.removeChild(currentboxtobeadded)

}

function addimage()
{
if(!document.onmousemove && addingimage==0)
{
addingimage=1

var adderobject={
createForm:function(){return newNode('form',{style:'float:right'},newNode('table',
newNode('tbody',
newNode('tr',newNode('td','File name:'),newNode('td',newNode('input',{'name':'filename'}))),
newNode('tr',newNode('td','Caption: '),newNode('td',newNode('input',{'name':'caption'}))),
newNode('tr',newNode('td',{colspan:2,align:'center'},
newNode('input',{type:'submit',value:'Place'}))))))},
fields:{
'filename':function(txt,error){return util.validateNoWikisyntax('filename', true)(txt, error)},
'caption':function(txt,error){if(txt){return txt}else{return error("Please specify a caption.")}}
},
onsubmit:function(values,render){
render('['+'[File:'+values.filename+'|thumb|'+values.caption+']]',function(newhtml){addimage2(values.filename,values.caption,newhtml)});
bodyContent.removeChild(bodyContent.firstChild)
}

}

new AdderWrapper(new Editor(), adderobject, bodyContent, bodyContent.firstChild)
bodyContent.firstChild.firstChild.firstChild.firstChild.childNodes[1].firstChild.focus()


}

}

function addimage2(q,qq,newboxhtml)
{
if(!document.onmousemove)
{

currentboxtobeadded=newNode('div',{style:'position:fixed;left:0px;top:0px;z-index:5;'})

currentboxtobeadded.innerHTML=newboxhtml;
setupboxtobeadded()

y=bodyContent.getElementsByTagName('table')
for(x=0;x<y.length;x++)
{
if(y[x].className.match(/inflection-table/))
{
y[x].onmouseover=function(){
this.parentNode.insertBefore(currentboxtobeadded,this.nextSibling);
currentboxtobeadded.style.position=''}
}
}

document.body.onclick=function(){if(!currentboxtobeadded.style.position){addimage3(q,qq,currentboxtobeadded.nextSibling)}}

}


}

function addimage3(q,qq,qqq)
{

y=document.getElementsByTagName('table')
for(x=0;x<y.length;x++)
{
y[x].onmouseover=null
}


document.body.onclick=null
document.onmousemove=null
document.body.style.cursor=''
addingimage=0
qqqq=currentboxtobeadded.firstChild;
currentboxtobeadded.removeChild(qqqq);
currentboxtobeadded.parentNode.removeChild(currentboxtobeadded);

function addimage4(wikitext)
{
prevheader=qqq;while(!(prevheader.nodeName&&prevheader.nodeName.toLowerCase()=="h2")){prevheader=prevheader.previousSibling}
findnumberofheaders=Number(prevheader.firstChild.getElementsByTagName('a')[0].href.match(/\d*$/))
wikitext=wikitext.replace(new RegExp("((?:(^|\n)=[\\s\\S]*?){"+findnumberofheaders+"}[\\s\\S]*?})\n"),'$1\n['+'[File:'+q+'|thumb|'+qq+']]\n');
ccc=wikitext
return wikitext
}
var editor=Editor()
editor.addEdit({

            edit: addimage4,
            redo: function () { qqq.parentNode.insertBefore(qqqq,qqq); },
            undo: function () { qqqq.parentNode.removeChild(qqqq); },
            summary: "+["+"[File:"+q+"]]"
        },qqqq);
}
// </nowiki>