מדיה ויקי:Gadget-TemplateParamWizard.js

מתוך צפונות ויקי

הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר / אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: ללחוץ על Ctrl-F5.
//Template parameters wizard
//Written by [[User:קיפודנחש]]
"use strict";
if(($.inArray(mw.config.get('wgAction'), ['edit', 'submit'])+1) && ( !$('#wpTextbox1').prop( 'readonly' ) ) )
$(function($) {
	// template parameter is an object with the following fields:
	// desc: desciption string
	// defval: default value (optional)
	// options: object with optional fields:
	//// multiline: number of lines
	//// depends: another field's name
	//// required: boolean
	//// date: use JS date widget
	//// choices: array of legal values for the field
	//// extended: this field will not show on initial screen, and will appear once the user selects "show all fields"

	var
	// templateParams is keyed by paramName.
		templateParams,
		paramsOrder,
	// which template are we working on
		template,
	// array of pairs - [paramName, inputField]
		dialogFields,
	// table rows keyed by paramName
		rowsBypName,
	// the fields, keyed by paramName
		fieldsBypName,
	// boolean, indicating the source of data is templatedata.
		tdTemplate,
	// boolean, indicating the source of the data is analyzing the template page itself.
		rawTemplate,
		isInline,
		rtl = $('body').is('.rtl'),
	// test to see if a string contains wikiCode and hence needs parsing, or cen be used as is.
		wikiCodeFinder = /[\[\]\{\}<>]/,
		globalExplanation = '',
		extendedParamCssRule,
		anyExtended = false,
		localStorageKey = 'templateParamWizard',
		emptiesKey = 'writeEmpties',
		oneLineTemplate = 'oneLineTemplate',
		allAliases = [];

	function addParam(name) {
		if ($.inArray(name, paramsOrder) == -1)
			paramsOrder.push(name);
	}

	function paramsFromSelection() {
		var selection = $("#wpTextbox1").textSelection('getSelection').replace(/^\s*\{\{|\}\}\s*$/g, ''); //scrap the first {{ and last }}
		var specials = [],
			match;
		while (true) { //extract inner links, inner templates and inner params - we don't want to split those.
			match = selection.match(/(\{\{[^\{\}\]\[]*\}\}|\[\[[^\{\}\]\[]*\]\]|\[[^\{\}\]\[]*\])/);
			if (! match || ! match.length)
				break;
			specials.push(match[0]);
			selection = selection.replace(match[0], "\0" + specials.length + "\0");
		}
		var params = selection.split(/ *\| */);
		params.shift(); // remove the template name 
		var ordered = 0;
		for (var i in params) {
			var param = params[i];
			if ( ! /=/.test(param) ) {
				param = ++ordered + '=' + param;
			}
			var paramPair = param.split("=");
			var name = $.trim(paramPair.shift());
			if ( ! isNaN( name ) ) {
				ordered = parseInt( name ); // this still won't work as advertise when template contains explicit parameter 2 before implicit 1: {{x | 2 = hey | ho }}
			}
			var val = paramPair.join('=');
			while (true) {
				match = val.match(/\0(\d+)\0/);
				if (! match || ! match.length)
					break;
				val = val.replace(match[0], specials[parseInt(match[1], 10)-1]);
			}
			if (name && paramPair.length) {
				var tp = templateParams[name] = 
					templateParams[name] || 
					~allAliases.indexOf(name) && { param:{}, options: {isAlias: 1} } || 
					{param: {}, options: { notInParamPage: 1}};
				addParam(name);
				$.extend( tp.options, { defval: val } );
				if ( /\n/.test( val ) ) $.extend( tp.options, { multiline : 5 } );
				// next line is for the case where there are "choices"' but current value is not one of them: add it as a new choice.
				if ( typeof tp.options.choices === 'string' && tp.options.choices.indexOf( val ) < 0 )
					tp.options.choices += ( ', ' + val ); 
			}
		}
	}

	function buildParamsRaw(data) {
		var
			paramExtractor = /{{3,}(.*?)[<|}]/mg,
			m;
		while (m = paramExtractor.exec(data)) {
			var paramName = $.trim(m[1]);
			templateParams[paramName] = { desc: '', options: {multiline: 5}, label: paramName, param:{} };
			addParam(paramName);
		}
	}
	
	function buildParamsTd(data) {
		var params = data.params,
			paramOrder = data.paramOrder;

		function optionsOfParam(param) {
			var options = {};
			if (param.required) options.required = true;
			if (param.suggestedvalues && param.suggestedvalues.length) options.choices = param.suggestedvalues.join(',');
			return options;
		}

		function onemore(name) {
			var param = params[name];
			if (param.deprecated) 
				return; // ignore deprecated parameters - pretend they are not in TD.
			templateParams[name] = { 
				desc: param.description || '', 
				options: optionsOfParam(param), 
				label: param.label || name, 
				param: param 
			};
			if (param.aliases) $.merge(allAliases, param.aliases); // collect alliases if there are any
			addParam(name);
		}
		
		isInline = data.format === 'inline';
		
		if (paramOrder && paramOrder.length) 
			for (var ind in paramOrder)
				onemore(paramOrder[ind]);
		else // no order - take them as they come.
			for (var paramname in params)
				onemore(paramname);

		// derive placeholders for feilds derived from wikidata
		if (data.maps && data.maps.hasOwnProperty('wikidata') && mw.config.get('wgWikibaseItemId')) {
			var wikidataFormattedValues = $('<div>');
			for (var k in data.maps['wikidata']) {
				wikidataFormattedValues.append($('<span>', {id: k, text:'{{#property:' + k + '}}'}))
			}
			$.post(
				mw.util.wikiScript('api'),
				{action: 'parse', text: wikidataFormattedValues.html(), disablelimitreport: 1, format: 'json', prop: 'text', title: mw.config.get('wgPageName')},
				function(wbd) {
					if (!wbd || !wbd.parse || !wbd.parse.text) return;
					for (var k in data.maps['wikidata']) {
						var wikidataVal = $('#' + k, wbd.parse.text['*']).text(),
							field = fieldsBypName[data.maps['wikidata'][k]];
						if (field)
							field.prop('placeholder', wikidataVal);
					}
				}
			);
		}
	}

	function buildParams(data) {
		var
			lines = data.split("\n"),
			line;

		function extractGlobalExplanation() {
			line = line.replace(/[!\|][^\|]*\|/, '');
			if (wikiCodeFinder.test(line))
				$.post(
					mw.util.wikiScript('api'),
					{action: 'parse', text: line, disablepp: 1, format: 'json'},
					function(data) {
						var html = data.parse.text['*'];
						globalExplanation = html;
						$('#tpw_globalExplanation').html(html).find('a').attr({target: '_blank'});
					}
				);
			else
				globalExplanation = line;
		}

		while (lines && lines.length) {
			line = lines.shift();
				if (!(/^\|-/.test(line))) // look for |- this is wikitext for table row.
					continue;
			line = lines.shift();
			if (line.indexOf('globalExplanation') + 1) {
				extractGlobalExplanation();
				continue;
			}
			if (! line || ! (/^\|/.test(line))) //wikitext for column
				continue;
			line = line.substr(1); // get rid of the leading |
			var fields = line.split('||');
			if (fields.length < 2)
				continue;
			var name = $.trim(fields[0]);
			if (! name)
				continue;
			var desc = $.trim(fields[1]);
			var pAttribs = {desc: desc};
			if (fields.length > 2)
				pAttribs.options = analyzeOptions($.trim(fields[2]));

			templateParams[name] = pAttribs;
			addParam(name);

		}
	}

	function analyzeOptions(str) {
		var res = {},
			avail = ['multiline', 'required', 'depends', 'defval', 'choices', 'date', 'extended'], // maybe we'll have more in the future
			tavail = $.map(avail, i18n),
			options = str.split(/\s*;\s*/);
		for (var i in options) {
			var option = options[i].split(/\s*=\s*/);
			var ind = $.inArray(option[0], tavail);
			if (ind + 1)
				res[avail[ind]] = option.length > 1 ? option[1] : true;
		}
		anyExtended = anyExtended || res.extended;
		return res;
	}

	function createWikiCode() {
		var par = [template],
			delim = $('#' + oneLineTemplate).prop('checked') ? '' : '\n',
			paramValueDelim =  $('#' + oneLineTemplate).prop('checked') ? '=' : ' = ',
			createEmpties = $('#createEmpties').prop('checked'),
			mustNumberNameless,
			valuedOrdered;
		
		for (var i = dialogFields.length - 1; i >= 0; i--) {
			var field = dialogFields[i],
				val = $.trim(field[1].val());
			
			if (isNaN(field[0])) continue; // look only at order-based fields
			
			mustNumberNameless |= 
				/=/.test(val) // order-based value containing "="
				|| valuedOrdered && !val; // empty ordered w lower index than a non-empty one.
			if (val) valuedOrdered = true;
		}
		 
		for (var i in dialogFields) {
			var
				field = dialogFields[i],
				name = $.trim(field[0]),
				f = field[1],
				opts = f.data('options'),
				param = templateParams[name],
				hidden = f.parents('.tpw_hidden').length,
				val = f.val().replace( /\s+$/, '' ); // leave leading newlines, for lists etc., but remove trailing.
			if (param && param.param && param.param.type === 'url') 
				val = val.replace(/\|/g, '{{!}}');
			if (f.attr('type') == 'checkbox' && ! f.prop('checked'))
				val = "";
			if ( ( !createEmpties || opts.notInParamPage )  && $.trim( val ) === "" ) 
				continue;//skip parameters with no value
				
			var next = mustNumberNameless || isNaN(name)
				? name + paramValueDelim + $.trim( val )
				: $.trim( val );
			par.push(next);
		}
		return "{{" + par.join(delim + ($('#' + oneLineTemplate).prop('checked')? "|" : "| ")) + delim + "}}";
	}

	function showPreview() {
		var temp = createWikiCode();
		$.post(mw.util.wikiScript('api'),
			{action: 'parse',
				title: mw.config.get('wgPageName'),
				prop: 'text',
				text: temp,
				format: 'json'
			},
			function(data) {
				if (data && data.parse && data.parse.text) {
					var buttons = [{text: i18n('close'), click: function() {$(this).dialog('close');}}],
						div = $('<div>').html(data.parse.text['*']);
					$('a', div).attr('target', '_blank'); // we don't want people to click on links in preview - they'll lose their work.
					$('<div>')
						.dialog(
							{title: i18n('preview'),
							modal: true,
							position: [60, 60],
							buttons: buttons})
						.append(div);
					circumventRtlBug();
				}
			});
	}

	function circumventRtlBug() {
		if (rtl)
			$('.ui-dialog-buttonpane button').css({float: 'right'}); // jQuery has problems with rtl dialogs + ie is braindamaged.
	}

	function i18n(key, param) {
		switch (mw.config.get('wgUserLanguage')) {
			case 'ar':
				switch (key) {
					case 'explain': return rawTemplate
						? 'قالب "' + template + '" ليس له صفحة وسائط فرعية، لذلك فما من وصف لمعطياته.'
						: 'الوسائط الضرورية محددة بالأحمر والبقية اختيارية.';
					case 'wizard dialog title': return 'وسائط ' + '<a href="' + mw.util.getUrl('قالب:' + template) + '" target="_blank">' + 'قالب:' + template + '</a>';
					case 'ok': return 'موافقة';
					case 'cancel': return 'إلغاء';
					case 'params subpage': return 'وسائط';
					case 'preview': return 'معاينة';
					case 'options select': return 'اختر معطى';
					case 'multiline': return 'عدد صفوف';
					case 'close': return 'أغلق';
					case 'required': return 'ضروري';
					case 'depends': return 'يلزمه';
					case 'defval': return 'غيابي';
					case 'choices': return 'خيارات';
					case 'date': return 'تاريخ';
					case 'extended': return 'مفصل';
					case 'button hint': return 'معالج وسائط القالب';
					case 'template selector title': return 'اكتب اسم القالب:';
					case 'notInParamPage': return 'وسيط "' + param + '" ليس من وسائط القالب';
					case 'editParamPage': return 'عدل صفحة الوسائط';
					case 'unknown error': return 'وقع خطأ.\n' + param;
					case 'please select template': return 'اسم القالب';
					case 'oneliner': return 'اجعله في صف واحد';
					case 'createempties': return 'إضافة الوسائط فارغة';
					case 'dateFormat': return 'd MM yy';
					case 'extended labels': return 'عرض كل الوسائط';
					default: return key;
				}
				break;
			case 'he':
				switch (key) {
					case 'explain': return hebExplain();
					case 'wizard dialog title': return 'מילוי הפרמטרים עבור ' + '<a href="' + mw.util.getUrl('תבנית:' + template) + '" target="_blank">' + 'תבנית:' + template + '</a>';
					case 'ok': return 'אישור';
					case 'cancel': return 'ביטול';
					case 'preview': return 'תצוגה מקדימה';
					case 'options select': return 'בחרו ערך מהרשימה';
					case 'multiline': return 'מספר שורות';
					case 'close': return 'סגור';
					case 'required': return 'שדה חובה';
					case 'depends': return 'תלוי';
					case 'defval': return 'ברירת מחדל';
					case 'choices': return 'אפשרויות';
					case 'date': return 'תאריך';
					case 'extended': return 'משני';
					case 'button hint': return 'אשף מילוי תבניות';
					case 'template selector title': return 'אנא הזינו את שם התבנית:';
					case 'notInParamPage': return 'השדה "' + param + '" לא מופיע ברשימת הפרמטרים של התבנית';
					case 'editParamPage': return 'לעריכת דף הפרמטרים';
					case 'unknown error': return 'טעות בהפעלת האשף.\n' + param;
					case 'please select template': return 'שם התבנית';
					case 'oneliner': return 'תבנית בשורה אחת';
					case 'createempties': return 'רשום שדות ריקים';
					case 'dateFormat': return 'd בMM yy';
					case 'extended labels': return 'הראה את כל הפרמטרים';
					case 'pve-required-empty': return 'התבנית דורשת שפרמטר זה יקבל ערך';
					case 'pve-deprecated': return 'שימוש בפרמטר מיושן';
					case 'pve-incompatible': return 'שדה זה מצפה לערך מספרי';
					case 'pve-no-such-name': return 'שדה זה לא קיים בתבנית';
					case 'explain-pve': return 'שדות עם שגיאה מסומנים ברקע ורוד';
					case 'pve-approve-close': return 'יש בתבנית שגיאות. אנא אשרו יציאה מהאשף';
				}	
				break;
			case 'ur':
				switch (key) {
					case 'explain': return 'جو خانے لازمی ہیں ان کے گرد سرخ رنگ کی لکیر کھینچ دی گئی ہے، بقیہ خانے اختیاری ہوں گے۔';
					case 'wizard dialog title': return 'سانچہ: "' + template + '" میں مطلوبہ معلومات درج کریں۔';
					case 'ok': return 'ٹھیک';
					case 'cancel': return 'منسوخ کریں';
					case 'params subpage': return 'پیرامیٹر';
					case 'preview': return 'نمائش';
					case 'options select': return 'کسی ایک کو منتخب کریں:';
					case 'multiline': return 'سطروں کی تعداد';
					case 'close': return 'بند کریں';
					case 'required': return 'لازمی';
					case 'depends': return 'اس پر موقوف ہے';
					case 'defval': return 'طے شدہ';
					case 'choices': return 'اختیارات';
					case 'date': return 'تاریخ';
					case 'extended': return 'مفصل';
					case 'button hint': return 'ساحر محددات سانچہ';
					case 'template selector title': return 'براہ کرم سانچہ کا نام درج کریں';
					case 'notInParamPage': return 'پیرامیٹر کی فہرست میں "' + param + '" ظاہر نہیں ہو رہا ہے';
					case 'editParamPage': return 'پیرامیٹر کے صفحہ میں ترمیم کریں';
					case 'unknown error': return 'نقص پیش آیا: \n' + param;
					case 'please select template': return 'براہ کرم سانچہ کا نام درج کریں';
					case 'oneliner': return 'یک سطری';
					case 'createempties': return 'صفحہ میں خالی پیرامیٹر درج کریں';
					case 'dateFormat': return 'd MM yy';
					case 'extended labels': return 'تمام پیرامیٹر دکھائیں';
				}
				break;
			case 'ru':
				switch (key) {
				case 'explain': return 'поля с красной рамкой обязательны, остальные - по желанию';
					case 'wizard dialog title': return 'Настройте параметры для шаблона: ' + template;
					case 'ok': return 'OK';
					case 'cancel': return 'Отмена';
					case 'params subpage': return 'Параметры';
					case 'preview': return 'Просмотр';
					case 'options select': return 'Выбрать:';
					case 'multiline': return 'Многострочный вид';
					case 'close': return 'Закрыть';
					case 'required': return 'Необходимое';
					case 'depends': return 'Зависит от';
					case 'defval': return 'По умолчанию';
					case 'choices': return 'Выбор';
					case 'date': return 'Дата';
					case 'extended': return 'Расширенный';
					case 'button hint': return 'Мастер параметров шаблона';
					case 'able templates category name': throw('Необходимо определить название категории для шаблонов с поддержкой мастера');
					case 'template selector title': return 'Пожалуйста, введите имя шаблона';
					case 'notInParamPage': return 'поле "' + param + '" не отображается в списке параметров шаблона';
					case 'editParamPage': return 'Править страницу параметров';
					case 'unknown error': return 'Произошла ошибка: \n' + param;
					case 'please select template': return 'Пожалуйста, введите имя шаблона';
					case 'oneliner': return 'Однострочный вид';
					case 'dateFormat': return 'ММ дд, гг';
					case 'extended labels': return 'Показать все параметры';
				}
			default:
				switch (key) {
					case 'explain': return 'fields with red border are required, the rest are optional';
					case 'wizard dialog title': return 'Set up parameters for template: ' + template;
					case 'ok': return 'OK';
					case 'cancel': return 'Cancel';
					case 'params subpage': return 'Parameters';
					case 'preview': return 'Preview';
					case 'options select': return 'Select one:';
					case 'multiline': return 'Multiline';
					case 'close': return 'Close';
					case 'required': return 'Required';
					case 'depends': return 'Depends on';
					case 'defval': return 'Default';
					case 'choices': return 'Choices';
					case 'date': return 'Date';
					case 'extended': return 'Extended';
					case 'button hint': return 'Template parameters wizard';
					case 'template selector title': return 'Please enter the template name';
					case 'notInParamPage': return 'field "' + param + '" does not appear in the template\'s parameters list';
					case 'editParamPage': return 'Edit paramters page';
					case 'unknown error': return 'Error occured: \n' + param;
					case 'please select template': return 'Please enter template name';
					case 'oneliner': return 'Single-line template';
					case 'createempties': return 'Write empty parameters to page';
					case 'dateFormat': return 'MM d, yy';
					case 'extended labels': return 'Show all parameters';
					case 'pve-required-empty': return 'The template requires a value for this filedך';
					case 'pve-deprecated': return 'deprecated parameter';
					case 'pve-incompatible': return 'expaects numteric value';
					case 'pve-no-such-name': return 'undercgonzed parameter';
					case 'explain-pve': return 'fields with errors are marked with pink background';
					case 'pve-approve-close': return 'Template contains errors. Please confim exit';
				}
		}
		return key;
	}

	function hebExplain() {
		var explanation;
		if (rawTemplate) return 'לתבנית "' + template + '" אין דף פרמטרים, ולכן לשדות אין תיאור.';
		if (anyRequiredParam()) return 'שדות חובה מסומנים במסגרת אדומה';
		return '';
	}
	
	function anyRequiredParam() {
		for (name in templateParams) {
			var param = templateParams[name];
			if (param.options.required) return true;
		}
		return false;
	}
	
	function templatePage() {
		var t = $.trim(template)
		return t.match(':') ? t : mw.config.get('wgFormattedNamespaces')[10] + ':' + t;
	}

	function updateRawPreview(){
		var canOK = 'enable';
		for (var i in dialogFields) {
			var df = dialogFields[i][1];
			var opts = df.data('options');
			if (opts && opts.required && $.trim(df.val()).length == 0)
				canOK = 'disable';
			if (opts && opts.depends) {
				var dep = fieldsBypName[opts.depends];
				var depEmpty = (dep && dep.val() && $.trim(dep.val())) ? false : true;
				var row = rowsBypName[df.data('paramName')];
				if (row)
					row.toggleClass('tpw_hidden', depEmpty);
			}
		}
		$(".ui-dialog-buttonpane button:contains('" + i18n('ok') + "')").button(canOK);
		$('#tpw_preview').text(createWikiCode());
		localStorage.setItem(localStorageKey + '.' + emptiesKey, $('#createEmpties').prop('checked'));
		validate();
	}
	
	function validate() {
		
		function validateField(param, input) {
			function markError(msg) { 
				input
					.addClass('tpw-paramvalidation')
					.attr('title', i18n(msg)); 
				return false; 
			}
			
			var hasVal = !! input.val();
			if (param.options.notInParamPage && hasVal) return markError('pve-no-such-name');
			if (param.param.required && ! hasVal) return markError('pve-required-empty');
			if (param.param.deprecated && hasVal) return markError('pve-deprecated');
			if (param.param.type === 'number' && isNaN( Number( input.val().replace(/,/g, '') ) ) ) return markError('pve-incompatible');
			return true;
		}
		
		var aOK = true;
		for (var i in dialogFields) {
			var
				field = dialogFields[i],
				name = $.trim(field[0]),
				input = field[1].removeClass('tpw-paramvalidation'),
				param = templateParams[name];
				
			aOK = validateField(param, input) && aOK;
		}
		
		$('#tpw-explain').html(i18n(aOK ? 'explain' : 'explain-pve') );
		
		return aOK;
	}

	function createInputField(paramName) {
		var params = templateParams[paramName],
			options = params.options || {},
			f,
			checkbox = false;

		if (options.choices) {
			var choices = options.choices.split(/\s*,\s*/);
			if (choices.length > 1) {
				f = $('<select>').append($('<option>', {text: i18n('options select'), value: '' }));
				for (var i in choices) {
					var choice = choices[i].trim(); // first and last may carry spaces
					var option = $('<option>', {text: choice, value: choice});
					f.append(option);
				}
			}
			else {
				checkbox = true;
				var choice = choices[0].trim();
				f = $('<input>', {type: 'checkbox', value: choices[0], text: choices[0].trim()})
					.css({float: rtl ? 'right' : 'left'});
				f.prop('checked', options.defval && options.defval.trim() == choices[0]);
			}
		}
		else if (options.multiline) {
			var rows = options.multiline;
			f = $('<textarea>', {rows: 1})
				.focus(function(){this.rows = 5;})
				.blur(function(){this.rows = 1});
		}
		else
			f = $('<input>', {type: 'text'});

		if (!checkbox && f.autoCompleteWikiText) // teach the controls to autocomplete.
			f.autoCompleteWikiText({positionMy: rtl ? "left top" : "right top"});

		f.css({width: checkbox ? '1em' : '28em'})
			.data({paramName: paramName, options: options})
			.on('paste cut drop input change', updateRawPreview);

		if (options.defval && ! checkbox)
			f.val(options.defval.trim());

		if (options.required)
			f.addClass('tpw-required'); 

		if (options.date)
			f.datepicker({dateFormat: typeof options.date  == "string" ? options.date : i18n('dateFormat')});
			
		return f;
	}

	var
		timer = null,
		lastVisited = $('<a>');

	function enterTipsy() {
		clearTimeout(timer);
		$(this).attr('inside', 1);
	}

	function leaveTipsy() {
		var $this = $(this);
		if ($this.attr('master') || $this.attr('inside')) {
			$this.attr('inside', '');
			timer = setTimeout(function(){lastVisited.tipsy('hide');}, 500);
		}
	}

	function tipsyContent() {
		var
			paramName = $(this).data('paramname'),
			def = templateParams[paramName],
			desc = def && def.desc || '';
		if (!def) return ''; // early terminate if param name is not valid
		if (def.htmlDesc)
			return def.htmlDesc;
		if (wikiCodeFinder.test(desc)) // does it need parsing?
			$.ajax({
				url: mw.util.wikiScript('api'),
				async: false,
				type: 'post',
				data: {action: 'parse', text: desc, disablepp: 1, format: 'json'}, // parse it.
				success: function(data) {
					var div = $('<div>').html(data.parse.text['*']);
					$('a', div).attr({target: '_blank'});
					def.htmlDesc = div.html();
				}
			});
		else
			def.htmlDesc = desc;
		return def.htmlDesc;
	}

	function addRow(paramName, table) {
		var
			def = templateParams[paramName],
			inputField = createInputField(paramName),
			nameColor = def.desc 
				? 'blue' 
				: def.options.notInParamPage 
					? 'red' 
					: def.options.isAlias 
						? 'green' 
						: 'black',
			tr = $('<tr>')
				.append(
					$('<td>', {width: 120})
					.css({fontWeight: 'bold', color: nameColor})
					.data({ paramname: paramName })
					.text(templateParams[paramName].label || paramName)
					.tipsy({html: true, trigger: 'manual', title: tipsyContent})
					.mouseenter(function() {
						clearTimeout(timer);
						$('.tipsy').remove();
						lastVisited = $(this);
						lastVisited.tipsy('show');
					})
					.mouseleave(leaveTipsy)
					.attr('master', 'true')
				)
				.append($('<td>').css({width: '30em'}).append(inputField));
		dialogFields.push([paramName, inputField]);
		if (def.options.extended)
			tr.addClass('tpw_extended');
		table.append(tr);
		rowsBypName[paramName] = tr;
		fieldsBypName[paramName] = inputField;
	}

	function injectResults() {
		$("#wpTextbox1").textSelection('encapsulateSelection', {replace: true, peri: createWikiCode()});
	}

	function createExtendedCheckBox() {
		return $('<p>')
				.text(i18n('extended labels'))
				.append($('<input>', {type: 'checkbox'})
					.change(function() {
						extendedParamCssRule.disabled = $(this).prop('checked');
					})
				);
	}

	function buildDialog(data) {
		var title = $('<span>').html(i18n('wizard dialog title', template));
		$('.tpw_disposable').remove();
		
		if (rawTemplate)
			buildParamsRaw(data);
		else if (tdTemplate) {
			buildParamsTd(data);
			if (data.description) title.find('a').attr({ title: data.description });
		}
		
		paramsFromSelection();
		var	table = $('<table>');
		var dialog = $('<div>', {'class': 'tpw_disposable'})
			.dialog({height: 'auto',
					title: title.html(),
					width: 'auto',
					overflow: 'auto',
					position: [$('body').width() * 0.2, $('body').height() * 0.1],
					open: function() {$(this).css({'max-height': Math.round($('body').height() * 0.7)});}
			})
			.append($('<div>', {id: 'tpw_globalExplanation'}).html(globalExplanation))
			.append($('<p>', { id: 'tpw-explain' } ).html(i18n('explain')) )
			.append(anyExtended ? createExtendedCheckBox() : '')
			.append(table)
			.append($('<p>')
				.append(i18n('oneliner'))
				.append($('<input>', {type: 'checkbox', id: oneLineTemplate}).prop('checked', isInline).change(updateRawPreview)
				)
			)
			.append($('<p>')
				.append(i18n('createempties'))
				.append($('<input>', {type:'checkbox', id:'createEmpties'})
					.change(updateRawPreview)
					.prop('checked', localStorage.getItem(localStorageKey + '.' + emptiesKey) == "true")
				)
			)
			.append($('<pre>', {id: 'tpw_preview'}).addClass('tpw-wikicode-preview'));
				
		while (paramsOrder.length)
			addRow(paramsOrder.shift(), table);

		var buttons = {}; // we need to do it this way, because with literal object, the keys must be literal.
		buttons[i18n('ok')] = function() {
			if (! validate() && ! confirm(i18n('pve-approve-close' ) ) ) return;
			injectResults(); 
			dialog.dialog('close'); 
		};
		buttons[i18n('cancel')] = function() {dialog.dialog('close');};
		buttons[i18n('preview')] = showPreview;
		dialog.dialog('option', 'buttons', buttons);
		circumventRtlBug();
		updateRawPreview();
		$('.tipsy').hover(enterTipsy, leaveTipsy);
	}

	function init() {
		template = null;
		templateParams = {};
		paramsOrder = [];
		dialogFields = [];
		rowsBypName = {};
		fieldsBypName = {};
		mw.util.addCSS(".tpw_hidden{display:none;}");
		anyExtended = false;
		extendedParamCssRule = extendedParamCssRule || mw.util.addCSS(".tpw_extended{display:none;}");
	}

	function reportError(a,b,error) {
		var key;
		if (typeof console != 'undefined') {
			for (key in a)
				if (typeof a[key] != 'function')
					console.log(key + '=>' + a[key]);
			console.log(b);
			console.log(error);
		}
		alert(i18n('unknown error', error));
	}

	function pickTemplate(item) {
		function okButtonPressed(e, ui) {
			template = ui ? ui.item.value : selector.val();
			fireDialog();
			templateSelector.dialog("close");
		}
		var selector = $('<input>')
			.css({width: '28em'})
			.autocomplete({
				source: function(request, response) {
					$.getJSON(
						mw.util.wikiScript('api'),
						{action:'opensearch', search: request.term, namespace: 10},
						function(data){
							if(data[1])
								response($(data[1]).map(function(index,item){return item.replace(/.*:/, '');}));
						}
					);
				},
				select: okButtonPressed
			});
		var templateSelector = $('<div>').dialog({
			title: i18n('template selector title'),
			height: 'auto',
			width: 'auto',
			modal: true,
			buttons: [
				{text: i18n('ok'), click: okButtonPressed},
				{text: i18n('cancel'), click: function(){templateSelector.dialog("close")}}
			]
		}).append(selector);
		circumventRtlBug();
		selector.focus();
	}

	function fireDialog() {
		var readRaw = function() {
			rawTemplate = true;
			$.ajax({
				url: mw.util.wikiScript(),
				data: {title: templatePage(), action: 'raw'},
				dataType: 'text',
				success: buildDialog,
				error: reportError
			});			
		},
		
		readTemplateData = function() {
			$.ajax({
				url: mw.util.wikiScript('api'),
				data: {action: 'templatedata', titles: templatePage(), redirects: true, format: 'json', lang: mw.config.get('wgUserLanguage') },
				dataType: 'json',
				success: function(data) {
					var found = false;
					if (data && data.pages)
						for (var pageid in data.pages) {
							tdTemplate = true;
							found = true;
							buildDialog(data.pages[pageid]);
							break;
						}
					if (! found) 
						readRaw();
				},
				error: readRaw
			});			
		};
		
		rawTemplate = false;
		readTemplateData();
	}

	function templateContext() {
		var selection = $("#wpTextbox1").textSelection('getSelection'),
			caretPos, beforeText, afterText, templateStart, templateEnd;

		// trust the user
		if ( selection.length > 0 ) {
			return selection; 
		}

		caretPos =  $("#wpTextbox1").textSelection('getCaretPosition');
		beforeText = $("#wpTextbox1").val().substr(0, caretPos);
		afterText = $("#wpTextbox1").val().substr(caretPos);
		templateStart = beforeText.lastIndexOf('{{');
		templateEnd = afterText.indexOf('}}') + 2;

		// only under opportunistic template context assumptions
		if ( $("#wpTextbox1").val().split('{').length != $("#wpTextbox1").val().split('}').length ||
			  (beforeText.split('{{').length === beforeText.split('}}').length) ||
			  (afterText.split('{{').length === afterText.split('}}').length) ) 
			return ''; 

		// determine the start and the end of the template context
		while (beforeText.substr(templateStart).split('{{').length <= beforeText.substr(templateStart).split('}}').length)
			templateStart = beforeText.lastIndexOf('{{', templateStart - 1)
		

		while (afterText.substr(0, templateEnd).split('{{').length >= afterText.substr(0, templateEnd).split('}}').length)
			templateEnd = afterText.indexOf('}}', templateEnd) + 2;

		// extend the selection to the current template context		
		$("#wpTextbox1").focus().textSelection('setSelection', {
			start: templateStart,
			end: caretPos + templateEnd
		});
		return $("#wpTextbox1").textSelection('getSelection');
	}

	function doIt() {
		mw.loader.using(['jquery.ui','jquery.tipsy','jquery.textSelection', 'mediawiki.api'], function() {
			init();
			var match = templateContext().match(/^\{\{([^|}]*)/);
			template = match ? $.trim(match[1]) : null;
			if (template)
				fireDialog();
			else
				pickTemplate();
		});
	}
	function addToWikiEditor(){
		$('#wpTextbox1').wikiEditor('addToToolbar', {
			section: 'main',
			group: 'insert',
			tools: {
				'templateParamsWizard': {
					label: i18n('button hint'),
					type: 'button',
					icon: '//upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Template_alt_full_black_22.svg/22px-Template_alt_full_black_22.svg.png',
					action: {type: 'callback', execute: doIt}
				}
			}
		});				
	}
	if (mw.user.options.get('usebetatoolbar'))
		mw.loader.using(['ext.wikiEditor'], function() {
			if(typeof $.wikiEditor != 'undefined') {
				if ($('#wikiEditor-ui-toolbar').length === 1) addToWikiEditor();//in case it loaded after toolbar initaliztion
				else $( '#wpTextbox1' ).on( 'wikiEditor-toolbar-doneInitialSections', addToWikiEditor);
			}
			});
		else
			$('div #toolbar').append( // "old style"
				$('<img>', {src: '//upload.wikimedia.org/wikipedia/commons/e/eb/Button_plantilla.png', title: i18n('button hint'), 'class': 'mw-toolbar-editbutton'})
				.css({cursor: 'pointer'})
				.click(doIt)
			);
} );