MediaWiki:Gadget-TemplateParamWizard.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press 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 we did not find "Parameters" page, so the parameters are extracted from template page itself. rawTemplate, 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; 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 = []; while (true) { //extract inner links, inner templates and inner params - we don't want to sptit those. var 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) { var 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) { templateParams[name] = templateParams[name] || {options: {notInParamPage: 1}}; addParam(name); $.extend(templateParams[name].options, {'defval': paramPair.join('=')}); } } } function buildParamsRaw(data) { var paramExtractor = /{{3,}(.*?)[<|}]/mg, m; while (m = paramExtractor.exec(data)) { templateParams[m[1]] = {desc: '', options: {multiline: 5}}; addParam(m[1]); } } 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 (line.indexOf('globalExplanation') + 1) { extractGlobalExplanation() continue; } if (! line || ! (/^\|/.test(line))) //wikitext for column continue; var fields = line.substr(1).split(/\|{1,2}/); 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*/); if (option.length < 2) option = option[0].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'; 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(val=="" && f.attr('type') != 'checkbox') continue;//skip parameters with no value if (f.attr('type') == 'checkbox' && ! f.prop('checked')) val = ""; 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.wikiGetlink('قالب:' + 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 'dateFormat': return 'd MM yy'; case 'extended labels': return 'Show all parameters'; } case 'he': switch (key) { case 'explain': return rawTemplate ? 'לתבנית "' + template + '" אין דף פרמטרים, ולכן לשדות אין תיאור.' : 'השדות המסומנים באדום הם חובה, השאר אופציונליים.'; case 'wizard dialog title': return 'מילוי הפרמטרים עבור ' + '<a href="' + mw.util.wikiGetlink('תבנית:' + 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 'dateFormat': return 'd בMM yy'; 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 '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 '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()); } 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.wikiGetlink(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 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'}).change(updateRawPreview)) ) .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') .live('mouseenter', enterTipsy) .live('mouseleave', 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) { 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() { function okButtonPressed() { template = 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() { rawTemplate = false; $.ajax({ url: mw.util.wikiScript(), data: {title: paramPage(), action: 'raw', ctype: 'text/x-wiki'}, success: buildDialog, cache: false, error: function() { rawTemplate = true; $.ajax({ url: mw.util.wikiScript(), data: {title: templatePage(), action: 'raw', ctype: 'text/x-wiki'}, success: buildDialog, error: reportError }); } }); } function doIt() { mw.loader.using(['jquery.ui.widget','jquery.tipsy','jquery.textSelection', 'jquery.ui.autocomplete', 'jquery.ui.dialog', 'jquery.ui.datepicker'], function() { init(); var match = $("#wpTextbox1").textSelection('getSelection').match(/^\{\{([^|}]*)/); template = match ? $.trim(match[1]) : null; if (template) fireDialog(); else pickTemplate(); }); } if (mw.user.options.get('usebetatoolbar')) mw.loader.using(['jquery.wikiEditor','jquery.wikiEditor.toolbar.config','ext.wikiEditor.toolbar'], function() { if(typeof $.wikiEditor != 'undefined' && $.wikiEditor.isSupported()) $('#wpTextbox1').wikiEditor('addToToolbar', { section: 'advanced', groups: { 'wizards': { tools: { 'linkTemplatewizard': { label: i18n('button hint'), type: 'button', icon: '//upload.wikimedia.org/wikipedia/commons/d/dd/Vector_toolbar_template_button.png', action: {type: 'callback', execute: doIt} } } } } }); }); 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) ); });