Sixtest Wiki:API4/Code

var TW = function { "use strict"; var API = this, newObject, newList, newEvent, newUI; window.$API = API; /* Refer to this to access top API level */ this.baseURL = "/wiki/Project:API4"; this.data = {}; /* Collections of data (items, npcs, monsters, objects, etc.) */ this.edit = {}; /* Functions for interacting with the wiki database */ /* Basic structure: * API * > user * > > edit, rights etc * > events * > > run, beforerun, change, ... * > data * > > [global namespace for extension data, which may be used by other extensions * > ext * > > [extension data, not shared with other extensions] */	/* User * Data important to the user such as: edittoken, edit history, state history, rights, ... * rights * - used by extensions to restrict access (e.g. submit function restricted to trusted groups) * history * - list of user events that triggered responses by any extension. Must be supported by extensions * edittoken * - the edit token needed to perform an edit * edit * - edits data within a variable * submit * - submits the edited data */	this.user = { rights: [], edittoken: '', history: [] };	/* Main object constructor * Use this to create a new object with a specific prototype: * var myList = newObject(object prototype) */	newObject = function (p) { var Func = function {}; Func.prototype = p;		Func.prototype.add = function (name, func) { Func.prototype[name] = func; };		return new Func; };	/* Main list object * Create list object using API.create.list(data) * Methods: * * iterate: iterate over all elements of the list * * subset: create a new list with only the specified elements * * query: provided a function, iterates over all list elements; creates a sublist with all elements that the function returned true for * * filter: provided an object of parameter values, assigns strength values and sorts the list by strength. Example: filter({npcvalue:2}) will assign strength = 2 * npcvalue and sort it accordingly * * edit: edit a value of the element. Changes will be applied to ALL lists because the data is shared (referenced, not copied) */	$API.data.merge = function { var list, merge, a = [], b = [], func = function (a, b) { return (a.name > b.name ? 1 : (a.name < b.name ? -1 : 0));			};		merge = function (a, b, func) { // assumes arr1 and arr2 are sorted themselves // a and b will be undefined (!!) if it is not a valid list object or follow that structure ... a = a || []; b = b || []; var fin = [], ai = 0, bi = 0, al = a.length, bl = b.length, dFunc = function (a, b) { var rtn; if (a < b) { rtn = -1; } else if (a > b) { rtn = 1; } else { rtn = 0; }					return rtn; },				cv; func = func || dFunc; while (ai < al && bi < bl) { cv = func(a[ai], b[bi]); if (cv <= 0) { fin[fin.length] = a[ai]; ai += 1; } else if (cv > 0) { fin[fin.length] = b[bi]; bi += 1; }			}			while (ai < al) { fin[fin.length] = a[ai]; ai += 1; }			while (bi < bl) { fin[fin.length] = b[bi]; bi += 1; }			return fin; };		for (list in $API.data) { if ($API.data.hasOwnProperty(list)) { b = $API.data[list].data; a = merge(a, b, func); }		}		$API.data.all = $API.create.list("all", a); };	newList = function { return newObject({			"iterate": function (func, callback) {				var returnVals = [], data = this.data, rtn, length = data.length, i = 0;				for (i; i < length; i += 1) {					returnVals[i] = func.apply(this, [i, data[i]]);				}				if (callback !== undefined) {					rtn = callback.apply(this, [returnVals]);				} else {					rtn = returnVals;				}				return rtn;			},			"subset": function (arr, name) {				var newList, data;				data = this.query(function (d) { return $.inArray(d.name, arr) > -1; });				newList = API.create.list(name, data);				return newList;			},			"query": function (cond, limit) {				var data = this.data, i = 0, len = data.length, output, outputData = [], results = 0;				limit = limit || Infinity;				if (typeof cond !== 'function') {					return false;				}				function itemQuery(id) {					return cond(data[id]);				}				for (i; i < len && results < limit; i += 1) {					if (itemQuery(i)) {						outputData[outputData.length] = data[i];						results += 1;					}				}				output = $API.create.list( "_temp", outputData, false );				return output;			},			"filter": function (obj) {				var data = this.data, i = 0, len = data.length, output, filtered = [];				if (typeof obj !== 'object') {					return false;				}				function itemQuery(id) {					var p, item = data[id], strength = 0, f, v, outputObj = {};					outputObj.name = data[id].name;					for (p in obj) {						if (obj.hasOwnProperty(p)) {							f = (obj[p] === undefined) ? 0 : obj[p];							v = (item[p] === undefined) ? 0 : item[p];							strength += f * v;							outputObj[p] = item[p];						}					}					outputObj.strength = strength;					return outputObj;				}				for (i; i < len; i += 1) {					filtered[i] = itemQuery(i);				}				output = $API.create.list( "_temp", filtered, true );				output.data.sort(function (a, b) { return b.strength - a.strength; });				return output;			},			"edit": function (item, param, value) {				var newItem;				// TODO: cleanup				// $API.edit.changed moved to $API.edit.changed				$API.edit.changed = $API.edit.changed || {};				function inString(c, str) {					var len = str.length, i = 0;					for (i; i < len; i += 1) {						if (c === str.charAt(i)) {							return true;						}					}					return false;				}				newItem = $API.data.item.query(function (d) { return d.name === item; }, 1).data[0];				if (newItem !== undefined) {					newItem[param] = value;					if ($API.edit.changed[newItem.name] === undefined) {						$API.edit.changed[newItem.name] = {};					}					$API.edit.changed[newItem.name][param] = value;					return newItem;				}				return false;			}		}); };	newEvent = function { return newObject({			"fire": function {				if (!(this.runonce && this.runcount > 0)) {					/* Fire all listeners attached to this object */					this.listeners = this.listeners || [];					var listeners = this.listeners, i = 0, len = listeners.length, rtns = [];					for (i; i < len; i += 1) {						rtns[i] = listeners[i];					}					this.runcount += 1;				}			},			"listen": function (func) {				/* Listen to this object */				this.listeners = this.listeners || [];				var pos = this.listeners.length; // identifier				this.listeners[pos] = func;				return pos;			},			"remove": function (n) {				if (n !== undefined) {					delete (this.listeners[n]);				} else {					/* Remove all event listeners */					while (this.listeners.length) {						this.remove(this.listeners.length - 1);						this.listeners.length -= 1;					}				}			}		}); };	newUI = function (e) { var ui = $API.ui, arrayClear = function (arr) { var newArr = [], i = 0, len = arr.length; for (i; i < len; i += 1) { if (arr[i] !== undefined) { newArr[newArr.length] = arr[i]; }				}				return newArr; },			rtnObj = newObject({				children: [],				removeReferences: function {					// remove the references to the object for garbage collection					delete ($API.ui.data[this.dataIndex]);					delete (this.parent.children[this.parentIndex]);					return true;				},				child: function (e) {					var eleObj = newUI(e);					eleObj.parentIndex = this.children.length;					this.children[eleObj.parentIndex] = eleObj;					eleObj.dataIndex = $API.ui.data.length;					$API.ui.data[eleObj.dataIndex] = eleObj;					this.ele.appendChild(eleObj.ele);					eleObj.parent = this; // used for infanticide					eleObj.level = this.level + 1;					return eleObj;				},				destroy: function  {					this.remove;					$API.ui.data = arrayClear($API.ui.data);				},				remove: function  {					var i = 0,						len = this.children.length,						$c;					for (i; i < len; i += 1) {						this.children[i].remove;						//delete (this.children[this.children[i].parentIndex]); //parent.children = arrayClear(this.parent.children); }					this.children = arrayClear(this.children); this.removeReferences; while (this.ele.hasChildNodes) { this.ele.removeChild(this.ele.lastChild); }					return this; },				getPos: function (ele) { var posx = 0, posy = 0; ele = ele || this.ele; if (ele.offsetParent) { do { posx += ele.offsetLeft; posy += ele.offsetTop; ele = ele.offsetParent; } while (ele); return [posx, posy]; }					return false; }			});		if (e !== undefined) {			rtnObj.ele = document.createElement(e);		}		return rtnObj;	};	this.create = {		/* Object for constructor creation		 * To add an object, use API.create[myObjName] = function(arg1, ..., argN) {}		 * ... and newObject(prototype)		 */		"list": function (name, data, apply) {			var list = newList;			if (apply === undefined) {				apply = true;			}			list.data = data;			if (apply) {				API.data[name] = list;			}			return list;		},		"event": function (name, data) {			var event, defaults;			data = data || {};			API.event = API.event || {};			event = newEvent;			defaults = {				"runonce": false,				"runcount": 0			};			event.runonce = data.runonce || defaults.runonce;			event.runcount = data.runcount || defaults.runcount;			API.event[name] = event;			return event;		},		"extension": function (name, hasUI) {			API.ext = API.ext || {};			API.ext[name] = {};			if (hasUI === undefined) { hasUI = true; }			API.ext[name].ui = hasUI ? newUI : undefined; return API.ext[name]; },		"ui": function (name) { var contentRoot = document.getElementById("mw-content-text"), $root = newUI('div'), list = [ $root ]; $root.ele.className = 'APIRoot'; $root.level = 0; contentRoot.appendChild($root.ele); $API.ui = $API.create.list(				'ui',				list,				false			); return $API.ui; },		"stylesheet": function (css) { var e = document.createElement("style"), at = document.getElementsByTagName("head")[0]; e.type = 'text/css'; function applyCSS(css) { if (e.styleSheet) { e.styleSheet.cssText = css; } else { e.appendChild(document.createTextNode(css)); }				at.appendChild(e); return e;			} if (css.charAt(0) === '/') { // relative url $.ajax({					url: $API.baseURL + css + '?action=raw',					success: function (response) {						css = response;						applyCSS(css);					}				}); } else { applyCSS(css); }		}	};	$API.Base64 = (function {		// mostly from http://www.webtoolkit.info/javascript-base64.html		// modified to comply with JSLint as much as possible		// applying to $API, using "real" private variables		/*jslint bitwise: true, regexp: true*/		var keyStr,			encode,			decode,			utf8_encode,			utf8_decode;		keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";		encode = function (str) {			var output = '', i = 0, len, chr1, chr2, chr3, enc1, enc2, enc3, enc4;			str = utf8_encode(str);			len = str.length;			while (i < len) {				chr1 = str.charCodeAt(i);				i += 1;				chr2 = str.charCodeAt(i);				i += 1;				chr3 = str.charCodeAt(i);				i += 1;				enc1 = chr1 >> 2;				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);				enc4 = chr3 & 63;				if (isNaN(chr2)) {					enc3 = enc4 = 64;				} else if (isNaN(chr3)) {					enc4 = 64;				} output += keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); }			return output; };		decode = function (str) { var output = '', i = 0, len, chr1, chr2, chr3, enc1, enc2, enc3, enc4; str = str.replace(/[^A-Za-z0-9\+\/\=]/g, ""); len = str.length; while (i < len) { enc1 = keyStr.indexOf(str.charAt(i)); i += 1; enc2 = keyStr.indexOf(str.charAt(i)); i += 1; enc3 = keyStr.indexOf(str.charAt(i)); i += 1; enc4 = keyStr.indexOf(str.charAt(i)); i += 1; chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output += String.fromCharCode(chr1); if (enc3 !== 64) { output += String.fromCharCode(chr2); }				if (enc4 !== 64) { output += String.fromCharCode(chr3); }			}			output = utf8_decode(output); return output; };		utf8_encode = function (str) { var utftext = '', n = 0, len, c;			str = str.replace(/\r\n/g, "\n"); len = str.length; for (n; n < len; n += 1) { c = str.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if ((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); }			}			return utftext; };		utf8_decode = function (utftext) { var str = '', i = 0, c = 0, c1 = 0, c2 = 0, len = utftext.length; while (i < len) { c = utftext.charCodeAt(i); if (c < 128) { str += String.fromCharCode(c); i += 1; } else if ((c > 191) && (c < 224)) { c1 = utftext.charCodeAt(i + 1); str += String.fromCharCode(((c & 31) << 6) | (c1 & 63)); i += 2; } else { c1 = utftext.charCodeAt(i + 1); c2 = utftext.charCodeAt(i + 2); str += String.fromCharCode(((c & 15) << 12) | ((c1 & 63) << 6) | (c2 & 63)); i += 3; }			}			return str; };		/*jslint bitwise: false, regexp: false*/ return { encode: encode, decode: decode };	}); }; window.$API = new TW;

(function {	"use strict";	var extension = $API.create.extension("Parser");	extension.parse = function (input, add) {		var mode, tokens, str, c, subj, arr, parsed, len, i, callParserFunc, subjA, carr, restoreDefaults, j = 0, lenj, data, itemID = {}, currid, result = [], param, trim;		mode = {			name: false,			group: false,			param: false,			nameArr: false,			propertyArr: false,			valueArr: false,			property: false,			trim: true,			value: false,			quote: false,			barvar: false		};		tokens = [];		str = '';		subj = {};		subjA = {};		arr = [];		parsed = [];		carr = [];		len = input.length;		i = 0;		trim = function (str) {			/*jslint regexp: true */			var re = /\s*(.+?)\s*$/;			/*jslint regexp: false */			return str.match(re)[1];		};		restoreDefaults = function  {			mode = {				name: false,				group: false,				param: false,				nameArr: false,				propertyArr: false,				valueArr: false,				property: false, trim: true, value: false, quote: false, barvar: false };		};		tokens = [ {				name: "OPEN_GROUP", token: "{", func: function { // enter group mode mode.group = true; // arr is the 1st level of the group. // arr should be edited by: // sounds: [ "MsgA", "MsgB", "MsgC" ]; // droppedby: [ "Rat", "Cave Rat" ]; if (arr.length === 0) { subj.value = arr; }					if (str !== '') { // if a string comes before the group, add it to the array // (why are we doing this again?) arr[arr.length] = str; str = ''; }					// errr... sounds and droppedby don't use this format, so it shouldn't be here ... // arr[arr.length] = carr; mode.trim = true; }			},			{				name: "WHITESPACE", token: " ", func: function { // trim whitespace if in trim mode if (!mode.trim) { str += c;					} }			},			{				name: "DOT", token: ".", func: function { // dot changes mode from property to name // don't do it if the mode property is not active if (mode.property) { // we're done with editing the property, so set property = str; subj.property = str; // reset str str = ''; // trim mode because names are allowed to have spaces mode.trim = false; // now edit the name mode.name = true; // no longer in property mode mode.property = false; } else { str += c;					} }			},			{				name: "COLON", token: ":", func: function { if (mode.group && mode.barvar) { subjA.name = trim(str); str = ''; mode.nameArr = false; mode.valueArr = true; } else { str += c;					} }			},			{				name: "COMMA", token: ",", func: function { // the comma changes from name -> value // only if in group mode, and not in literal mode if (mode.group && !mode.quote) { if (mode.barvar) { // dealing with convention {|a,b;c} subjA.name = trim(str); str = ''; mode.nameArr = false; mode.valueArr = true; } else { // dealing with convention { "a", "b", "c" } if (str !== '') { arr[arr.length] = str; str = ''; // whitespace should be trimmed until the next "								mode.trim = true;							}						}					} else {						str += c;					}				}			},			{				name: "SEMICOLON",				token: ";",				func: function {					if (mode.group && mode.barvar) {						// semicolon changes mode from nameArr or valueArr to propertyArr						// only if in group & not sounds !(!barvar)						if (mode.valueArr || mode.nameArr) {							if (mode.valueArr) {								subjA.value = str;								str = ;							} else {								subjA.name = trim(str);								str = ;							}							mode.propertyArr = true;							mode.valueArr = mode.nameArr = false;						}					} else if (mode.group && !mode.barvar) {						arr[arr.length] = str;						str = '';						// whitespace should be trimmed until the next " mode.trim = true; } else { str += c;					} }			},			{				name: "QUOTE", token: "\"",				func: function {					// change to literal mode					mode.quote = !mode.quote;					// literal mode = no trimming					mode.trim = !mode.quote;				}			},			{				name: "BAR",				token: "|",				func: function  {					// bar starts a new variable = trim mode					mode.trim = true;					// here's where it gets confusing ...					// if we're in a group the behavior is different					if (mode.group) {						// in group mode, | separates items, but only if using the 'trade convention'						// format is: { |item1, value1, meta1 |... }						if (str !== ) {							if (mode.propertyArr) {								subjA.property = str;								str = ;							} else if (mode.valueArr) {								subjA.value = str;								str = ;							} else {								subjA.name = trim(str);								str = ;							}						} else if (mode.nameArr) {							subjA.name = trim(str);							str = '';						}						if (subjA.name || subjA.value || subjA.property) { arr[arr.length] = subjA; }						subjA = {}; mode.propertyArr = mode.valueArr = false; mode.nameArr = mode.barvar = true; mode.trim = false; } else { if (str !== '') { if (mode.value) { subj.value = str; str = ''; } else { subj.name = str; str = ''; }						}						mode.value = false; if (subj.name || subj.value) { parsed[parsed.length] = subj; str = ''; subj = {}; }						// restore defaults restoreDefaults; mode.property = true; }				}			},			{				name: "EQUALS", token: "=", func: function { if (!mode.group) { mode.trim = false; if (mode.name) { mode.name = false; mode.value = true; subj.name = str; str = ''; } else { mode.property = false; mode.value = true; subj.property = str; str = ''; }					} else { str += c;					} }			},			{				name: "CLOSE_GROUP", token: "}", func: function { if (!mode.barvar) { // for sounds etc.						arr[arr.length] = str; subj.value = arr; str = ''; arr = []; } else { // for trades etc.						if (mode.nameArr) { subjA.name = trim(str); } else if (mode.valueArr) { subjA.value = str; } else if (mode.propertyArr) { subjA.property = str; }						str = ''; arr[arr.length] = subjA; carr = []; arr = []; subjA = {}; mode.barvar = false; }					mode.nameArr = mode.valueArr = mode.propertyArr = mode.group = false; }			},			{				name: "END_OF_FILE", token: "_EOF_", func: function { /*					if (subj.name || subj.value || subj.property) { parsed[parsed.length] = subj; subj = {}; str = ''; }					*/				}			}		];		callParserFunc = function (array, property, value) { var len = array.length, i = 0; for (i; i < len; i += 1) { if (array[i][property] === value) { array[i].func; return true; }			}			return false; };		for (i; i < len; i += 1) { c = input.charAt(i); if (!callParserFunc(tokens, 'token', c)) { str += c;			} }		callParserFunc(tokens, 'token', '|'); if (str !== '') { if (mode.property) { subj.property = str; } else if (mode.value) { if (subj.value.length) { subj.value[subj.value.length] = str; } else { subj.value = str; }			} else if (mode.name) { subj.name = str; }			str = ''; parsed[parsed.length] = subj; subj = {}; }		lenj = parsed.length; for (j; j < lenj; j += 1) { data = parsed[j]; if (data.name) { currid = itemID[data.name]; if (currid === undefined) { currid = itemID[data.name] = result.length; result[currid] = { "name": data.name, "id": currid }; if (add) { for (param in add) { if (add.hasOwnProperty(param)) { result[currid][param] = add[param]; }						}					}				}				if (data.property && data.value) { result[currid][data.property] = data.value; }			}		}		return result; }; }); (function { "use strict"; var extension = $API.create.extension("Edit"); extension.submit = function { $API.ext.Ajax.update($API.edit.changed || {}, function {			$API.edit.changed = {};		}); }; }); (function { "use strict"; var $extension = $API.create.extension("Item"), init; /* Item extension * Purpose: * Downloads & parses data files @ %API%/Item/Data%n% * Sets $API.data.item = [ the_parsed_data ]; */	$extension.loadData = function { var datafiles, datafileslen, loaderCallback, data = [], datafilesloaded = 0, i = 0; datafiles = [ "/wiki/Sixtest_Wiki:API/Item/Data1", "/wiki/Sixtest_Wiki:API/Item/Data2", "/wiki/Sixtest_Wiki:API/Item/Data3" ];		datafileslen = datafiles.length; loaderCallback = function (i) { // Used by $.ajax to put response text to an array. Should be done // like this because of the asynchronous nature of ajax; another // version of i is created in this scope so that variables are // assigned property when working asynchronously. return function (text) { var j = 0, len = datafileslen, datastr = ''; data[i] = text; datafilesloaded += 1; if (datafilesloaded === datafileslen) { for (j; j < len; j += 1) { datastr += data[j]; }					$extension.assign($API.ext.Parser.parse(datastr, { dataType: "Item" })); }			};		};		for (i; i < datafileslen; i += 1) { $.ajax({				url: datafiles[i] + "?action=raw",				dataType: 'text',				success: loaderCallback(i)			}); }	};	$extension.assign = function (data) { $extension.data = $API.data.item = $API.create.list("item", data); return $extension.data; };	if (init !== false) { $extension.loadData; }	/* Layout for items */ $extension.layout = function (data, container) { var $cont = container, params = { 'name': '0', 'flavortext': '0', 'implemented': '0', 'range': '1', 'levelrequired': '1', 'vocrequired': '1', 'attack': '1', 'defense': '1', 'defensemod': '1', 'elementattack': '1', 'resist': '1', 'armor': '1', 'weight': '1', 'volume': '1', 'itemclass': '2', 'primarytype': '2', 'secondarytype': '2', 'hands': '2', 'type': '2', 'attrib': '3', 'stackable': '3', 'writable': '3', 'writechars': '3', 'enchantable': '3', 'edible': '3', 'regenseconds': '3', 'npcvalue': '4', 'npcvaluerook': '4', 'npcprice': '4', 'npcpricerook': '4', 'buyfrom': '4', 'sellto': '4', 'droppedby': '5'}, elements = {}; container.ele.className = 'SearchSuggest_Display' + ' ' + 'item' + ' ' + ($API.user.edittools ? 'edittools-enabled' : 'edittools-disabled'); (function init {			var groups, funcGeneric, property, i, len, objprop, base;			// generic function for all params			funcGeneric = function (base, data, param, func) {				var obj = base.child('div');				func = func || function (obj) {					// main function here					return obj;				};				base.ele.className += (base.ele.className ? ' ' : '') + 'p-' + param;				obj.ele.className = param;				obj.ele.appendChild(document.createTextNode(data[param]));				obj = func(obj);				return obj;			};			// list of grouped parameters			if ($API.user && $API.user.ext && $API.user.ext.Item && $API.user.ext.Item.layout) {				// allows for a customized grouping - just a test!				groups = $API.user.ext.Item.layout;			} else {				groups = {					"general": [ "name", "flavortext", "implemented" ],					"data": [ "range", "levelrequired", "vocrequired", "attack", "defense", "defensemod", "elementattack", "resist", "armor", "weight", "volume" ],					"classification": [ "itemclass", "primarytype", "secondarytype", "hands", "type" ],					"additional": [ "attrib", "stackable", "writable", "writechars", "enchantable", "edible" ],					"trades": [ "npcvalue", "npcvaluerook", "npcprice", "npcpricerook", "buyfrom", "sellto" ],					"droppedby": [ "droppedby" ] };			}			// iterate over the data's properties for (property in data) { if (data.hasOwnProperty(property)) { for (objprop in groups) { if (groups.hasOwnProperty(objprop)) { i = 0; len = groups[objprop].length; for (i; i < len; i += 1) { if (groups[objprop][i] === property) { if (!elements[objprop]) { elements[objprop] = container.child('div'); }									base = elements[objprop]; funcGeneric(base, data, property); }							}						}					}				}			}		});	}; }); (function {	"use strict";	var $extension = $API.create.extension("Spell"),		init;	/* Spell extension	 * Purpose:	 * Downloads & parses data files @ %API%/Spell/Data%n%	 * Sets $API.data.spell = [ the_parsed_data ];	 * Defines the default layout of spells.	 */	$extension.loadData = function  {		var datafiles, datafileslen, loaderCallback, data = [], datafilesloaded = 0, i = 0;		datafiles = [			"/wiki/Sixtest_Wiki:API/Spell/Data1"		];		datafileslen = datafiles.length;		loaderCallback = function (i) {			// Used by $.ajax to put response text to an array. Should be done			// like this because of the asynchronous nature of ajax; another			// version of i is created in this scope so that variables are			// assigned property when working asynchronously.			return function (text) {				var j = 0, len = datafileslen, datastr = '';				data[i] = text; datafilesloaded += 1; if (datafilesloaded === datafileslen) { for (j; j < len; j += 1) { datastr += data[j]; }					$extension.assign($API.ext.Parser.parse(datastr, { dataType: 'Spell' })); }			};		};		for (i; i < datafileslen; i += 1) { $.ajax({				url: datafiles[i] + "?action=raw",				dataType: 'text',				success: loaderCallback(i)			}); }	};	$extension.assign = function (data) { $extension.data = $API.data.spell = $API.create.list("spell", data); return $extension.data; };	$extension.layout = function (data, container) { // TODO: cleanup .... var $cont = container; container.ele.className = 'SearchSuggest_Display' + ' ' + 'spell' + ' ' + ($API.user.edittools ? 'edittools-enabled' : 'edittools-disabled'); (function init {			var groups, funcGeneric, property, i, len, objprop, base, elements = {};			// generic function for all params			funcGeneric = function (base, data, param, func) {				var obj = base.child('div'),					isArray = (typeof data[param] === 'object'),					arrayOfObjects = (isArray ? (typeof data[param][0] === 'object') : false),					propLen = (isArray ? data[param].length : 0),					$cdiv,					$cdivi,					i;				base.ele.className += (base.ele.className ? ' ' : '') + 'p-' + param;				obj.ele.className = param;				//obj.ele.appendChild(document.createTextNode(data[param]));				if (isArray) {					if (arrayOfObjects) {						// dealing with e.g. trades which isn't a simple array (can be [ {name:'Axe',value:'2'} ] for example)						// make header text						$cdiv = obj.child('div');						$cdiv.ele.className = 'header-' + param;						$cdiv.ele.appendChild(document.createTextNode(param.charAt(0).toUpperCase + param.slice(1)));						// iterate over the data						i = 0;						for (i; i < propLen; i += 1) {							if (data[param][i].name) {								$cdivi = $cdiv.child('div');								$cdivi.ele.className = param + '-' + i;								$cdivi.ele.appendChild(document.createTextNode(data[param][i].name));								if (data[param][i].value) {									$cdivi.ele.className += ' bad';								}							}						}					} else {						// dealing with single-level array like sounds: [ "A", "B", "C" ]; i = 0; for (i; i < propLen; i += 1) { $cdiv = obj.child('div'); $cdiv.ele.className = param + '-' + i;							$cdiv.ele.appendChild(document.createTextNode(data[param][i])); }					}				} else { // dealing with simple string (e.g. name, location) obj.ele.appendChild(document.createTextNode(data[param])); }				func = func || function (obj) { // main function here return obj; };				obj = func(obj); return obj; };			// list of grouped parameters if ($API.user && $API.user.ext && $API.user.ext.spell && $API.user.ext.spell.layout) { // allows for a customized grouping - just a test! groups = $API.user.ext.spell.layout; } else { groups = { "general": [ "name", "implemented" ], "data": [ ], "classification": [ "job", "city" ], "additional": [ "location" ], "sounds": [ "sounds" ], "trades": [ "buysell", "buys", "sells" ] };			}			// iterate over the data's properties for (property in data) { if (data.hasOwnProperty(property)) { for (objprop in groups) { if (groups.hasOwnProperty(objprop)) { i = 0; len = groups[objprop].length; for (i; i < len; i += 1) { if (groups[objprop][i] === property) { if (!elements[objprop]) { elements[objprop] = container.child('div'); }									base = elements[objprop]; funcGeneric(base, data, property); }							}						}					}				}			}		});	};	if (init !== false) {		$extension.loadData;	} }); (function {	"use strict";	var $extension = $API.create.extension("NPC"),		init;	/* Item extension	 * Purpose:	 * Downloads & parses data files @ %API%/NPC/Data%n%	 * Sets $API.data.npc = [ the_parsed_data ];	 */	$extension.loadData = function  {		var datafiles, datafileslen, loaderCallback, data = [], datafilesloaded = 0, i = 0;		datafiles = [			"/wiki/Sixtest_Wiki:API/NPC/Data1",			"/wiki/Sixtest_Wiki:API/NPC/Data2",			"/wiki/Sixtest_Wiki:API/NPC/Data3"		];		datafileslen = datafiles.length;		loaderCallback = function (i) {			// Used by $.ajax to put response text to an array. Should be done			// like this because of the asynchronous nature of ajax; another			// version of i is created in this scope so that variables are			// assigned property when working asynchronously.			return function (text) {				var j = 0, len = datafileslen, datastr = '';				data[i] = text;				datafilesloaded += 1; if (datafilesloaded === datafileslen) { for (j; j < len; j += 1) { datastr += data[j]; }					$extension.assign($API.ext.Parser.parse(datastr, { dataType: 'NPC' })); }			};		};		for (i; i < datafileslen; i += 1) { $.ajax({				url: datafiles[i] + "?action=raw",				dataType: 'text',				success: loaderCallback(i)			}); }	};	$extension.assign = function (data) { $extension.data = $API.data.npc = $API.create.list("npc", data); return $extension.data; };	$extension.layout = function (data, container) { // TODO: cleanup .... var $cont = container; container.ele.className = 'SearchSuggest_Display' + ' ' + 'npc' + ' ' + ($API.user.edittools ? 'edittools-enabled' : 'edittools-disabled'); (function init {			var groups, funcGeneric, property, i, len, objprop, base, elements = {};			// generic function for all params			funcGeneric = function (base, data, param, func) {				var obj = base.child('div'),					isArray = (typeof data[param] === 'object'),					arrayOfObjects = (isArray ? (typeof data[param][0] === 'object') : false),					propLen = (isArray ? data[param].length : 0),					$cdiv,					$cdivi,					i;				base.ele.className += (base.ele.className ? ' ' : '') + 'p-' + param;				obj.ele.className = param;				//obj.ele.appendChild(document.createTextNode(data[param]));				if (isArray) {					if (arrayOfObjects) {						// dealing with e.g. trades which isn't a simple array (can be [ {name:'Axe',value:'2'} ] for example)						// make header text						$cdiv = obj.child('div');						$cdiv.ele.className = 'header-' + param;						$cdiv.ele.appendChild(document.createTextNode(param.charAt(0).toUpperCase + param.slice(1)));						// iterate over the data						i = 0;						for (i; i < propLen; i += 1) {							if (data[param][i].name) {								$cdivi = $cdiv.child('div');								$cdivi.ele.className = param + '-' + i;								$cdivi.ele.appendChild(document.createTextNode(data[param][i].name));								if (data[param][i].value) {									$cdivi.ele.className += ' bad';								}							}						}					} else {						// dealing with single-level array like sounds: [ "A", "B", "C" ]; i = 0; for (i; i < propLen; i += 1) { $cdiv = obj.child('div'); $cdiv.ele.className = param + '-' + i;							$cdiv.ele.appendChild(document.createTextNode(data[param][i])); }					}				} else { // dealing with simple string (e.g. name, location) obj.ele.appendChild(document.createTextNode(data[param])); }				func = func || function (obj) { // main function here return obj; };				obj = func(obj); return obj; };			// list of grouped parameters if ($API.user && $API.user.ext && $API.user.ext.NPC && $API.user.ext.NPC.layout) { // allows for a customized grouping - just a test! groups = $API.user.ext.NPC.layout; } else { groups = { "general": [ "name", "implemented" ], "data": [ ], "classification": [ "job", "city" ], "additional": [ "location" ], "sounds": [ "sounds" ], "trades": [ "buysell", "buys", "sells" ] };			}			// iterate over the data's properties for (property in data) { if (data.hasOwnProperty(property)) { for (objprop in groups) { if (groups.hasOwnProperty(objprop)) { i = 0; len = groups[objprop].length; for (i; i < len; i += 1) { if (groups[objprop][i] === property) { if (!elements[objprop]) { elements[objprop] = container.child('div'); }									base = elements[objprop]; funcGeneric(base, data, property); }							}						}					}				}			}		});	};	if (init !== false) {		$extension.loadData;	} }); (function {	"use strict";	// TODO: make reusable	// extension	var extension = $API.create.extension('SearchSuggest'),		// api references		$ui,		$root,		// variables		elements = {},		focus = 0,		// variables		searchResults,		// functions		init,		// event functions		e_onkeyup,		e_onkeydown,		e_onmouseover;	$API.user.edittools = ($API.user.edittools === undefined ? true : $API.user.edittools);	$ui = $API.ui || $API.create.ui;	$root = $ui.query(function (d) { return d.level === 0 && d.ele.className === 'APIRoot'; }, 1);	init = function {		// initialize the extension ... add elements / apply css classes / js properties etc.		if (!extension.initiated) {			// search - main container			elements.main = $root.data[0].child('div');			elements.main.ele.className = 'SearchSuggest';			// search - left container (item view box)			elements.left = elements.main.child('div');			elements.left.ele.className = 'SearchSuggestLeft';			// search - right container (search results box)			elements.right = elements.main.child('div');			elements.right.ele.className = 'SearchSuggestRight';			// search - left - input box			elements.input = elements.left.child('input');			elements.input.ele.className = 'SearchSuggest_Input';			// search - left - search suggestions			elements.output = elements.left.child('div'); elements.output.ele.className = 'SearchSuggest_Output'; // search - right - display elements.display = elements.right.child('div'); elements.display.ele.className = 'SearchSuggest_Display'; // events for search suggestion / browsing elements.input.ele.onkeyup = e_onkeyup(elements.input, elements.output, "all"); elements.input.ele.onkeydown = e_onkeydown(elements.input, elements.output, "all"); elements.output.ele.onmouseover = e_onmouseover(elements.input, elements.output, "all"); // css $API.create.stylesheet('/Item/css'); }		// set initiated = true so this won't be rerun return true; };	/* Search suggestion function */ extension.searchSuggest = function (element, target, dataType, amount) { var re, i, len, dataobj; // if element/target is undefined, use the defaults element = element || elements.input; target = target || elements.output; dataType = dataType || 'all'; amount = amount || 10; function addSuggestion(name) { // name - item to suggest var e = target.child('div'); e.ele.appendChild(document.createTextNode(name)); }		// regex to search re = (element.value === undefined ? element.ele.value : element.value).toLowerCase; // clear search suggestion box from last time while (target.ele.hasChildNodes) { target.ele.removeChild(target.ele.lastChild); }		// reset the focus or it may bug upon search focus = 0; if (dataType === 'all' && $API.data.all === undefined) { // merge all data... $API.data.merge; }		// the data we are searching: default to "all" dataobj = $API.data[dataType] || $API.data.all; searchResults = dataobj.query(function (d) {			// get results from regex			var name = d.name.toLowerCase;			return name.match(re);		}, amount).data; for (i = 0, len = searchResults.length; i < len; i += 1) { addSuggestion(searchResults[i].name, target); }	};	extension.displayItem = function (id) { var item, property, strOut = '', tools, toggleTools, edit; toggleTools = function { var banned = $API.user.banned, active = $API.user.edittools; if (!elements.display.ele.children.length || !elements.edittools.ele.children.length) { // does this cause memory leak? shouldn't ... elements.edittools = elements.display.child('div'); elements.edittoolsActive = elements.edittools.child('img'); elements.edittoolsMain = elements.edittools.child('div'); elements.edittoolsPublish = elements.edittoolsMain.child('div'); elements.edittoolsPublishLink = elements.edittoolsPublish.child('a'); elements.edittoolsPublishLink.ele.appendChild(document.createTextNode('Publish')); } else { $API.user.edittools = active = !active; }			$(elements.display.ele).removeClass('edittools-' + (!(active && !banned) ? 'enabled' : 'disabled')); $(elements.display.ele).addClass('edittools-' + (active && !banned ? 'enabled' : 'disabled')); elements.edittools.ele.className = 'toolbox'; elements.edittoolsMain.ele.className = 'edittools'; elements.edittoolsPublish.ele.className = 'edittoolspublish'; elements.edittoolsActive.ele.title = "Edit tools are " + (active && !banned ? 'enabled' : 'disabled') + "." + (banned ? "" : " Click to toggle."); elements.edittoolsActive.ele.src = (banned ? 'http://images.wikia.com/sixtest/images/f/f0/Tools-Banned.png' : (active ? 'http://images.wikia.com/sixtest/images/d/d3/Tools-Active.png' : 'http://images.wikia.com/sixtest/images/6/69/Tools-Inactive.png')); elements.edittoolsPublishLink.ele.onclick = (active && !banned ? $API.ext.Edit.submit : null); elements.edittoolsPublishLink.ele.title = (active && !banned ? "Submit all the data you have changed this session." : ''); };		if (typeof id === 'string') { item = $API.data.item.query(function (data) {				return data.name === id;			}).data[0]; } else if (typeof id === 'number') { item = searchResults[id]; }		if (item !== undefined) { // first remove existing items if any have been requested if (elements.display === undefined) { elements.display = elements.main.child('div'); //elements.display.ele.className = 'SearchSuggest_Display'; }			// clear the DOM of child elements // to prevent memory leak, destroy their associated objects by clearing all references ($API.data.ui, [parent].children) elements.display.destroy; // search - right - edittools toggleTools; elements.edittoolsActive.ele.onclick = toggleTools; if (item && item.dataType && $API.ext[item.dataType] && $API.ext[item.dataType].layout) { $API.ext[item.dataType].layout(item, elements.display); }		}	};	extension.listFocus = function (target, offset) { var children = (target || elements.output).ele.children, focusElement = children[focus]; focusElement.className = ''; if (typeof offset === 'number') { focus = (focus + offset + children.length) % children.length; }		focusElement = children[focus]; focusElement.className = 'SSactive'; extension.displayItem(focus); };	e_onkeydown = function (element, target, type, amount) { element = element || elements.output; target = target || elements.output; return function (e) { var rtn; if (e.keyCode === 40) { rtn = extension.listFocus(target, +1); } else if (e.keyCode === 38) { rtn = extension.listFocus(target, -1); } else { rtn = extension.searchSuggest(element, target, type, amount); }			return rtn; };	};	e_onmouseover = function (element, target, type, amount, display) { element = element || elements.input.ele; target = target || elements.output; display = display !== false; return function (e) { var hoverElement = e.srcElement || e.originalTarget, i = 0, children = target.ele.children, childrenLen = children.length; for (i; i < childrenLen; i += 1) { if (children[i] === hoverElement) { children[focus].className = ''; focus = i;					children[focus].className = 'SSactive'; if (display) { extension.displayItem(focus); } else { element.value = hoverElement.childNodes[0].data; }					return true; }			}			return false; };	};	e_onkeyup = function (element, target, type, amount) { element = element || elements.output; target = target || elements.output; return function (e) { var rtn = false; if (e.keyCode !== 40 && e.keyCode !== 38) { rtn = extension.searchSuggest(element, target, type, amount); }			return rtn; };	};	extension.employ = function (element, target, type, amount) { // attaches an instance of the search suggest function // i.e. the mouse / key events and suggestions var val = element.value || element.ele.value; type = type || 'all'; element = element.ele || element; element.onkeyup = e_onkeyup(element, target, type, amount); element.onkeydown = e_onkeydown(element, target, type, amount); target.ele.onmouseover = e_onmouseover(element, target, type, amount, false); };	extension.initialized = extension.initialized || init; }); (function ajax { "use strict"; var extension = $API.create.extension('Ajax'), paused = false, pausedIntervalChecker = 500, pausedCheckState; // maximum running ajax connections extension.max = 3; // properties to ignore (pagename) extension.pause = function { paused = !paused; };	pausedCheckState = function (func) { var interval, check; check = function { if (!paused) { clearInterval(interval); func; }		};		interval = setInterval(check, pausedIntervalChecker); };	// method to get files with pagenames extension.get = function (pagenames, callback) { var results = [], len, ajax, get, done = 0; if (pagenames === undefined || pagenames.length === undefined || pagenames[0].length === undefined) { pagenames = extension.throttle(pagenames); }		len = pagenames.length; ajax = function (i, j) { if (results[i] === undefined) { results[i] = []; }			return function (response) { results[i][j] = response; done += 1; if (done === pagenames[i].length) { i += 1; if (i < len) { get(i); } else if (typeof callback === 'function') { callback(results); }				}			};		};		get = function (i) { var j = 0, len = pagenames[i].length, ajaxFunc; done = 0; ajaxFunc = function (j) { return function { $.ajax({						url: pagenames[i][j].pagename + '?action=raw',						cache: false,						type: 'GET',						success: ajax(i, j)					}); };			};			for (j; j < len; j += 1) { pausedCheckState(ajaxFunc(j)); }		};		get(0); };	extension.post = function (pagenames, texts, callback) { var done = 0, submit, data = [], i, len, func; pagenames = extension.unthrottle(pagenames); texts = extension.unthrottle(texts); i = 0; len = pagenames.length; for (i; i < len; i += 1) { data[i] = { name: pagenames[i], text: texts[i] };		}		data = extension.throttle(data); func = function (i) { return function (response) { if (response) { done += 1; if (done === data[i].length) { done = 0; i += 1; if (i < data.length) { submit(i); } else if (typeof callback === 'function') { callback; }					}				}			};		};		submit = function (i) { var j = 0, len = data[i].length, ajaxFunc; ajaxFunc = function (j) { return function { $.ajax({						url: '/api.php',						type: 'POST',						dataType: 'json',						data : {							'action': 'edit',							'title': data[i][j].name,							'format': 'json',							'text': data[i][j].text,							'token': $API.user.edittoken,							'minor': 'minor',							'summary': 'JS edit test.'						},						success: func(i)					}); };			};			for (j; j < len; j += 1) { pausedCheckState(ajaxFunc(j)); }		};		submit(0); };	extension.unthrottle = function (arrs) { var newArr = [], merge; merge = function (arr) { var i = 0, len = arr.length; for (i; i < len; i += 1) { if (typeof arr[i] === 'object' && arr[i].length) { merge(arr[i]); } else { newArr[newArr.length] = arr[i]; }			}		};		merge(arrs); return newArr; };	extension.throttle = function (obj, amount) { var property, arr, split; split = function (arr, arrayLength) { var i = 0, curr, len = arr.length, arrs = []; if (arrayLength >= 1) { for (i; i < len; i += 1) { curr = Math.floor(i / arrayLength); if (arrs[curr] === undefined) { arrs[curr] = []; }					arrs[curr][arrs[curr].length] = arr[i]; }				i = 0; len = arrs.length; return arrs; }			return false; };		amount = amount || 3; if (obj.length === undefined) { arr = []; for (property in obj) { if (obj.hasOwnProperty(property)) { obj[property].pagename = property; arr[arr.length] = obj[property]; }			}		}		arr = split((arr || obj), amount); return arr; };	extension.getToken = function (callback) { var ajaxFunc; ajaxFunc = function { return function { $.ajax({					url: '/api.php',					type: 'GET',					dataType: 'json',					data: {						'action': 'query',						'format': 'json',						'prop': 'info',						'intoken': 'edit',						'titles': 'Main Page'					},					success: function (obj) {						var querypages, querypage;						if (obj !== undefined && obj.query !== undefined && obj.query.pages !== undefined) {							querypages = obj.query.pages;						}						for (querypage in querypages) {							if (querypages.hasOwnProperty(querypage) && querypages[querypage].edittoken) {								$API.user.edittoken = querypages[querypage].edittoken;								callback;							}						}					}				}); };		};		if ($API.user.edittoken !== undefined && $API.user.edittoken !== '') { callback; } else { /* Get edittoken only if they don't have one yet */ if ($API.user === undefined) { $API.user = {}; }			pausedCheckState(ajaxFunc); }	};	extension.format = function (data, callback) { data = extension.throttle(data, extension.max); extension.get(data, function (responses) {			var regex, i, len, property, match, j, jlen, names = [], texts = [];			i = 0;			len = responses.length;			for (i; i < len; i += 1) {				names[i] = [];				texts[i] = [];				j = 0;				jlen = responses[i].length;				for (j; j < jlen; j += 1) {					names[i][j] = data[i][j].pagename;					for (property in data[i][j]) {						if (data[i][j].hasOwnProperty(property)) {							regex = new RegExp("(\\|\\s*" + property + "\\s*=\\s*)([\\s\\S]+?)[\\n\\s]+(\\|[\\n\\s]*[\\s\\S]+=|\\}\\}[\\s\\n]*$)");							match = responses[i][j].match(regex);							if (match !== null) {								texts[i][j] = (texts[i][j] || responses[i][j]).replace(match[0], match[1] + data[i][j][property] + "\n" + match[3]);							}						}					}				}			}			callback(names, texts);		}); };	extension.update = function (data, callback) { extension.getToken(function {			extension.format(data, function (names, texts) { extension.post(names, texts, callback); });		});	}; });