mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-01 17:36:35 +00:00
a232147b11
Change-Id: I75ac4b378ef485feead9d0eff34ed7877b94b5d0
417 lines
12 KiB
JavaScript
417 lines
12 KiB
JavaScript
/*
|
|
* VisualEditor user interface MWTemplateDialog class.
|
|
*
|
|
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Dialog for inserting and editing MediaWiki transclusions.
|
|
*
|
|
* @class
|
|
* @abstract
|
|
* @extends ve.ui.NodeDialog
|
|
*
|
|
* @constructor
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.ui.MWTemplateDialog = function VeUiMWTemplateDialog( config ) {
|
|
// Parent constructor
|
|
ve.ui.MWTemplateDialog.super.call( this, config );
|
|
|
|
// Properties
|
|
this.transclusionModel = null;
|
|
this.loaded = false;
|
|
this.preventReselection = false;
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ui.MWTemplateDialog, ve.ui.NodeDialog );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ui.MWTemplateDialog.static.icon = 'template';
|
|
|
|
ve.ui.MWTemplateDialog.static.modelClasses = [ ve.dm.MWTransclusionNode ];
|
|
|
|
ve.ui.MWTemplateDialog.static.actions = [
|
|
{
|
|
action: 'apply',
|
|
label: OO.ui.deferMsg( 'visualeditor-dialog-action-apply' ),
|
|
flags: 'primary',
|
|
modes: 'edit'
|
|
},
|
|
{
|
|
action: 'insert',
|
|
label: OO.ui.deferMsg( 'visualeditor-dialog-action-insert' ),
|
|
flags: [ 'primary', 'constructive' ],
|
|
modes: 'insert'
|
|
},
|
|
{
|
|
label: OO.ui.deferMsg( 'visualeditor-dialog-action-cancel' ),
|
|
flags: 'safe',
|
|
modes: [ 'insert', 'edit' ]
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Configuration for booklet layout.
|
|
*
|
|
* @static
|
|
* @property {Object}
|
|
* @inheritable
|
|
*/
|
|
ve.ui.MWTemplateDialog.static.bookletLayoutConfig = {
|
|
continuous: true,
|
|
outlined: false
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handle the transclusion being ready to use.
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.onTransclusionReady = function () {
|
|
this.loaded = true;
|
|
this.$element.addClass( 've-ui-mwTemplateDialog-ready' );
|
|
this.popPending();
|
|
};
|
|
|
|
/**
|
|
* Handle parts being replaced.
|
|
*
|
|
* @param {ve.dm.MWTransclusionPartModel} removed Removed part
|
|
* @param {ve.dm.MWTransclusionPartModel} added Added part
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.onReplacePart = function ( removed, added ) {
|
|
var i, len, page, name, names, params, partPage, reselect, insertable, addedCount,
|
|
removePages = [];
|
|
|
|
if ( removed ) {
|
|
// Remove parameter pages of removed templates
|
|
partPage = this.bookletLayout.getPage( removed.getId() );
|
|
if ( removed instanceof ve.dm.MWTemplateModel ) {
|
|
params = removed.getParameters();
|
|
for ( name in params ) {
|
|
removePages.push( this.bookletLayout.getPage( params[name].getId() ) );
|
|
}
|
|
removed.disconnect( this );
|
|
}
|
|
if ( this.loaded && !this.preventReselection && partPage.isActive() ) {
|
|
reselect = this.bookletLayout.getClosestPage( partPage );
|
|
}
|
|
removePages.push( partPage );
|
|
this.bookletLayout.removePages( removePages );
|
|
}
|
|
|
|
if ( added ) {
|
|
page = this.getPageFromPart( added );
|
|
if ( page ) {
|
|
this.bookletLayout.addPages( [ page ], this.transclusionModel.getIndex( added ) );
|
|
if ( reselect ) {
|
|
// Use added page instead of closest page
|
|
this.setPageByName( added.getId() );
|
|
}
|
|
// Add existing params to templates (the template might be being moved)
|
|
if ( added instanceof ve.dm.MWTemplateModel ) {
|
|
names = added.getParameterNames();
|
|
params = added.getParameters();
|
|
// Prevent selection changes
|
|
this.preventReselection = true;
|
|
for ( i = 0, len = names.length; i < len; i++ ) {
|
|
this.onAddParameter( params[names[i]] );
|
|
}
|
|
this.preventReselection = false;
|
|
added.connect( this, { add: 'onAddParameter', remove: 'onRemoveParameter' } );
|
|
if ( names.length ) {
|
|
this.setPageByName( params[names[0]].getId() );
|
|
}
|
|
}
|
|
|
|
// Add required and suggested params to user created templates
|
|
if ( added instanceof ve.dm.MWTemplateModel && this.loaded ) {
|
|
// Prevent selection changes
|
|
this.preventReselection = true;
|
|
addedCount = added.addPromptedParameters();
|
|
this.preventReselection = false;
|
|
names = added.getParameterNames();
|
|
params = added.getParameters();
|
|
if ( names.length ) {
|
|
this.setPageByName( params[names[0]].getId() );
|
|
} else if ( addedCount === 0 ) {
|
|
page.onAddButtonFocus();
|
|
}
|
|
}
|
|
}
|
|
} else if ( reselect ) {
|
|
this.setPageByName( reselect.getName() );
|
|
}
|
|
|
|
insertable = this.isInsertable();
|
|
this.actions.setAbilities( { apply: insertable, insert: insertable } );
|
|
|
|
this.updateTitle();
|
|
};
|
|
|
|
/**
|
|
* Handle add param events.
|
|
*
|
|
* @param {ve.dm.MWParameterModel} param Added param
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.onAddParameter = function ( param ) {
|
|
var page;
|
|
|
|
if ( param.getName() ) {
|
|
page = new ve.ui.MWParameterPage( param, param.getId(), { $: this.$ } );
|
|
} else {
|
|
page = new ve.ui.MWParameterPlaceholderPage( param, param.getId(), { $: this.$ } );
|
|
}
|
|
this.bookletLayout.addPages( [ page ], this.transclusionModel.getIndex( param ) );
|
|
if ( this.loaded && !this.preventReselection ) {
|
|
this.setPageByName( param.getId() );
|
|
} else {
|
|
this.onAddParameterBeforeLoad( page );
|
|
}
|
|
|
|
// Recalculate tab indexes
|
|
this.$body.find( '.ve-ui-mwParameterPage' ).each( function ( index ) {
|
|
$( this )
|
|
.find( '.ve-ui-mwParameterPage-field > .oo-ui-textInputWidget > textarea' )
|
|
.attr( 'tabindex', index * 3 + 1 )
|
|
.end()
|
|
.find( '.ve-ui-mwParameterPage-infoButton' )
|
|
.attr( 'tabindex', index * 3 + 2 )
|
|
.end()
|
|
.find( '.ve-ui-mwParameterPage-removeButton' )
|
|
.attr( 'tabindex', index * 3 + 3 )
|
|
.end()
|
|
.find( '.ve-ui-mwParameterPage-more' )
|
|
.attr( 'tabindex', index * 3 + 4 );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Additional handling of parameter addition events before loading.
|
|
*
|
|
* @param {ve.ui.MWParameterPage} page Parameter page object
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.onAddParameterBeforeLoad = function () {};
|
|
|
|
/**
|
|
* Handle remove param events.
|
|
*
|
|
* @param {ve.dm.MWParameterModel} param Removed param
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.onRemoveParameter = function ( param ) {
|
|
var page = this.bookletLayout.getPage( param.getId() ),
|
|
reselect = this.bookletLayout.getClosestPage( page );
|
|
|
|
this.bookletLayout.removePages( [ page ] );
|
|
if ( this.loaded && !this.preventReselection ) {
|
|
this.setPageByName( reselect.getName() );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks if transclusion is in a valid state for inserting into the document
|
|
*
|
|
* If the transclusion is empty or only contains a placeholder it will not be insertable.
|
|
*
|
|
* @returns {boolean} Transclusion can be inserted
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.isInsertable = function () {
|
|
var parts = this.transclusionModel && this.transclusionModel.getParts();
|
|
|
|
return this.loading.state() === 'resolved' &&
|
|
parts.length &&
|
|
( parts.length > 1 || !( parts[0] instanceof ve.dm.MWTemplatePlaceholderModel ) );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getBodyHeight = function () {
|
|
return 400;
|
|
};
|
|
|
|
/**
|
|
* Get a page for a transclusion part.
|
|
*
|
|
* @param {ve.dm.MWTransclusionModel} part Part to get page for
|
|
* @return {OO.ui.PageLayout|null} Page for part, null if no matching page could be found
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getPageFromPart = function ( part ) {
|
|
if ( part instanceof ve.dm.MWTemplateModel ) {
|
|
return new ve.ui.MWTemplatePage( part, part.getId(), { $: this.$ } );
|
|
} else if ( part instanceof ve.dm.MWTemplatePlaceholderModel ) {
|
|
return new ve.ui.MWTemplatePlaceholderPage( part, part.getId(), { $: this.$ } );
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Get the label of a template or template placeholder.
|
|
*
|
|
* @param {ve.dm.MWTemplateModel|ve.dm.MWTemplatePlaceholderModel} part Part to check
|
|
* @returns {string} Label of template or template placeholder
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getTemplatePartLabel = function ( part ) {
|
|
return part instanceof ve.dm.MWTemplateModel ?
|
|
part.getSpec().getLabel() : ve.msg( 'visualeditor-dialog-transclusion-placeholder' );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getSelectedNode = function ( data ) {
|
|
var selectedNode = ve.ui.MWTemplateDialog.super.prototype.getSelectedNode.call( this );
|
|
|
|
// Data initialization
|
|
data = data || {};
|
|
|
|
// Require template to match if specified
|
|
if ( selectedNode && data.template && !selectedNode.isSingleTemplate( data.template ) ) {
|
|
return null;
|
|
}
|
|
|
|
return selectedNode;
|
|
};
|
|
|
|
/**
|
|
* Set the page by name.
|
|
*
|
|
* Page names are always the ID of the part or param they represent.
|
|
*
|
|
* @param {string} name Page name
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.setPageByName = function ( name ) {
|
|
if ( this.bookletLayout.isOutlined() ) {
|
|
this.bookletLayout.getOutline().selectItem(
|
|
this.bookletLayout.getOutline().getItemFromData( name )
|
|
);
|
|
} else {
|
|
this.bookletLayout.setPage( name );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update the dialog title.
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.updateTitle = function () {
|
|
var parts = this.transclusionModel && this.transclusionModel.getParts();
|
|
|
|
this.title.setLabel(
|
|
parts && parts.length === 1 && parts[0] ?
|
|
this.getTemplatePartLabel( parts[0] ) :
|
|
ve.msg( 'visualeditor-dialog-transclusion-loading' )
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.initialize = function () {
|
|
// Parent method
|
|
ve.ui.MWTemplateDialog.super.prototype.initialize.call( this );
|
|
|
|
// Properties
|
|
this.panels = new OO.ui.StackLayout( { $: this.$ } );
|
|
this.bookletLayout = new OO.ui.BookletLayout(
|
|
ve.extendObject(
|
|
{ $: this.$ },
|
|
this.constructor.static.bookletLayoutConfig
|
|
)
|
|
);
|
|
|
|
// Initialization
|
|
this.$content.addClass( 've-ui-mwTemplateDialog' );
|
|
this.$body.append( this.panels.$element );
|
|
this.panels.addItems( [ this.bookletLayout ] );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getActionProcess = function ( action ) {
|
|
if ( action === 'apply' || action === 'insert' ) {
|
|
return new OO.ui.Process( function () {
|
|
var surfaceModel = this.getFragment().getSurface(),
|
|
obj = this.transclusionModel.getPlainObject();
|
|
|
|
if ( this.selectedNode instanceof ve.dm.MWTransclusionNode ) {
|
|
this.transclusionModel.updateTransclusionNode( surfaceModel, this.selectedNode );
|
|
} else if ( obj !== null ) {
|
|
// Collapse returns a new fragment, so update this.fragment
|
|
this.fragment = this.getFragment().collapseRangeToEnd();
|
|
this.transclusionModel.insertTransclusionNode( this.getFragment() );
|
|
}
|
|
|
|
this.close( { action: action } );
|
|
}, this );
|
|
}
|
|
|
|
return ve.ui.MWTemplateDialog.super.prototype.getActionProcess.call( this, action );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getSetupProcess = function ( data ) {
|
|
data = data || {};
|
|
return ve.ui.MWTemplateDialog.super.prototype.getSetupProcess.call( this, data )
|
|
.next( function () {
|
|
var template, promise;
|
|
|
|
// Properties
|
|
this.loaded = false;
|
|
this.transclusionModel = new ve.dm.MWTransclusionModel();
|
|
|
|
// Events
|
|
this.transclusionModel.connect( this, { replace: 'onReplacePart' } );
|
|
|
|
// Initialization
|
|
if ( !this.selectedNode ) {
|
|
this.actions.setMode( 'insert' );
|
|
if ( data.template ) {
|
|
// New specified template
|
|
template = ve.dm.MWTemplateModel.newFromName(
|
|
this.transclusionModel, data.template
|
|
);
|
|
promise = this.transclusionModel.addPart( template ).done( function () {
|
|
template.addPromptedParameters();
|
|
} );
|
|
} else {
|
|
// New template placeholder
|
|
promise = this.transclusionModel.addPart(
|
|
new ve.dm.MWTemplatePlaceholderModel( this.transclusionModel )
|
|
);
|
|
}
|
|
} else {
|
|
this.actions.setMode( 'edit' );
|
|
// Load existing template
|
|
promise = this.transclusionModel
|
|
.load( ve.copy( this.selectedNode.getAttribute( 'mw' ) ) );
|
|
}
|
|
this.actions.setAbilities( { apply: false, insert: false } );
|
|
this.pushPending();
|
|
promise.always( this.onTransclusionReady.bind( this ) );
|
|
}, this );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWTemplateDialog.prototype.getTeardownProcess = function ( data ) {
|
|
return ve.ui.MWTemplateDialog.super.prototype.getTeardownProcess.call( this, data )
|
|
.first( function () {
|
|
// Cleanup
|
|
this.$element.removeClass( 've-ui-mwTemplateDialog-ready' );
|
|
this.transclusionModel.disconnect( this );
|
|
this.transclusionModel.abortRequests();
|
|
this.transclusionModel = null;
|
|
this.bookletLayout.clearPages();
|
|
this.content = null;
|
|
}, this );
|
|
};
|