מדיה ויקי: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';

	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 sptit 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(/\s*\|\s*/);
		for (var i in params) {
			var param = params[i];
			while (true) {
				match = param.match(/\0(\d+)\0/);
				if (! match || ! match.length)
					break;
				param = param.replace(match[0], specials[parseInt(match[1], 10)-1]);
			}
			var paramPair = param.split("=");
			var name = $.trim(paramPair.shift());
			if (name && paramPair.length) {
				var tp = templateParams[name] = templateParams[name] || {options: {notInParamPage: 1}},
					val = paramPair.join( '=' );
				addParam(name);
				$.extend( tp.options, { 'defval': val } );
				// 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}};
			addParam(paramName);
		}
	}
	
	function buildParamsTd(data) {
		var params = data.params,
			paramOrder = data.paramOrder;

		function onemore(name) {
			var param = params[name];
			templateParams[name] = { desc: param.description && param.description.he || '', options: { required: param.required }  };
			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',
			createEmpties = $('#createEmpties').prop('checked');
		for (var i in dialogFields) {
			var
				field = dialogFields[i],
				name = $.trim(field[0]),
				f = field[1],
				hidden = f.parents('.tpw_hidden').length,
				val = $.trim(f.val());
			if (f.attr('type') == 'checkbox' && ! f.prop('checked'))
				val = "";
			if (!createEmpties && val === "") 
				continue;//skip parameters with no value
			par.push(name + '=' + val);
		}
		return "{{" + par.join(delim + "|") + 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 'Extended';
					case 'button hint': return 'معالج وسائط القالب';
					case 'able templates category name': 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 'Write empty parameters to page';
					case 'dateFormat': return 'd MM yy';
					case 'extended labels': return 'Show all parameters';
					default: return key;
				}
				break;
			case 'he':
				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 'able templates category name': 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;
			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 'able templates category name': throw('Must define category name for wizard-capable templates');
					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';
				}
		}
		return key;
	}

	function paramPage() {return mw.config.get('wgFormattedNamespaces')[10] + ':' + $.trim(template) + '/' + i18n('params subpage');}

	function templatePage() {return mw.config.get('wgFormattedNamespaces')[10] + ':' + $.trim(template);}

	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'));
	}

	function createInputField(paramName) {
		var options = templateParams[paramName].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)
					f.append($('<option>', {text: choices[i], value: choices[i]}));
			}
			else {
				checkbox = true;
				f = $('<input>', {type: 'checkbox', value: choices[0], text: choices[0]})
					.css({float: rtl ? 'right' : 'left'});
				f.prop('checked', options.defval && options.defval == choices[0]);
			}
		}
		else if (options.multiline) {
			var rows = options.multiline;
			f = $('<textarea>', {rows: 1})
				.data({dispRows: isNaN(parseInt(rows)) ? 5 : rows})
				.focus(function(){this.rows = $(this).data('dispRows');})
				.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})
			.bind('paste cut drop input change', updateRawPreview);

		if (options.defval)
			f.val(options.defval);

		if (options.required)
			f.addClass('tpw_required').css({border: '1px red solid'});

		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).text(),
			def = templateParams[paramName],
			desc = def.desc || '';
		if (def.htmlDesc)
			return def.htmlDesc;
		if (def.options.notInParamPage)
			return $('<div>')
				.append(i18n('notInParamPage', paramName) + '<br />')
				.append($('<a>', {href: mw.util.getUrl(paramPage()) + '?action=edit', target: '_blank', text: i18n('editParamPage')}))
				.html();
		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' : 'black'),
			tr = $('<tr>')
				.append(
					$('<td>', {width: 120})
					.css({fontWeight: 'bold', color: nameColor})
					.text(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) {
		$('.tpw_disposable').remove();
		if (rawTemplate)
			buildParamsRaw(data);
		else if (tdTemplate) 
			buildParamsTd(data)
		else
			buildParams(data);
		
		paramsFromSelection();
		var	table = $('<table>');
		var dialog = $('<div>', {'class': 'tpw_disposable'})
			.dialog({height: 'auto',
					title: i18n('wizard dialog title', template),
					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>').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'})
				.css({backgroundColor: "lightGreen", maxWidth: '40em', maxHeight: '8em', overflow: 'auto'}));
		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() {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'},
				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;
		$.ajax({
			url: mw.util.wikiScript(),
			data: {title: paramPage(), action: 'raw'},
			dataType: 'text',
			success: buildDialog,
			cache: false,
			error: 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.widget','jquery.tipsy','jquery.textSelection', 'jquery.ui.autocomplete', 'jquery.ui.dialog', 'jquery.ui.datepicker', '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(['jquery.wikiEditor','jquery.wikiEditor.toolbar.config','ext.wikiEditor.toolbar','ext.wikiEditor','ext.wikiEditor.toolbar'], function() {
			if(typeof $.wikiEditor != 'undefined' && $.wikiEditor.isSupported()) {
				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)
			);
} );