ميډياويکي:Gadget-rater.js

د ويکيپېډيا، وړیا پوهنغونډ له خوا

د نور تفصيل لپاره د غځول په تنۍ کلېک وکړئيادښت: د غوره توبونو د خوندي کولو وروسته، خپل د کتنمل (بروزر) ساتل شوې حافظه تازه کړی.د نور تفصيل لپاره د غځول په تنۍ کلېک وکړئ.

  • فايرفاکس/ سفري: په دې کتنمل کې د Reload د ټکوهلو په وخت د Shift تڼۍ نيولې وساتی، او يا هم Ctrl-F5 يا Ctrl-Rتڼۍ کېښکاږۍ (په Apple Mac کمپيوټر باندې ⌘-R کېښکاږۍ)
  • گووگل کروم: په دې کتنمل کې د Ctrl-Shift-R تڼۍ کېښکاږۍ (د مک لپاره ⌘-Shift-R)
  • انټرنټ اېکسپلورر: په دې کتنمل کې د Refresh د ټکوهلو په وخت کې د Ctrl تڼۍ کېښکاږلې ونيسۍ، او يا هم د Ctrl-F5 تڼۍ کېښکاږۍ
  • اوپرا: په دې کتنمل کې د خپل براوزر ساتل شوې حافظه پدې توگه سپينولی شی Tools→Preferences
لاسوند[جوړول]
/**
 * THE ИƎTTIЯWƎЯ RATER by [[:en:User:Kephir]]
 * Rewritten to Pashto Wikipedia by [[User:Bloch khan]]
 */
/*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;

importStylesheet('mediawiki:Gadget-rater.css');

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

/*
=== Template data ===
 */
var raterData = {};
	
function getRaterData(kind) {
	if (raterData[kind] === void(null)) {
		try {
			$.ajax({
				'url': mw.config.get('wgScript') + '?action=raw&ctype=application/json&maxage=86400&title=User:FShbib/gadgets/rater/' + kind + '.js',
				'dataType': 'json',
				'async': false,
				'success': function (data) {
					raterData[kind] = data;
				},
				'error': function (xhr, message) {
					throw new Error(message);
				}
			});
		}  catch (e) {
			alert('خطأ في استرداد بيانات مقيّم "' + kind + '": ' + e.message + '. سيفشل المقيّم في العمل على الغالب.');
			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': mw.config.get('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('Error retrieving template data for "' + name + '": ' + e.message + '. Please fix it if you can. Falling back to default data, which may be inaccurate.');
			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') ? 'نعم'
				: (tpinfo.params[key].values === 'flag-perm') ? 'لا'
				: ''
			) : ''
		};
		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('جارٍ توليد الخطأ.');
					}
				});
				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 + ' — عدّل المصدر وحاول مجددًا.');
					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', ['تقييم المقالة',
			el('span', ['\u00a0[', link('أغلق', function (ev) {
				ev.preventDefault();
				self.show(false);
			}), ']'], { 'class': 'editsection' }),
			el('span', ['\u00a0[',
				link('حول الأداة', mw.util.getUrl('ويكيبيديا:المقيم'), { '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', [
				'ملاحظات',
				el('ul', [
					el('li', [
						'بعض قوالب مشاريع الويكي (المعلّمة ', el('span', 'هكذا', { 'class': 'fallback' }),
						') تفتقد بيانات المقيّم. بسبب هذا، فقد تم استخدام البيانات الافتراضية، والتي في بعض الأحيان لا تعكس الوسائط المعرّفة في القالب. كمثال، بعض أشرطة مشاريع الويكي لا تستخدم الأهمية أو طلبات إضافة الصور وصناديق المعلومات، أو تستخدم تسميات مخصصة لها. فضلًا استخدم المعاينة للتحقق من أن الوسائط معرّفة أو محرر المصدر لتصحيح الوسائط أو اتبع وصلة [عدل] لتعديل بيانات القالب. ', link('أفرغ كاش المتصفح', mw.util.getUrl('ويكيبيديا:إفراغ_الكاش')), ' للتأكد من أنّ أداة المقيّم تستخدم آخر البيانات.'
					])
				])
			], { 'class': 'notes' }),
			el('form', [
				uiAddTemplName = el('input', null, {
					'type': 'text',
					'size': '30',
					'placeholder': 'الكلمة المفتاحية، أو المشروع أو القالب',
					'class': 'name'
				}),
				el('input', null, {
					'type': 'submit',
					'value': 'أضف'
				})
			], { '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://ar.wikipedia.org/wiki/User:FShbib/gadgets/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('جارٍ الحفظ…');
				self.save(markup, summary, {
					success: function () {
						self.setStatus('تم الحفظ.');
						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 === 'نعم') || (value.toLowerCase() === 'نعم'))
				return true;
			if ((value === '0') || (value === 'لا') || (value.toLowerCase() === 'لا'))
				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 ? 'نعم' : null);
					}, false);
				},
				'flag-perm': function () {
					widget = el('input', null, { 'type': 'checkbox' });
					widget.checked = normaliseBool(value);
					widget.addEventListener('change', function () {
						updateValue(name, this.checked ? 'نعم' : 'لا');
					}, false);
				},
				'flag-inv': function () {
					widget = el('input', null, { 'type': 'checkbox' });
					widget.checked = !normaliseBool(value);
					widget.addEventListener('change', function () {
						updateValue(name, this.checked ? 'لا' : 'نعم');
					}, 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': ['', 'بذرة', 'بداية', 'ج', 'ب', 'م.ج', 'أ', 'م.مخ', 'قائمة', 'ق.مخ', 'غير مقالية'],
					'normalise': 'mlc'
				},
				'class-ext': {
					'list': ['', 'بذرة', 'بداية', 'ج', 'ب', 'م.ج', 'أ', 'م.مخ', 'قائمة', 'ق.مخ', 'غير مقالية', 'تصنيف', 'توضيح', 'ملف', 'بوابة', 'مشروع', 'قالب', 'تحويلة'],
					'normalise': 'mlc'
				},
				'importance-std': {
					'list': ['', 'فائقة', 'عالية', 'متوسطة', 'قليلة', 'ضئيلة', 'غير مطبقة', 'غير معروفة'],
					'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 ? 'نعم' : 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, 'نعم');
				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('عدل', '/wiki/Template:' + tp.getTemplateName() + '/rater-data.js?action=edit&editintro=User:FShbib/gadgets/rater/data-editnotice&preload=User:FShbib/gadgets/rater/data-preload'), ']'], { 'class': 'absent', 'title': 'عدل البيانات' }), ': ',
			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': 'وسيط جديد مخصص', 'size': 20 }),
				el('input', null, { 'type': 'submit', 'value': 'أضف' })
			], { '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('استدعاء قالب مكسور لـ "' + 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('خطأ أثناء تحليل: ' + e.message + '. أصلح كود المصدر وحاول مجددًا.');
			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 = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber') - (mw.config.get('wgNamespaceNumber') % 2) + 1] + ':' + mw.config.get('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 (/^تصنيف:مقالات_.*?_(غير_مقيمة|غير_معروفة_الأهمية)$/.test(mw.config.get('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\/%D9%86%D9%82%D8%A7%D8%B4:/, '/wiki/');
}

if ((mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').template) && /\/rater-data\.js$/.test(mw.config.get('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 () { });
}

});