Μετά την αποθήκευση πρέπει να καθαρίσετε την κρυφή μνήμη (cache) του browser σας για να δείτε τις αλλαγές: Σε Chrome, Firefox, Safari, Internet Explorer και Edge: Κρατήστε πατημένο το Shift και κάντε κλικ στο κουμπί Ανανέωση στην μπάρα εργαλείων.

/**
 * THE ИƎTTIЯWƎЯ RATER
 */
/*jshint shadow:true, latedef:true, boss:true, scripturl:true, loopfunc:true, undef:true */
/*global $, mw, importStylesheet, wgScript, wgNamespaceIds, wgFormattedNamespaces, wgNamespaceNumber, wgPageName, wgTitle, wgAction */
mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'jquery.ui'], function () {
"use strict";

if (mw.config.get('wgNamespaceNumber') < 0)
	return;
	
mw.loader.load( 'https://en.wikipedia.org/w/index.php?title=User:Kephir/gadgets/rater.css&action=raw&ctype=text/css', 'text/css' );

/*
== Code ==
 */
var api = new mw.Api();

/*
=== Template data ===
 */
var raterData = {};
	
function getRaterData(kind) {
	if (raterData[kind] === void(null)) {
		try {
			$.ajax({
				'url': wgScript + '?action=raw&ctype=application/json&maxage=86400&title=User:Geraki/rater/' + kind + '.js',
				'dataType': 'json',
				'async': false,
				'success': function (data) {
					raterData[kind] = data;
				},
				'error': function (xhr, message) {
					throw new Error(message);
				}
			});
		}  catch (e) {
			alert('Error retrieving Rater "' + kind + '" data: ' + e.message + '. Rater will probably fail to work.');
			raterData[kind] = null;
		}
	}
	return raterData[kind];
}

var projectKeywords = null;

function getKeywordsMapping() {
	if (projectKeywords === null) {
		var projects = getRaterData('projects');
		projectKeywords = {};
		for (var key in projects) {
			for (var i = 0; i < projects[key].length; ++i) {
				projectKeywords[projects[key][i].toLowerCase()] = key;
			}
		}
	}
	return projectKeywords;
}

function cloneInto(what, target) {
	if (typeof what === 'object') {
		if (what === null)
			return what;
		if ((target === null) || (typeof target !== 'object'))
			target = {};
		for (var key in what) {
			target[key] = cloneInto(what[key], target[key]);
		}
		return target;
	} else
		return what;
}

function clone(what) {
	return cloneInto(what, null);
}

var projectData = {};
function getTemplateInfo(name) {
	if (projectData[name] === void(null)) {
		try {
			$.ajax({
				'url': wgScript + '?action=raw&ctype=application/json&maxage=86400&title=Template:' + name + '/rater-data.js',
				'dataType': 'json',
				'async': false,
				'success': function (data) {
					projectData[name] = data;
				},
				'error': function (xhr, message) {
					if (xhr.status === 404) { // just pretend nothing happened.
						projectData[name] = null;
						return;
					}
					throw new Error(message);
				}
			});
		}  catch (e) {
			alert('Αποτυχία στην ανάκτηση δεδομένων προτύπου για το "' + name + '": ' + e.message + '. Παρακαλούμε διορθώστε το αν μπορείτε. Κατάπτωση στις προεπιλογές, που μπορεί να μην είναι ακριβείς.');
			projectData[name] = null;
		}
	}
	return projectData[name];
}
	
function wantedTemplate(name) {
	var projects = getRaterData('projects');
	return name in projects;
}

function normaliseTitle(name) {
	var aliases = getRaterData('aliases');
	name = (new mw.Title(name)).getMainText();
	if (aliases[name])
		name = aliases[name];
	return name;
}

/*
=== Template object ===
 */
function ProjectTemplate(name, params, postws) {
	var sumtrack = {};
	var isnew = (params === null);
	var dropped = false;
	var tpinfo = getTemplateInfo(name);
	var defdata = getRaterData('default');
	var fallback;

	if (tpinfo === null || typeof(tpinfo) === 'undefined') {
		tpinfo = {};
		fallback = true;
	}
	tpinfo = cloneInto(tpinfo, clone(defdata));

	// make data nice
	if (tpinfo.taskforces) {
		if (tpinfo.taskforces.items) {
			var newtf = [];
			for (var key in tpinfo.taskforces.items) {
				var tfe = {
					'name': tpinfo.taskforces.items[key],
					'part': (typeof tpinfo.taskforces.partsuf === 'string') ? key + tpinfo.taskforces.partsuf : null,
					'prio': (typeof tpinfo.taskforces.priosuf === 'string') ? key + tpinfo.taskforces.priosuf : null
				};
				newtf[newtf.length] = tfe;
			}
			tpinfo.taskforces = newtf;
		}
	} else
		tpinfo.taskforces = [];

	for (var key in tpinfo.params)
		if (tpinfo.params[key] === null)
			delete tpinfo.params[key];

	for (var i = 0; i < tpinfo.taskforces.length; ++i) {
		var tf = tpinfo.taskforces[i];
		if (tf.part) {
			tpinfo.params[tf.part] = cloneInto(tpinfo.params[tf.part] || {}, {
				'group': 'task'
			});
		}
		if (tf.prio) {
			tpinfo.params[tf.prio] = cloneInto(tpinfo.params[tf.prio] || {}, {
				'group': 'task'
			});
		}
		if (tf.prio && tf.part) {
			tpinfo.params[tf.prio].implies = tf.part;
		}
	}

	params = params || {};

	// process params
	for (var key in params) {
		if ((key in tpinfo.params) && (tpinfo.params[key].alias)) {
			params[tpinfo.params[key].alias] = params[key];
			delete params[key];
		}
	}

	this.isFallback = function () {
		return fallback;
	};

	this.getParam = function (key) {
		return key in params ? params[key] : null;
	};
	
	this.setParam = function (key, value) {
		if ((value === null) || (value === void(null)))
			return this.delParam(key);
		params[key] = String(value);
		sumtrack[key] = String(value);
		return value;
	};
	
	this.delParam = function (key) {
		delete params[key];
		sumtrack[key] = false;
		return void(null);
	};

	this.serialise = function () {
		if (dropped)
			return '';
		var result = '{{' + name;
		for (var key in params) {
			result += ' |' + key + '=' + params[key];
		}
		return result + '}}' + postws;
	};
	
	this.getChanges = function () {
		if (dropped)
			return isnew ? '' : '-' + this.getProjectName();
		var result = [];
		for (var key in sumtrack) {
			if (sumtrack[key] !== false)
				result[result.length] = key + '=' + sumtrack[key];
			else
				result[result.length] = '-' + key;
		}
		if (!result.length) {
			if (isnew)
				return '+' + this.getProjectName();
			return void(null);
		}
		return (isnew ? '+' : '') + this.getProjectName() + ': ' + result.join(", ");
	};
	
	this.getProjectName = function () {
		if (tpinfo.name)
			return tpinfo.name;
		return name.replace(/^Επιχείρηση /, '');
	};
	
	this.getTemplateName = function () {
		return name;
	};
	
	this.getParamData = function (key) {
		var defParamData = {
			'desc': key,
			'group': (key in tpinfo.params) ? 'main' : 'unk',
			'mandatory': false,
			'obsolete': false,
			'values': 'string',
			'defvalue': (key in tpinfo.params) ?
				( (tpinfo.params[key].values === 'flag-temp') ? 'y'
				: (tpinfo.params[key].values === 'flag-perm') ? 'n'
				: ''
			) : ''
		};
		return cloneInto(tpinfo.params[key] || {}, defParamData);
	};
	
	this.drop = function () {
		return dropped = !dropped;
	};

	this.forEachParam = function (walker) {
		for (var key in tpinfo.params) {
			if (tpinfo.params[key].alias)
				continue;
			if (walker(key, key in params ? params[key] : null, this.getParamData(key)))
				return;
		}
		for (var key in params) {
			if (!(key in tpinfo.params))
				if (walker(key, params[key], this.getParamData(key)))
					return;
		}
	};
	
	this.forEachTaskForce = function (walker) {
		for (var i = 0; i < tpinfo.taskforces.length; ++i) {
			var tf = tpinfo.taskforces[i];
			if (walker(tf.name, this.getParam(tf.part), this.getParam(tf.prio), tf.part, tf.prio))
				return;
		}
	};
}

/*
=== Interface ===
 */
function UserInterface() {
	if (this === window)
		return new UserInterface();

	var self = this;

	function el(tag, child, attr, events) {
		var node = document.createElement(tag);

		if (child) {
			if (typeof child !== 'object')
				child = [child];
			for (var i = 0; i < child.length; ++i) {
				var ch = child[i];
				if ((ch === void(null)) || (ch === null))
					continue;
				else if (typeof ch !== 'object')
					ch = document.createTextNode(String(ch));
				node.appendChild(ch);
			}
		}

		if (attr) for (var key in attr) {
			node.setAttribute(key, String(attr[key]));
		}

		if (events) for (var key in events) {
			node.addEventListener(key, events[key], false);
		}

		return node;
	}

	function link(child, href, attr, ev) {
		attr = attr || {};
		ev = ev || {};
		if (typeof href === 'string')
			attr.href = href;
		else {
			attr.href = 'javascript:void(null);';
			ev.click = href;
		}
		return el('a', child, attr, ev);
	}

	function pform(child, handler, attr) {
		attr = attr || {};
		attr.action = 'javascript:void(null);';
		return el('form', child, attr, { 'submit': handler });
	}
	
	var pluckedData = [];
	var tab = [null, null, null];
	
	var tabIsFresh = [false, false, false];
	var uiSource, uiPreview, uiStatus, uiTemplates, uiSummary, uiAddTemplName;
	var dirtySummary = false;
	
	var curTab = 0;
	function setTab(target) {
		tab[curTab].style.display = 'none';
		tab[curTab = target].style.display = '';
	}
	
	function switchTab(target) {
		if (!tabIsFresh[target]) {
			if (target === 2) { // preview
				var source = tabIsFresh[1] ? uiSource.value : serialise();
				api.post({
					'action': 'parse',
					'title': talkpage,
					'text': source,
					'pst': '1',
					'prop': 'text'
				}, {
					success: function (result) {
						uiPreview.innerHTML = result.parse.text['*']; // XXX
						setTab(target);
						tabIsFresh[2] = true;
					},
					error: function () {
						self.setStatus('Rendering error.');
					}
				});
				return;
			} else if (target === 1) { // source
				if (tabIsFresh[0]) {
					uiSource.value = serialise();
				}
			} else if (target === 0) { // editor
				try {
					self.extract(uiSource.value);
				} catch (e) {
					self.setStatus(e.message + ' — edit the source and try again.');
					return;
				}
			}
			tabIsFresh[target] = true;
		}
		
		setTab(target);
	}
	
	function tabSwitcher(target) {
		return function (ev) {
			switchTab(target);
		};
	}
	
	function serialise() {
		var result = '';
		for (var i = 0; i < pluckedData.length; ++i) {
			if (typeof pluckedData[i] === 'string')
				result += pluckedData[i];
			else if (pluckedData[i].serialise)
				result += pluckedData[i].serialise();
		}
		return result;
	}
	
	function gatherSummary() {
		var sums = [];
		for (var i = 0; i < pluckedData.length; ++i) {
			if (pluckedData[i].getChanges) {
				var ch = pluckedData[i].getChanges();
				if (ch) sums[sums.length] = ch;
			}
		}
		return 'Αποτίμηση: ' + sums.join('; ') + ' ';
	}

	var lastTemplate;
	var contig;

	var uiProjList = el('datalist');
	(function () {
		var projects = getRaterData('projects');
		for (var key in projects) {
			var pname = projects[key][0] || key.replace(/^Επιχείρηση /, '');
			uiProjList.appendChild(el('option', [pname], { 'value': pname }));
		}
	})();
	
	var uiBox = el('div', [
		// header
		el('h2', ['Assessment',
			el('span', ['\u00a0[', link('close', function (ev) {
				ev.preventDefault();
				self.show(false);
			}), ']'], { 'class': 'editsection' }),
			el('span', ['\u00a0[',
				'β (2012-11-11) | ',
				link('about', mw.util.getUrl('User:Kephir/gadgets/rater'), { 'title': 'Σχετικά' }), ' | ',
				link('feedback', '/wiki/User_talk:Geraki/rater?action=edit&section=new&preloadtitle=Ανατροφοδότηση', { 'title': 'Ανατροφοδότηση' }),
			']'], { 'class': 'editsection' })
		]),
			
		// tabs
		el('ul', [
			el('li', [link('Επεξεργαστής' , tabSwitcher(0))]),
			el('li', [link('Κώδικας' , tabSwitcher(1))]),
			el('li', [link('Προεπισκόπηση', tabSwitcher(2))])
		], { 'class': 'tabs' }),
			
		// body: main
		tab[0] = el('div', [
			uiTemplates = el('ul'),
			el('div', [
				'Notes',
				el('ul', [
					el('li', [
						'Some WikiProject templates (marked ', el('span', 'like this', { 'class': 'fallback' }),
						') lack Rater data. Because of this, default data is used, which sometimes does not reflect actual parameters recognised by the template. For example, some project banners do not use the importance or request for image/infobox fields, or use atypical names for them. Please use the preview to check if the parameters are recognised, the source editor to make corrections or follow the [ed] link to fill in template data. ', link('Flush your browser cache', mw.util.getUrl('WP:BYPASS')), ' to make sure Rater uses fresh template data.'
					])
				])
			], { 'class': 'notes' }),
			el('form', [
				uiAddTemplName = el('input', null, {
					'type': 'text',
					'size': '30',
					'placeholder': 'keyword, project or template name',
					'class': 'name'
				}),
				el('input', null, {
					'type': 'submit',
					'value': 'add'
				})
			], { 'action': 'javascript:void(0);', 'class': 'new-template' }, {
				'submit': function (ev) {
					var name = uiAddTemplName.value;
					if (!name)
						return;
					var keywords = getKeywordsMapping();
					if (keywords[name.toLowerCase()])
						name = keywords[name.toLowerCase()];
					name = normaliseTitle(name);
					if (!wantedTemplate(name))
						name = 'Επιχείρηση ' + name;
					if (!wantedTemplate(name)) {
						if (!confirm('"' + uiAddTemplName.value + '" δεν αναφέρεται σε γνωστή Βικιεπιχείρηση (δες https://el.wikipedia.org/wiki/User:Geraki/rater/projects.js). Προσθήκη όπως και αν έχει;'))
							return;
					}
					var ptpl = new ProjectTemplate(name, null, '\n');
					pluckedData.splice(++lastTemplate, 0, ptpl);
					uiTemplates.appendChild(createUIForProjectTemplate(ptpl, true));
					tabIsFresh[1] = tabIsFresh[2] = false;
					uiAddTemplName.value = '';
					if (!dirtySummary) {
						uiSummary.value = gatherSummary();
					}
				}
			})
		]),
			
		// body: source
		tab[1] = el('div', [
			uiSource = el('textarea', null, {
				'rows': 15,
				'cols': 70
			}, {
				'keypress': function () {
					tabIsFresh[0] = tabIsFresh[2] = false;
					if (!dirtySummary) {
						dirtySummary = true;
						uiSummary.classList.add('dirty');
						uiSummary.value = ' ';
					}
				},
				'change': function () {
					tabIsFresh[0] = tabIsFresh[2] = false;
					if (!dirtySummary) {
						dirtySummary = true;
						uiSummary.classList.add('dirty');
						uiSummary.value = ' ';
					}
				}
			})
		]),

		// body: preview
		tab[2] = el('div', [
			uiPreview = el('div')
		]),

		el('br', null, { 'class': 'before-footer' }),

		// footer
		el('div', [
			el('div', [
				el('label', 'Σύνοψη: '),
				uiSummary = el('input', null, {
					'type': 'text',
					'size': '60'
				}, { 'keypress': function (ev) {
					if (!dirtySummary) {
						dirtySummary = true;
						this.classList.add('dirty');
					}
				}})
			]),
			el('span', [uiStatus = document.createTextNode('')], { 'class': 'status-line' }),
			el('input', null, {
				'type': 'button',
				'value': 'Αποθήκευση',
				'class': 'save-button'
			}, { 'click': function (ev) {
				var summary = uiSummary.value;
				var markup = (curTab === 0) ? serialise() : uiSource.value;
				self.setStatus('Submitting…');
				self.save(markup, summary, {
					success: function () {
						self.setStatus('Updated.');
						setTimeout(function () {
							self.show(false);
						}, 1500);
					},
					error: function () {
						console.error(arguments);
						self.setStatus('Error.');
					}
				});
			} }),
			el('br')
		], { 'class': 'bottom' }),
			
		// nothing
		null
	], { 'class': 'kephir-rater' });
	
	$(uiBox).draggable().resizable(); // XXX: jQuery sucks
	tab[0].style.display = tab[1].style.display = tab[2].style.display = 'none';

	function createUIForProjectTemplate(tp, expanded) {
		var paramWidget = {};
		
		function normaliseBool(value) {
			if (typeof value === 'boolean')
				return value;
			if ((value === '1') || (value === 'Y') || (value === 'y') || (value.toLowerCase() === 'yes'))
				return true;
			if ((value === '0') || (value === 'N') || (value === 'n') || (value.toLowerCase() === 'no'))
				return false;
			throw new Error("cannot normalise boolean value");
		}
		
		function updateValue(param, value) {
			tabIsFresh[1] = tabIsFresh[2] = false;
			tp.setParam(param, value);
			if (!dirtySummary) {
				uiSummary.value = gatherSummary();
			}
		}

		var grph = { };
		function createWidget(name, value, data) {
			var widget;
			
			var dataTypes = {
				'flag-temp': function () {
					widget = el('input', null, { 'type': 'checkbox' });
					widget.checked = normaliseBool(value);
					widget.addEventListener('change', function () {
						updateValue(name, this.checked ? 'yes' : null);
					}, false);
				},
				'flag-perm': function () {
					widget = el('input', null, { 'type': 'checkbox' });
					widget.checked = normaliseBool(value);
					widget.addEventListener('change', function () {
						updateValue(name, this.checked ? 'yes' : 'no');
					}, false);
				},
				'flag-inv': function () {
					widget = el('input', null, { 'type': 'checkbox' });
					widget.checked = !normaliseBool(value);
					widget.addEventListener('change', function () {
						updateValue(name, this.checked ? 'no' : 'yes');
					}, false);
				},
				'string': function () {
					widget = el('input', null, { 'type': 'text' });
					widget.value = value;
					widget.addEventListener('change', function () {
						updateValue(name, this.value);
					}, false);
					widget.addEventListener('keypress', function () {
						updateValue(name, this.value);
					}, false);
				},
				'class-std': {
					'list': ['', 'Προς επέκταση', 'Έναρξη', 'Γ', 'Β', 'ΚΛ', 'Α', 'ΑΑ', 'Κατηγορία', 'Διάφορα', 'NA'],
					'normalise': 'mlc'
				},
				'class-ext': {
					'list': ['', 'Προς επέκταση', 'Έναρξη', 'Γ', 'Β', 'ΚΛ', 'Α', 'ΑΑ', 'Κατηγορία', 'Διάφορα', 'NA', 'Αρχείο', 'Πύλη', 'Πρότυπο'],
					'normalise': 'mlc'
				},
				'importance-std': {
					'list': ['', 'Ύψιστη', 'Υψηλή', 'Μεσαία', 'Χαμηλή', 'NA', 'Άγνωστη'],
					'normalise': 'mlc'
				}
			};
			var uiName = el('span', [data.desc]);
			var uiHelp = null;
			
			if (typeof data.values === 'string') {
				try {
					if (typeof dataTypes[data.values] === 'function')
						dataTypes[data.values]();
					else if (dataTypes[data.values])
						data.values = dataTypes[data.values];
					else
						dataTypes.string();
				} catch (e) {
					dataTypes.string();
				}
			}

			if (data.values.list) {
				widget = el('select');
				
				var vlist;
				if (data.values.list instanceof Array) {
					vlist = data.values.list;
					for (var i = 0; i < vlist.length; ++i) {
						widget.appendChild(el('option', [vlist[i]], { 'value': vlist[i] }));
					}
				} else {
					vlist = Object.keys(data.values.list);
					for (var key in data.values.list) {
						widget.appendChild(el('option', [data.values.list[key]], { 'value': key }));
					}
				}
				
				if (data.values.normalise) {
					value = value || '';
					switch (data.values.normalise) {
					case 'lc':
						value = value.toLowerCase();
						break;
					case 'mlc':
						for (var i = 0; i < vlist.length; ++i) {
							if (vlist[i].toLowerCase() === value.toLowerCase()) {
								value = vlist[i];
								break;
							}
						}
						break;
					}
				}

				if (data.values.aliases) {
					if (value in data.values.aliases) {
						value = data.values.aliases[value];
					}
				}

				if (vlist.indexOf(value) === -1) {
					dataTypes.string();
					// TODO: datalist
				} else {
					widget.value = value;
					widget.addEventListener('change', function () {
						updateValue(name, this.value);
					}, false);
				}
			}

			// TODO: datalist

			if (data.obsolete) {
				uiName.classList.add('obsolete');
				uiName.title = 'Αυτή η παράμετρος είναι παρωχημένη.';
			}
			
			if (data.helplink) {
				uiHelp = el('span', ['[', link('?', mw.util.getUrl(data.helplink)), ']']);
			}

			paramWidget[name] = widget;
			return el('li', [uiName, uiHelp && ' ', uiHelp, ': ', widget]);
		}
		
		function createPlaceholder(name, data) {
			var pholder = el('li', [paramWidget[name] = link(data.desc, function (ev) {
				tabIsFresh[1] = tabIsFresh[2] = false;
				tp.setParam(name, data.defvalue);
				if (grph[data.group])
					grph[data.group].classList.remove('absent');
				var widget = createWidget(name, data.defvalue, data);
				pholder.parentNode.insertBefore(widget, pholder);
				pholder.parentNode.removeChild(pholder);
				if (!dirtySummary) {
					uiSummary.value = gatherSummary();
				}
			})], { 'class': 'absent' });
			return pholder;
		}
		
		function createWidgetTF(tfname, part, prio, partpname, priopname, prioscale) {
			var uiCheck = null, uiCombo = null;

			if (partpname !== null) {
				uiCheck = el('input', null, { 'type': 'checkbox' });
				try {
					uiCheck.checked = normaliseBool(part);
				} catch (e) {
					uiCheck.checked = true;
				}
				uiCheck.addEventListener('change', function (w) {
					updateValue(partpname, this.checked ? 'yes' : null);
				}, false);
				paramWidget[partpname] = uiCheck;
			}

			if (priopname !== null) {
				var items = prioscale || ["", "Ύψιστη", "Υψηλή", "Μεσαία", "Χαμηλή", "Άγνωστη"]; // XXX
				uiCombo = el('select');
				for (var i = 0; i < items.length; ++i) {
					uiCombo.appendChild(el('option', [items[i]], { 'value': items[i] }));
				}
				uiCombo.value = prio;
				uiCombo.addEventListener('change', function () {
					updateValue(priopname, this.value);
				}, false);
				paramWidget[priopname] = uiCombo;
			}

			return el('li', [uiCheck, tfname, uiCombo && ': ', uiCombo]);
		}

		function createPlaceholderTF(tfname, partpname, priopname) {
			var pholder = el('li', [ paramWidget[priopname] = paramWidget[partpname] = link(tfname, function (ev) {
				if (partpname) tp.setParam(partpname, 'y');
				if (priopname) tp.setParam(priopname, '');
				var widget = createWidgetTF(tfname, true, '', partpname, priopname);
				grph.task.classList.remove('absent');
				pholder.parentNode.insertBefore(widget, pholder);
				pholder.parentNode.removeChild(pholder);
				if (!dirtySummary) {
					uiSummary.value = gatherSummary();
				}
			})], { 'class': 'absent' });
			return pholder;
		}

		var uiParams, uiGroups, uiDel;
		var ui = el('li', [
			uiDel = link('(delete)', function () {
				if (tp.drop())
					ui.classList.add('dropped');
				else
					ui.classList.remove('dropped');
				tabIsFresh[1] = tabIsFresh[2] = false;
				if (!dirtySummary)
					uiSummary.value = gatherSummary();
			}, { 'class': 'delete-template' }),
			el('b', [link(tp.getProjectName(), mw.util.getUrl('Template:' + tp.getTemplateName()))]),
			el('small', [' [', link('ed', '/wiki/Πρότυπο:' + tp.getTemplateName() + '/rater-data.js?action=edit&editintro=User:Geraki/rater/data-editnotice&preload=User:Geraki/rater/data-preload'), ']'], { 'class': 'absent', 'title': 'Edit data' }), ': ',
			uiParams = el('ul', [], { 'class': 'params' }),
			uiGroups = el('dl', [], { 'class': 'p-groups' })
		], { 'class': 'template-entry' });
		var grpNames = {
			'vis' : 'Εμφάνιση',
			'req' : 'Αιτήματα',
			'task': 'Ομάδες εργασίας',
			'misc': 'Διάφορα',
			'unk' : 'Μη αναγνωρισμένα'
		};
		var grps = {
			'main': uiParams
		};
		var grpl = {
			'main': null
		};
		
		function addGroup(tag) {
			uiGroups.appendChild(grph[tag] = el('dt', grpNames[tag] || tag, { 'class': 'absent' }));
			uiGroups.appendChild(el('dd', [grps[tag] = el('ul', null, { 'class': 'params' })]));
			return grps[tag];
		}

		if (!expanded) {
			ui.classList.add('hide-absent');
		}

		if (tp.isFallback()) {
			ui.classList.add('fallback');
		}
		
		tp.forEachParam(function (name, value, data) {
			if (data.group === 'task')
				return;
			if (!grps[data.group])
				addGroup(data.group);
			if ((value === null) && !data.mandatory)
				if (!data.obsolete)
					grps[data.group].appendChild(createPlaceholder(name, data));
				else;
			else {
				grps[data.group].appendChild(createWidget(name, value, data));
				if (grph[data.group])
					grph[data.group].classList.remove('absent');
			}
		});
		
		tp.forEachTaskForce(function (tfname, part, prio, partpname, priopname) {
			var item;
			if (!grps.task) addGroup('task');
			if (partpname && (part === null))
				item = createPlaceholderTF(tfname, partpname, priopname);
			else {
				item = createWidgetTF(tfname, part, prio, partpname, priopname);
				grph.task.classList.remove('absent');
			}
			grps.task.appendChild(item);
		});
		
		var uiNewCustParmName;
		ui.appendChild(el('div', [
			el('form', [
				uiNewCustParmName = el('input', null, { 'type': 'text', 'placeholder': 'new custom parameter', 'size': 20 }),
				el('input', null, { 'type': 'submit', 'value': 'add' })
			], { 'action': 'javascript:void(0);', 'class': 'new-custom-param' }, {
				'submit': function (ev) {
					var nname = uiNewCustParmName.value;
					tabIsFresh[1] = tabIsFresh[2] = false;
					if (paramWidget[nname]) {
						if (paramWidget[nname].tagName === 'A') { // XXX - placeholder
							paramWidget[nname].click();
						} else {
							// reactivate if deleted (deleting not implemented yet)
							paramWidget[nname].focus();
						}
					} else {
						var pd = tp.getParamData(nname);
						tp.setParam(nname, '');
						(grps[pd.group] || addGroup(pd.group)).appendChild(createWidget(nname, '', pd));
						if (!dirtySummary) {
							uiSummary.value = gatherSummary();
						}
					}
					uiNewCustParmName.value = '';
				}
			})
		], { 'class': 'absent' }));

		var hidelink, hltext;
		uiParams.appendChild(hidelink = link([hltext = document.createTextNode('[+]')], function () {
			if (!ui.classList.contains('hide-absent')) {	
				ui.classList.add('hide-absent');
				hltext.data = '[+]';
			} else {
				ui.classList.remove('hide-absent');
				hltext.data = '[–]';
			}
		}));

		return ui;
	}
	
	this.clear = function () {
		dirtySummary = false;
		uiSummary.value = '';
		uiSummary.classList.remove('dirty');
	};

	this.extract = function (markup) {
		var m;

		pluckedData = [];
		lastTemplate = -1;
		contig = true;

		while (uiTemplates.hasChildNodes())
			uiTemplates.removeChild(uiTemplates.firstChild);
		
		uiSource.value = markup;
		tabIsFresh[1] = true;

		try {
			// TODO: Parsoid?
			while (markup !== '') {
				if (!(m = /^([^]*?)\{\{\s*((?:\}[^\}\|]|[^\}\|])+?)\s*(?=\||}})/.exec(markup))) {
					pluckedData[pluckedData.length] = markup;
					break;
				}
				var name = normaliseTitle(m[2]);
				if (!wantedTemplate(name)) {
					pluckedData[pluckedData.length] = markup.substr(0, m[0].length);
					markup = markup.substr(m[0].length);
					continue;
				}
				pluckedData[pluckedData.length] = m[1];
				var params = {};
				var postws = '';
				markup = markup.substr(m[0].length);
				var ppid = 1;

				for (;;) {
					if (m = /^\s*}}(\s*)/.exec(markup)) {
						postws = m[1];
						markup = markup.substr(m[0].length);
						break;
					} else if (m = /^\s*\|\s*([^=\|]+?)\s*=\s*(.*?)\s*(?=\||}})/.exec(markup)) {
						markup = markup.substr(m[0].length);
						params[m[1]] = m[2];
					} else if (m = /^\s*\|\s*(.*?)\s*(?=\||}})/.exec(markup)) {
						markup = markup.substr(m[0].length);
						params[ppid++] = m[1];
					} else {
						throw new Error('Broken template invocation for "' + name + '"');
					}
				}
				
				if (lastTemplate !== (pluckedData.length - 1))
					contig = false;

				var ptpl = new ProjectTemplate(name, params, postws);
				pluckedData[lastTemplate = pluckedData.length] = ptpl;
				uiTemplates.appendChild(createUIForProjectTemplate(ptpl));
			}

			tabIsFresh[0] = true;
			setTab(0);
		} catch (e) {
			this.setStatus('Error while parsing: ' + e.message + '. Fix the source code and try again.');
			setTab(1);
		}
	};
		
	this.save = function (markup, summary, handlers) {
		alert('@!#?@!\n' + summary + '\n' + markup);
	};

	this.show = function (value) {
		uiBox.style.display = value ? '' : 'none';
		if (value) {
			if (curTab === 0)
				uiAddTemplName.focus();
			else if (curTab === 1)
				uiSource.focus();
		}
	};
	
	this.install = function (where) {
		function uniqid() {
			var s = '', cs = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.-';
			for (var i = 0; i < 24; ++i) {
				s += cs.charAt(Math.floor(Math.random() * cs.length));
			}
			return s;
		}
		where.appendChild(uiBox);
		where.appendChild(uiProjList);
		uiProjList.id = 'kephir-rater-' + uniqid();
		uiAddTemplName.setAttribute('list', uiProjList.id);
	};
	
	this.setStatus = function (message, level) {
		uiStatus.data = message;
	};
	
	return this;
}

