MediaWiki:Gadget-cws-manager-dialog.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.
/**
* @class
* @property {CwsManager} cwsManager
* @property {mw.Title} proposalTitle
* @property {boolean} watched
* @property {string|null} action
*/
class Dialog extends OO.ui.ProcessDialog {
static name = 'cwsManagerDialog';
static size = 'medium';
static title = 'Manage proposal';
static actions = [
{
action: 'submit',
label: 'Submit',
flags: [ 'primary', 'progressive' ]
},
{
label: 'Cancel',
flags: [ 'safe', 'close' ]
}
];
/**
* @param {CwsManager} cwsManagerContext
* @param {string} proposalPageName
* @param {boolean} watched
* @constructor
*/
constructor( cwsManagerContext, proposalPageName, watched ) {
super();
this.cwsManager = cwsManagerContext;
this.proposalTitle = mw.Title.newFromText( proposalPageName ).getPrefixedText();
this.watched = watched;
this.action = null;
// Add properties needed by the ES5-based OOUI inheritance mechanism.
// This roughly simulates OO.inheritClass()
Dialog.parent = Dialog.super = OO.ui.ProcessDialog;
OO.initClass( OO.ui.ProcessDialog );
Dialog.static = Object.create( OO.ui.ProcessDialog.static );
Object.keys( Dialog ).forEach( ( key ) => {
Dialog.static[ key ] = Dialog[ key ];
} );
}
/**
* @param {...*} args
* @override
*/
initialize( ...args ) {
super.initialize( ...args );
this.actionFieldset = this.#getActionFieldset();
this.moveFieldset = this.#getMoveFieldset();
this.moveFieldset.toggle( false );
this.archiveFieldset = this.#getArchiveFieldset();
this.archiveFieldset.toggle( false );
this.unarchiveFieldset = this.#getUnarchiveFieldset();
this.unarchiveFieldset.toggle( false );
this.approveFieldset = this.#getApproveFieldset();
this.approveFieldset.toggle( false );
this.watchFieldset = this.#getWatchFieldset();
this.formPanel = new OO.ui.PanelLayout( { padded: true, expanded: false } );
this.formPanel.$element.append(
this.actionFieldset.$element,
this.moveFieldset.$element,
this.archiveFieldset.$element,
this.unarchiveFieldset.$element,
this.approveFieldset.$element,
this.watchFieldset.$element
);
this.$body.append( this.formPanel.$element );
}
/**
* Update the view based on which action was selected.
*
* @param {OO.ui.RadioOptionWidget} radioButton
*/
onActionInputChange( radioButton ) {
this.action = radioButton.data;
this.moveFieldset.toggle( this.action === 'move' );
this.approveFieldset.toggle( this.action === 'approve' );
this.archiveFieldset.toggle( this.action === 'archive' );
this.unarchiveFieldset.toggle( this.action === 'unarchive' );
this.getActions().setAbilities( { submit: true } );
this.updateSize();
}
/**
* Show/hide the archive reason input, as applicable.
*
* @param {OO.ui.MenuOptionWidget} option
*/
onArchiveReasonInputChange( option ) {
const wikitext = option.data.reason;
if ( wikitext.includes( '$1' ) ) {
this.archivePromptFieldLayout.setLabel( option.data.prompt );
this.archivePromptFieldLayout.toggle( true );
} else {
this.archivePromptFieldLayout.toggle( false );
}
if ( option.data.note ) {
this.archiveReasonFieldLayout.setNotices( [
option.data.note
] );
} else {
this.archiveReasonFieldLayout.setNotices( [] );
}
this.updateSize();
}
/**
* Get the fieldset for selecting an action on the proposal.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getActionFieldset() {
const [ categoryName ] = this.#getTitleParts();
const moveRadio = new OO.ui.RadioOptionWidget( {
label: 'Rename proposal or move to a different category',
data: 'move'
} );
const archiveRadio = new OO.ui.RadioOptionWidget( {
label: 'Move to the archive',
data: 'archive'
} );
archiveRadio.toggle( categoryName !== 'Archive' );
const unarchiveRadio = new OO.ui.RadioOptionWidget( {
label: 'Unarchive proposal',
data: 'unarchive'
} );
unarchiveRadio.toggle( categoryName === 'Archive' );
const approveRadio = new OO.ui.RadioOptionWidget( {
label: 'Approve and prepare for translation',
data: 'approve'
} );
approveRadio.toggle(
![ 'Archive', 'Larger suggestions', 'Untranslated' ].includes( categoryName )
);
const radioSelect = new OO.ui.RadioSelectWidget( {
items: [ moveRadio, archiveRadio, unarchiveRadio, approveRadio ]
} );
radioSelect.connect( this, {
choose: this.onActionInputChange
} );
return new OO.ui.FieldsetLayout( {
label: 'Select action to take on this proposal',
items: [ radioSelect ]
} );
}
/**
* Get the fieldset for moving a proposal.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getMoveFieldset() {
const [ categoryName, proposalName ] = this.#getTitleParts();
const moveFieldset = new OO.ui.FieldsetLayout( { label: 'Move proposal' } );
this.moveTitleInput = new OO.ui.TextInputWidget( {
value: proposalName,
indicator: 'required'
} );
this.moveCategoryInput = this.#getCategoryInput( categoryName );
this.moveReasonInput = new OO.ui.TextInputWidget();
moveFieldset.addItems( [
new OO.ui.FieldLayout( this.moveTitleInput, {
label: 'Proposal title:',
align: 'top'
} ),
new OO.ui.FieldLayout( this.moveCategoryInput, {
label: 'Category:',
align: 'top'
} ),
new OO.ui.FieldLayout( this.moveReasonInput, {
label: 'Optional comment for the logs:',
align: 'top'
} )
] );
return moveFieldset;
}
/**
* Get the fieldset for unarchiving a proposal.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getUnarchiveFieldset() {
const [ categoryName, proposalName ] = this.#getTitleParts();
const unarchiveFieldset = new OO.ui.FieldsetLayout( { label: 'Unarchive proposal' } );
this.unarchiveTitleInput = new OO.ui.TextInputWidget( {
value: proposalName,
indicator: 'required'
} );
this.unarchiveCategoryInput = this.#getCategoryInput( categoryName );
this.unarchiveReasonInput = new OO.ui.TextInputWidget();
unarchiveFieldset.addItems( [
new OO.ui.Element( {
text: 'Unarchiving will remove the archive rationale, if present, ' +
'and move the proposal to the specified category.'
} ),
new OO.ui.FieldLayout( this.unarchiveTitleInput, {
label: 'Proposal title:',
align: 'top'
} ),
new OO.ui.FieldLayout( this.unarchiveCategoryInput, {
label: 'Category:',
align: 'top'
} ),
new OO.ui.FieldLayout( this.unarchiveReasonInput, {
label: 'Optional comment for the logs:',
align: 'top'
} )
] );
return unarchiveFieldset;
}
/**
* Get a new category input.
*
* @param {string} categoryName
* @return {OO.ui.DropdownInputWidget}
* @private
*/
#getCategoryInput( categoryName ) {
return new OO.ui.DropdownInputWidget( {
options: this.cwsManager.config.categories.concat( [ 'Larger suggestions' ] ).map( ( category ) => {
return { data: category, label: category };
} ),
value: categoryName
} );
}
/**
* Get the fieldset for archiving a proposal.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getArchiveFieldset() {
const reasons = this.cwsManager.config.archive_reasons;
const dropdownOptions = [];
reasons.forEach( ( reason ) => {
dropdownOptions.push( new OO.ui.MenuOptionWidget( {
data: reason,
label: reason.display || reason.reason
} ) );
} );
const archiveFieldset = new OO.ui.FieldsetLayout( {
label: 'Archive proposal'
} );
this.archiveReasonInput = new OO.ui.DropdownWidget( {
label: 'Select a rationale for archiving…',
menu: {
items: dropdownOptions
}
} );
this.archiveReasonInput.getMenu().on( 'select', this.onArchiveReasonInputChange.bind( this ) );
this.archiveReasonFieldLayout = new OO.ui.FieldLayout( this.archiveReasonInput, {
label: 'Rationale:',
align: 'top'
} );
this.archivePromptInput = new OO.ui.TextInputWidget( {
indicator: 'required'
} );
this.archivePromptFieldLayout = new OO.ui.FieldLayout( this.archivePromptInput, {
align: 'top'
} );
this.archivePromptFieldLayout.toggle( false );
this.archiveTextarea = new OO.ui.MultilineTextInputWidget( {
placeholder: 'Add any additional messaging here, which will be posted as a comment in the Discussion section.'
} );
const formElements = [
this.archiveReasonFieldLayout,
this.archivePromptFieldLayout,
new OO.ui.FieldLayout( this.archiveTextarea, {
label: 'Optional additional comment (encouraged):',
align: 'top'
} )
];
archiveFieldset.addItems( formElements );
return archiveFieldset;
}
/**
* Get the fieldset for approving a proposal.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getApproveFieldset() {
const content = new OO.ui.HtmlSnippet(
'<p>This action will move the proposal to a new /Proposal subpage, ' +
'convert it to use the proposal template, and insert translate tags. ' +
'Afterwards, the <translate> tags will become visible in transclusions until ' +
'the page is marked for translation, so <strong>please act quickly</strong>. ' +
'Please first verify no use of <tvar> or other fixes to the translation page are needed.'
);
return new OO.ui.FieldsetLayout( {
label: 'Approve proposal',
content: [ content ]
} );
}
/**
* Get the fieldset for the bottom options, such as to watch the proposal page.
*
* @return {OO.ui.FieldsetLayout}
* @private
*/
#getWatchFieldset() {
const watchFieldset = new OO.ui.FieldsetLayout();
this.watchCheckbox = new OO.ui.CheckboxInputWidget( { selected: this.watched } );
const formElements = [
new OO.ui.FieldLayout( this.watchCheckbox, {
label: 'Watch proposal page',
align: 'inline'
} )
];
watchFieldset.addItems( formElements );
return watchFieldset;
}
/**
* Get the proposal category and subpage name.
*
* @return {Array<string>} [ category name, proposal name ]
* @private
*/
#getTitleParts() {
const categoryName = this.proposalTitle.split( '/' )[ 1 ];
const titleRegex = new RegExp(
`^${this.cwsManager.config.survey_root}/${categoryName}/`
);
return [ categoryName, this.proposalTitle.replace( titleRegex, '' ) ];
}
/**
* @param {Object} data
* @return {OO.ui.Process}
* @override
*/
getSetupProcess( data ) {
return super.getSetupProcess( data )
.next( () => {
this.getActions().setAbilities( { submit: !!this.action } );
} );
}
/**
* @param {string} action
* @return {OO.ui.Process}
* @override
*/
getActionProcess( action ) {
return super.getActionProcess( action )
.next( () => {
if ( action === 'submit' ) {
return this.cwsManager.submit(
this.action,
this.getFormData( this.action )
);
}
return super.getActionProcess( action );
} )
.next( () => {
if ( action === 'submit' ) {
// Redirect to the proposal (which may be the same page).
// In the case of moving proposals, we rely on redirects.
window.location.replace( mw.util.getUrl( this.proposalTitle ) );
}
return super.getActionProcess( action );
} );
}
/**
* Get an Object containing all the data we need for submission.
*
* @param {string} action
* @return {Object}
*/
getFormData( action ) {
const data = {
proposalTitle: this.proposalTitle,
watchProposal: this.watchCheckbox.isSelected()
};
switch ( action ) {
case 'move':
return Object.assign( data, {
newName: this.moveTitleInput.getValue(),
newCategory: this.moveCategoryInput.getValue(),
reason: this.moveReasonInput.getValue()
} );
case 'archive':
return Object.assign( data, {
reason: this.archiveReasonInput.getMenu().findSelectedItem().data.reason,
param: this.archivePromptInput.getValue(),
comment: this.archiveTextarea.getValue(),
// 'archive' also 'move' so we need the subpage title as well
newName: this.#getTitleParts()[ 1 ]
} );
case 'unarchive':
return Object.assign( data, {
newName: this.unarchiveTitleInput.getValue(),
newCategory: this.unarchiveCategoryInput.getValue(),
reason: this.unarchiveReasonInput.getValue()
} );
case 'approve':
// Falls through, no extra data needed.
}
return data;
}
}
module.exports = Dialog;