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.
var ParamEdit = {

	messages: {
		'param-edit-template-not-found': 'Template "$1" not found in this page',
		'param-edit-template-repeated': 'Template "$1" was found more than once in this page',
		'param-edit-summary-insert': 'Add parameter "$1" with value "$2" to Template:$3',
		'param-edit-summary-update': 'Change parameter "$1" from "$2" to "$3" in Template:$4',
		'param-edit-summary-delete': 'Delete parameter "$1" from Template:$2',
		'param-edit-summary': 'Edit summary',
		'param-edit-example': 'Example: $1',
		'param-edit-publish': 'Publish',
		'param-edit-cancel': 'Cancel',
	},

	/**
	 * Will hold the wikitext of the current page
	 */
	pageWikitext: '',

	/**
	 * Will hold the template data of the template(s) being edited
	 */
	templateData: {},

	init: function () {
		mw.messages.set( ParamEdit.messages );
		$( '#mw-content-text' ).find( '.paramedit' ).each( ParamEdit.makeEditButton );
	},

	makeEditButton: function () {
		var path = '<path fill="currentColor" d="M16.77 8l1.94-2a1 1 0 0 0 0-1.41l-3.34-3.3a1 1 0 0 0-1.41 0L12 3.23zm-5.81-3.71L1 14.25V19h4.75l9.96-9.96-4.75-4.75z"></path>';
		var icon = '<svg width="14" height="14" viewBox="0 0 20 20">' + path + '</svg>';
		var $button = $( '<span class="noprint">' + icon + '</span>' );
		$button.css( { color: '#a2a9b1', cursor: 'pointer' } );
		$button.on( 'mouseenter', function () { $( this ).css( 'color', '#202122' ); } );
		$button.on( 'mouseleave', function () { $( this ).css( 'color', '#a2a9b1' ); } );
		$button.on( 'click', ParamEdit.edit );
		$( this ).html( $button );
	},

	edit: function () {
		var $button = $( this ).closest( '.paramedit' );
		var template = $button.data( 'template' );

		// If the page wikitext and template data are set
		// it means they were already loaded by a previous click
		if ( ParamEdit.pageWikitext && ParamEdit.templateData[ template ] ) {
			ParamEdit.makeEditForm( $button, template );
			return;
		}

		$.when(
			ParamEdit.getPageWikitext(),
			ParamEdit.getTemplateData( template )
		).done( function () {
			ParamEdit.makeEditForm( $button, template );
		} );
	},

	makeEditForm: function ( $button, template ) {

		// Check that the template is present in the page
		// @todo Support nested templates
		var templateRegExp = new RegExp( '{{' + template + '[^}]*?}}', 'ig' );
		var match = ParamEdit.pageWikitext.match( templateRegExp );
		if ( !match ) {
			mw.notify( mw.msg( 'param-edit-template-not-found', template ) );
			return;
		}

		// Check that there's only one template in the page
		if ( match.length > 1 ) {
			mw.notify( mw.msg( 'param-edit-template-repeated', template ) );
			return;
		}

		var userLanguage = mw.config.get( 'wgUserLanguage' );
		var contentLanguage = mw.config.get( 'wgContentLanguage' );
		var templateData = ParamEdit.templateData[ template ] || {};
		var param = $button.data( 'param' );
		var paramData = templateData.params && templateData.params[ param ] || {};
		var paramType = paramData.type || 'string';
		var paramLabel = paramData.label && ( paramData.label[ userLanguage ] || paramData.label[ contentLanguage ] ) || param;
		var paramDescription = paramData.description && ( paramData.description[ userLanguage ] || paramData.description[ contentLanguage ] );
		var paramExample = paramData.example && ( paramData.example[ userLanguage ] || paramData.example[ contentLanguage ] );
		var help = ( paramDescription ? '<div>' + paramDescription + '</div>' : '' ) + ( paramExample && paramType !== 'boolean' ? '<div>' + mw.msg( 'param-edit-example', paramExample ) + '</div>' : '' );
		var $help = help ? $( help ) : null;

		var value = $button.data( 'value' ).toString();
		var wikitextInput = new OO.ui.TextInputWidget( { name: 'wikitext', value: value } );
		var wikitextLayout = new OO.ui.FieldLayout( wikitextInput, { label: paramLabel, align: 'top', help: $help, helpInline: true } );
		switch ( paramType ) {
			case 'boolean':
				var selected = value.toLowerCase() === 'no' ? false : true; // @todo i18n
				wikitextInput = new OO.ui.CheckboxInputWidget( { name: 'wikitext', selected: selected } );
				wikitextLayout = new OO.ui.FieldLayout( wikitextInput, { label: paramLabel, align: 'inline', help: $help, helpInline: true } );
				break;
			case 'content':
				wikitextInput = new OO.ui.MultilineTextInputWidget( { name: 'wikitext', value: value, autosize: true } );
				wikitextLayout = new OO.ui.FieldLayout( wikitextInput, { label: paramLabel, align: 'top', help: $help, helpInline: true } );
				break;
			case 'number':
				wikitextInput = new OO.ui.NumberInputWidget( { name: 'wikitext', value: value } );
				wikitextLayout = new OO.ui.FieldLayout( wikitextInput, { label: paramLabel, align: 'top', help: $help, helpInline: true } );
				break;
		}
		var summaryInput = new OO.ui.TextInputWidget( { name: 'summary', placeholder: mw.msg( 'param-edit-summary' ) } );
		var summaryLayout = new OO.ui.FieldLayout( summaryInput );
		var formLayout = new OO.ui.FormLayout( { items: [ wikitextLayout, summaryLayout ] } );

		OO.ui.confirm( formLayout.$element, {
			size: 'medium',
			actions: [ {
				action: 'reject',
				label: mw.msg( 'param-edit-cancel' ),
				flags: [ 'safe', 'close' ]
			}, {
				action: 'accept',
				label: mw.msg( 'param-edit-publish' ),
				flags: [ 'primary', 'progressive' ] 
			} ]
		} ).done( function ( confirm ) {
			if ( confirm ) {
				ParamEdit.publish( $button, template, param, value, paramType, wikitextInput, summaryInput );
			}
		} );
	},

	publish: function ( $button, template, param, value, paramType, wikitextInput, summaryInput ) {

		// If nothing changed, simply close the dialog
		var newValue = wikitextInput.getValue();
		if ( value === newValue ) {
			return;
		}

		// First replace the button for a loading spinner
		// to prevent further clicks and to signal the user that something's happening
		var $spinner = ParamEdit.makeSpinner();
		$button.replaceWith( $spinner );

		var page = mw.config.get( 'wgPageName' );
		new mw.Api().edit( page, function ( revision ) {
	        var wikitext = revision.content;

			// @todo Support nested templates
			var templateRegExp = new RegExp( '{{' + template + '[^}]*?}}', 'i' );
			var templateMatch = wikitext.match( templateRegExp );

			// This should never happen because we already checked
			if ( !templateMatch ) {
				var error = mw.msg( 'param-edit-template-not-found', template );
				mw.notify( error );
				return;
			}

			// Booleans are a bit of a special case
			if ( paramType === 'boolean' ) {
				newValue = wikitextInput.isSelected() ? 'yes' : 'no'; // @todo i18n
			}

			// @todo Support inline format
			var templateWikitext = templateMatch[0];
			var paramRegExp = new RegExp( '\\n\\| *' + param + ' *= *.*' );
			var paramMatch = templateWikitext.match( paramRegExp );
			var newTemplateWikitext;
			if ( paramMatch ) {
				if ( newValue ) {
					newTemplateWikitext = templateWikitext.replace( paramRegExp, '\n| ' + param + ' = ' + newValue );
				} else {
					newTemplateWikitext = templateWikitext.replace( paramRegExp, '' );
				}
			} else {
				newTemplateWikitext = templateWikitext.replace( '}}', '\n| ' + param + ' = ' + newValue + '\n}}' );
			}
			wikitext = wikitext.replace( templateWikitext, newTemplateWikitext );

			var summary = ParamEdit.makeSummary( summaryInput, template, param, value, newValue );

			return { text: wikitext, summary: summary };

	    } ).done( ParamEdit.success );
	},

	/**
	 * On success, simply reload the entire page
	 * 
	 * The ideal UX would be to simply replace the value of the parameter
	 * but that is actually very complicated (almost impossible) to do well.
	 * The "second best" solution would be to replace the entire template,
	 * this will be doable once Parsoid is enabled by default in all page views.
	 * In the meantime, modern browsers remember the current scroll position,
	 * so simply reloading the page is a "good enough" solution.
	 */
	success: function () {
		location.reload();
	},

	/**
	 * Get the wikitext of the current page
	 */
	getPageWikitext: function () {
		return new mw.Api().get( {
			page: mw.config.get( 'wgPageName' ),
			action: 'parse',
			prop: 'wikitext',
			formatversion: 2,
		} ).done( function ( data ) {
			var pageWikitext = data.parse.wikitext;
			ParamEdit.pageWikitext = pageWikitext;
		} );
	},

	/**
	 * Get the template data of the given template
	 */
	getTemplateData: function ( template ) {
		return new mw.Api().get( {
			titles: 'Template:' + template,
			action: 'templatedata',
			redirects: true,
			includeMissingTitles: true,
			formatversion: 2
		} ).done( function ( data ) {
			var templateData = Object.values( data.pages )[0];
			ParamEdit.templateData[ template ] = templateData;
		} );
	},

	/**
	 * Helper method to make a helpful edit summary
	 */
	 makeSummary: function ( summaryInput, template, param, oldValue, newValue ) {
		var summary = summaryInput.getValue();
		if ( !summary ) {
			if ( newValue ) {
				if ( oldValue ) {
					summary = mw.msg( 'param-edit-summary-update', param, oldValue, newValue, template );
				} else {
					summary = mw.msg( 'param-edit-summary-insert', param, newValue, template );
				}
			} else {
				summary = mw.msg( 'param-edit-summary-delete', param, template );
			}
		}
		summary += ' #ParamEdit';
		return summary;
	 },

	/**
	 * Helper method to make a spinner (loading) icon
	 */
	 makeSpinner: function () {
		var spinner = '<svg class="miniedit-spinner" width="14" height="14" viewBox="0 0 100 100">';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.000" transform="rotate(-90 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.125" transform="rotate(-45 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.250" transform="rotate(0 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.375" transform="rotate(45 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.500" transform="rotate(90 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.625" transform="rotate(135 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.750" transform="rotate(180 50 50)" />';
		spinner += '<rect fill="#555555" height="10" rx="5" ry="5" width="28" x="67" y="45" opacity="0.875" transform="rotate(225 50 50)" />';
		spinner += '</svg>';
		var $spinner = $( spinner );
		var degrees = 0;
		setInterval( function () {
			degrees += 45;
			$spinner.css( 'transform', 'rotate(' + degrees + 'deg)' );
		}, 100 );
		return $spinner;
	}
};

mw.loader.using( [
	'mediawiki.api',
	'mediawiki.user',
	'mediawiki.util',
	'oojs-ui-core',
	'oojs-ui-windows',
	'oojs-ui-widgets'
], ParamEdit.init );
Cookies help us deliver our services. By using our services, you agree to our use of cookies.