/*
=== Glue ===
 */
var api = new mw.Api();

var ui = new UserInterface();
ui.show(false);
ui.install(document.body);

var talkpage = wgFormattedNamespaces[wgNamespaceNumber - (wgNamespaceNumber % 2) + 1] + ':' + wgTitle;

var link = mw.util.addPortletLink(mw.config.get('skin') === 'vector' ? 'p-views' : 'p-cactions',
	'javascript:void(0);', 'Αποτίμηση', 'p-kephir-rater', 'Αποτίμηση του λήμματος', '5', '#ca-view'
);
link.addEventListener('click', function (ev) {
	ev.preventDefault();
	api.get({
		action: 'query',
		prop: 'info|revisions',
		rvprop: 'timestamp|content',
		rvsection: 0,
		rvlimit: 1,
		rvdir: 'older',
		intoken: 'edit',
		titles: talkpage
	}, {
		success: function (result) {
			var tpgpid = Object.keys(result.query.pages)[0];
			var tpg = result.query.pages[tpgpid];
			var tpgstart = tpg.starttimestamp;
			var tpgtoken = tpg.edittoken;
			var tpgbase = tpg.revisions ? tpg.revisions[0].timestamp : void(0);
			var tpgrev = tpg.lastrevid;
			ui.save = function (markup, summary, handlers) {
				api.post({
					action: 'edit',
					section: 0,
					title: talkpage,
					basetimestamp: tpgbase,
					starttimestamp: tpgstart,
					token: tpgtoken,
					notminor: true,
					summary: summary,
					watchlist: 'nochange',
					text: markup
				}, handlers);
			};
			ui.clear();
			ui.extract(tpg.revisions ? tpg.revisions[0]['*'] : '');
			ui.show(true);
		},
		error: function () {
			console.error(arguments);
			alert('Error. See console.');
		}
	});
}, false);

if (/^Category:(Unassessed|Unknown-importance)_.*?_articles$/.test(wgPageName)) {
	var links = document.getElementById('mw-pages').getElementsByTagName('a');
	for (var i = 0; i < links.length; ++i)
		links[i].href = links[i].href.replace(/\/wiki\/Συζήτηση:/, '/wiki/');
}

if ((wgNamespaceNumber === wgNamespaceIds.template) && /\/rater-data\.js$/.test(wgPageName)) {
	mw.config.set('wgCodeEditorCurrentLanguage', 'json');
	mw.loader.load('ext.codeEditor');
	if (Object.defineProperty)
		Object.defineProperty(window, 'syntaxHighlighterConfig', {
			'set': function () { }
		});
	else if (window.__defineSetter__)
		window.__defineSetter__('syntaxHighlighterConfig', function () { });
}

});