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