mediawiki-extensions-Visual.../modules/ve-mw/ui/dialogs/ve.ui.MWTransclusionDialog.js
Bartosz Dziewoński 13675e4a81 Fix confusion between #getSetupProcess and #getReadyProcess
The #getReadyProcess method should be used pretty much only to focus
a field inside the dialog after it is opened. It runs after the window
opening animation finishes, so if you add stuff to the window here,
that will be visibly delayed.

The #getSetupProcess method should be used pretty much for everything
else that depends on the opening `data`. It runs before the window
opening animation, so if you add stuff to the window here, it will be
visible while animating it.

Bug: T185944
Change-Id: I71ea5b6e1e1947c1cf8fd749100e854953a8ef3c
2018-07-11 23:47:55 +02:00

379 lines
11 KiB
JavaScript

/*
* VisualEditor user interface MWTransclusionDialog class.
*
* @copyright 2011-2018 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Dialog for inserting and editing MediaWiki transclusions.
*
* @class
* @extends ve.ui.MWTemplateDialog
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.ui.MWTransclusionDialog = function VeUiMWTransclusionDialog( config ) {
// Parent constructor
ve.ui.MWTransclusionDialog.super.call( this, config );
// Properties
this.mode = null;
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTransclusionDialog, ve.ui.MWTemplateDialog );
/* Static Properties */
ve.ui.MWTransclusionDialog.static.name = 'transclusion';
ve.ui.MWTransclusionDialog.static.title =
OO.ui.deferMsg( 'visualeditor-dialog-transclusion-title' );
ve.ui.MWTransclusionDialog.static.actions = ve.ui.MWTemplateDialog.static.actions.concat( [
{
action: 'mode',
modes: [ 'edit', 'insert' ],
// HACK: Will be set later, but we want measurements to be accurate in the mean time, this
// will not be needed when T93290 is resolved
label: $( document.createTextNode( '\u00a0' ) )
}
] );
/**
* Map of symbolic mode names and CSS classes.
*
* @static
* @property {Object}
* @inheritable
*/
ve.ui.MWTransclusionDialog.static.modeCssClasses = {
single: 've-ui-mwTransclusionDialog-single',
multiple: 've-ui-mwTransclusionDialog-multiple'
};
ve.ui.MWTransclusionDialog.static.bookletLayoutConfig = ve.extendObject(
{},
ve.ui.MWTemplateDialog.static.bookletLayoutConfig,
{ outlined: true, editable: true }
);
/* Methods */
/**
* Handle outline controls move events.
*
* @param {number} places Number of places to move the selected item
*/
ve.ui.MWTransclusionDialog.prototype.onOutlineControlsMove = function ( places ) {
var part, promise,
parts = this.transclusionModel.getParts(),
item = this.bookletLayout.getOutline().findSelectedItem();
if ( item ) {
part = this.transclusionModel.getPartFromId( item.getData() );
// Move part to new location, and if dialog is loaded switch to new part page
promise = this.transclusionModel.addPart( part, parts.indexOf( part ) + places );
if ( this.loaded && !this.preventReselection ) {
promise.done( this.setPageByName.bind( this, part.getId() ) );
}
}
};
/**
* Handle outline controls remove events.
*/
ve.ui.MWTransclusionDialog.prototype.onOutlineControlsRemove = function () {
var id, part, param,
item = this.bookletLayout.getOutline().findSelectedItem();
if ( item ) {
id = item.getData();
part = this.transclusionModel.getPartFromId( id );
// Check if the part is the actual template, or one of its parameters
if ( part instanceof ve.dm.MWTemplateModel && id !== part.getId() ) {
param = part.getParameterFromId( id );
if ( param instanceof ve.dm.MWParameterModel ) {
part.removeParameter( param );
}
} else if ( part instanceof ve.dm.MWTransclusionPartModel ) {
this.transclusionModel.removePart( part );
}
}
};
/**
* Handle add template button click events.
*/
ve.ui.MWTransclusionDialog.prototype.onAddTemplateButtonClick = function () {
this.addPart( new ve.dm.MWTemplatePlaceholderModel( this.transclusionModel ) );
};
/**
* Handle add content button click events.
*/
ve.ui.MWTransclusionDialog.prototype.onAddContentButtonClick = function () {
this.addPart( new ve.dm.MWTransclusionContentModel( this.transclusionModel, '' ) );
};
/**
* Handle add parameter button click events.
*/
ve.ui.MWTransclusionDialog.prototype.onAddParameterButtonClick = function () {
var part, param,
item = this.bookletLayout.getOutline().findSelectedItem();
if ( item ) {
part = this.transclusionModel.getPartFromId( item.getData() );
if ( part instanceof ve.dm.MWTemplateModel ) {
param = new ve.dm.MWParameterModel( part, '', null );
part.addParameter( param );
}
}
};
/**
* Handle booklet layout page set events.
*
* @param {OO.ui.PageLayout} page Active page
*/
ve.ui.MWTransclusionDialog.prototype.onBookletLayoutSet = function ( page ) {
this.addParameterButton.setDisabled(
!( page instanceof ve.ui.MWTemplatePage || page instanceof ve.ui.MWParameterPage )
);
this.bookletLayout.getOutlineControls().removeButton.toggle( !(
(
page instanceof ve.ui.MWParameterPage &&
page.parameter.isRequired()
) || (
this.transclusionModel.getParts().length === 1 &&
page instanceof ve.ui.MWTemplatePlaceholderPage
)
) );
};
/**
* @inheritdoc
*/
ve.ui.MWTransclusionDialog.prototype.onReplacePart = function ( removed, added ) {
var single, label;
ve.ui.MWTransclusionDialog.super.prototype.onReplacePart.call( this, removed, added );
if ( this.transclusionModel.getParts().length === 0 ) {
this.addParameterButton.setDisabled( true );
this.addPart( new ve.dm.MWTemplatePlaceholderModel( this.transclusionModel ) );
}
single = this.isSingleTemplateTransclusion();
label = ve.msg( 'visualeditor-dialog-action-insert' );
this.actions
.setAbilities( { mode: single } )
.forEach( { actions: 'insert' }, function ( action ) {
action.setLabel( label );
} );
};
/**
* Checks if transclusion only contains a single template or template placeholder.
*
* @return {boolean} Transclusion only contains a single template or template placeholder
*/
ve.ui.MWTransclusionDialog.prototype.isSingleTemplateTransclusion = function () {
var parts = this.transclusionModel && this.transclusionModel.getParts();
return parts && parts.length === 1 && (
parts[ 0 ] instanceof ve.dm.MWTemplateModel ||
parts[ 0 ] instanceof ve.dm.MWTemplatePlaceholderModel
);
};
/**
* @inheritdoc
*/
ve.ui.MWTransclusionDialog.prototype.getPageFromPart = function ( part ) {
var page = ve.ui.MWTransclusionDialog.super.prototype.getPageFromPart.call( this, part );
if ( !page && part instanceof ve.dm.MWTransclusionContentModel ) {
return new ve.ui.MWTransclusionContentPage( part, part.getId(), { $overlay: this.$overlay } );
}
return page;
};
/**
* Set dialog mode.
*
* Auto mode will choose single if possible.
*
* @param {string} [mode='multiple'] Symbolic name of dialog mode, `multiple`, `single` or 'auto'
*/
ve.ui.MWTransclusionDialog.prototype.setMode = function ( mode ) {
var name, single,
modeCssClasses = ve.ui.MWTransclusionDialog.static.modeCssClasses;
if ( this.transclusionModel ) {
if ( mode === 'auto' ) {
mode = this.isSingleTemplateTransclusion() ? 'single' : 'multiple';
}
}
if ( !modeCssClasses[ mode ] ) {
mode = 'multiple';
}
if ( this.mode !== mode ) {
this.mode = mode;
single = mode === 'single';
if ( this.$content ) {
for ( name in modeCssClasses ) {
this.$content.toggleClass( modeCssClasses[ name ], name === mode );
}
}
this.setSize( single ? 'medium' : 'large' );
this.bookletLayout.toggleOutline( !single );
this.updateTitle();
this.updateModeActionState();
// HACK blur any active input so that its dropdown will be hidden and won't end
// up being mispositioned
this.$content.find( 'input:focus' ).blur();
}
};
/**
* Update the dialog title.
*/
ve.ui.MWTransclusionDialog.prototype.updateTitle = function () {
if ( this.mode === 'multiple' ) {
this.title.setLabel( this.constructor.static.title );
} else {
// Parent method
ve.ui.MWTransclusionDialog.super.prototype.updateTitle.call( this );
}
};
/**
* Update the state of the 'mode' action
*/
ve.ui.MWTransclusionDialog.prototype.updateModeActionState = function () {
var parts = this.transclusionModel && this.transclusionModel.getParts(),
mode = this.mode;
this.actions.forEach( { actions: [ 'mode' ] }, function ( action ) {
action.setLabel(
mode === 'single' ?
ve.msg( 'visualeditor-dialog-transclusion-multiple-mode' ) :
ve.msg( 'visualeditor-dialog-transclusion-single-mode' )
);
} );
// Decide whether the button should be enabled or not. It needs to be:
// * disabled when we're in the initial add-new-template phase, because it's
// meaningless
// * disabled if we're in a multi-part transclusion, because the sidebar's
// forced open
// * enabled if we're in a single-part transclusion, because the sidebar's
// closed but can be opened to add more parts
if ( parts ) {
if ( parts.length === 1 && parts[ 0 ] instanceof ve.dm.MWTemplatePlaceholderModel ) {
// Initial new-template phase: button is meaningless
this.actions.setAbilities( { mode: false } );
} else if ( !this.isSingleTemplateTransclusion() ) {
// Multi-part transclusion: button disabled because sidebar forced-open
this.actions.setAbilities( { mode: false } );
} else {
// Single-part transclusion: button enabled because sidebar is optional
this.actions.setAbilities( { mode: true } );
}
}
};
/**
* Add a part to the transclusion.
*
* @param {ve.dm.MWTransclusionPartModel} part Part to add
*/
ve.ui.MWTransclusionDialog.prototype.addPart = function ( part ) {
var index, promise,
parts = this.transclusionModel.getParts(),
item = this.bookletLayout.getOutline().findSelectedItem();
if ( part ) {
// Insert after selected part, or at the end if nothing is selected
index = item ?
parts.indexOf( this.transclusionModel.getPartFromId( item.getData() ) ) + 1 :
parts.length;
// Add the part, and if dialog is loaded switch to part page
promise = this.transclusionModel.addPart( part, index );
if ( this.loaded && !this.preventReselection ) {
promise.done( this.setPageByName.bind( this, part.getId() ) );
}
}
};
/**
* @inheritdoc
*/
ve.ui.MWTransclusionDialog.prototype.getActionProcess = function ( action ) {
if ( action === 'mode' ) {
return new OO.ui.Process( function () {
this.setMode( this.mode === 'single' ? 'multiple' : 'single' );
}, this );
}
return ve.ui.MWTransclusionDialog.super.prototype.getActionProcess.call( this, action );
};
/**
* @inheritdoc
*/
ve.ui.MWTransclusionDialog.prototype.initialize = function () {
// Parent method
ve.ui.MWTransclusionDialog.super.prototype.initialize.call( this );
// Properties
this.addTemplateButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'puzzle',
title: ve.msg( 'visualeditor-dialog-transclusion-add-template' )
} );
this.addContentButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'wikiText',
title: ve.msg( 'visualeditor-dialog-transclusion-add-content' )
} );
this.addParameterButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'parameter',
title: ve.msg( 'visualeditor-dialog-transclusion-add-param' )
} );
// Events
this.bookletLayout.connect( this, { set: 'onBookletLayoutSet' } );
this.addTemplateButton.connect( this, { click: 'onAddTemplateButtonClick' } );
this.addContentButton.connect( this, { click: 'onAddContentButtonClick' } );
this.addParameterButton.connect( this, { click: 'onAddParameterButtonClick' } );
this.bookletLayout.getOutlineControls()
.addItems( [ this.addTemplateButton, this.addContentButton, this.addParameterButton ] )
.connect( this, {
move: 'onOutlineControlsMove',
remove: 'onOutlineControlsRemove'
} );
};
/**
* @inheritdoc
*/
ve.ui.MWTransclusionDialog.prototype.getSetupProcess = function ( data ) {
return ve.ui.MWTransclusionDialog.super.prototype.getSetupProcess.call( this, data )
.next( function () {
this.updateModeActionState();
this.setMode( 'auto' );
}, this );
};
/* Registration */
ve.ui.windowFactory.register( ve.ui.MWTransclusionDialog );