/*! * VisualEditor user interface MWTransclusionDialog class. * * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /*global mw */ /** * Dialog for a MediaWiki content transclusion. * * See https://raw.github.com/wikimedia/mediawiki-extensions-TemplateData/master/spec.templatedata.json * for the latest version of the TemplateData specification. * * @class * @extends ve.ui.PagedDialog * * @constructor * @param {ve.ui.Surface} surface * @param {Object} [config] Config options */ ve.ui.MWTransclusionDialog = function VeUiMWTransclusionDialog( surface, config ) { // Configuration initialization config = ve.extendObject( {}, config, { 'editable': true, 'adders': [ { 'name': 'template', 'icon': 'template', 'title': ve.msg( 'visualeditor-dialog-transclusion-add-template' ) }, { 'name': 'content', 'icon': 'source', 'title': ve.msg( 'visualeditor-dialog-transclusion-add-content' ) } ] } ); // Parent constructor ve.ui.PagedDialog.call( this, surface, config ); // Properties this.node = null; this.transclusion = null; }; /* Inheritance */ ve.inheritClass( ve.ui.MWTransclusionDialog, ve.ui.PagedDialog ); /* Static Properties */ ve.ui.MWTransclusionDialog.static.titleMessage = 'visualeditor-dialog-transclusion-title'; ve.ui.MWTransclusionDialog.static.icon = 'template'; ve.ui.MWTransclusionDialog.static.modelClasses = [ ve.dm.MWTransclusionNode ]; /* Methods */ ve.ui.MWTransclusionDialog.prototype.initialize = function () { // Parent method ve.ui.PagedDialog.prototype.initialize.call( this ); // Events this.outlineControlsWidget.connect( this, { 'move': 'onOutlineControlsMove', 'add': 'onOutlineControlsAdd' } ); }; ve.ui.MWTransclusionDialog.prototype.onOpen = function () { // Parent method ve.ui.PagedDialog.prototype.onOpen.call( this ); // Sanity check this.node = this.surface.getView().getFocusedNode(); // Properties this.transclusion = new ve.dm.MWTransclusionModel(); // Initialization if ( this.node instanceof ve.ce.MWTransclusionNode ) { this.transclusion.load( ve.copyObject( this.node.getModel().getAttribute( 'mw' ) ) ) .always( ve.bind( this.setupPages, this ) ); } else { this.transclusion.addPlaceholder(); this.setupPages(); } }; ve.ui.MWTransclusionDialog.prototype.onClose = function ( action ) { var surfaceModel = this.surface.getModel(), obj = this.transclusion.getPlainObject(); // Parent method ve.ui.PagedDialog.prototype.onClose.call( this ); // Save changes if ( action === 'apply' ) { if ( this.node instanceof ve.ce.MWTransclusionNode ) { if ( obj !== null ) { surfaceModel.getFragment().changeAttributes( { 'mw': obj } ); } else { surfaceModel.getFragment().removeContent(); } } else if ( obj !== null ) { surfaceModel.getFragment().collapseRangeToEnd().insertContent( [ { 'type': 'mwTransclusionInline', 'attributes': { 'mw': obj } }, { 'type': '/mwTransclusionInline' } ] ); } } this.clearPages(); this.node = null; this.content = null; }; /** * Handle add part events. * * @method * @param {ve.dm.MWTransclusionPartModel} part Added part */ ve.ui.MWTransclusionDialog.prototype.onAddPart = function ( part ) { var i, len, page, params, param, names; if ( part instanceof ve.dm.MWTemplateModel ) { page = this.getTemplatePage( part ); } else if ( part instanceof ve.dm.MWTransclusionContentModel ) { page = this.getContentPage( part ); } else if ( part instanceof ve.dm.MWTemplatePlaceholderModel ) { page = this.getPlaceholderPage( part ); } if ( page ) { page.index = this.getPageIndex( part ); this.addPage( part.getId(), page ); if ( part instanceof ve.dm.MWTemplateModel ) { names = part.getParameterNames(); params = part.getParameters(); for ( i = 0, len = names.length; i < len; i++ ) { param = params[names[i]]; page = this.getParameterPage( param ); page.index = this.getPageIndex( param ); this.addPage( param.getId(), page ); } part.connect( this, { 'add': 'onAddParameter', 'remove': 'onRemoveParameter' } ); } } }; /** * Handle remove part events. * * @method * @param {ve.dm.MWTransclusionPartModel} part Removed part */ ve.ui.MWTransclusionDialog.prototype.onRemovePart = function ( part ) { var name, params; if ( part instanceof ve.dm.MWTemplateModel ) { params = part.getParameters(); for ( name in params ) { this.removePage( params[name].getId() ); } part.disconnect( this ); } this.removePage( part.getId() ); }; /** * Handle add param events. * * @method * @param {ve.dm.MWTemplateParameterModel} param Added param */ ve.ui.MWTransclusionDialog.prototype.onAddParameter = function ( param ) { var page = this.getParameterPage( param ); page.index = this.getPageIndex( param ); this.addPage( param.getId(), page ); }; /** * Handle remove param events. * * @method * @param {ve.dm.MWTemplateParameterModel} param Removed param */ ve.ui.MWTransclusionDialog.prototype.onRemoveParameter = function ( param ) { this.removePage( param.getId() ); // Return to template page this.setPageByName( param.getTemplate().getId() ); }; /** * Handle outline controls move events. * * @method * @param {number} places Number of places to move the selected item */ ve.ui.MWTransclusionDialog.prototype.onOutlineControlsMove = function ( places ) { var part, index, name, parts = this.transclusion.getParts(), item = this.outlineWidget.getSelectedItem(); if ( item ) { name = item.getData(); part = this.transclusion.getPartFromId( name ); index = ve.indexOf( part, parts ); this.transclusion.removePart( part ); this.transclusion.addPart( part, index + places ); this.setPageByName( name ); } }; /** * Handle outline controls add events. * * @method * @param {string} type Type of item to add */ ve.ui.MWTransclusionDialog.prototype.onOutlineControlsAdd = function ( type ) { var part; switch ( type ) { case 'content': part = this.transclusion.addContent( '', this.getPartInsertionIndex() ); this.setPageByName( part.getId() ); break; case 'template': part = this.transclusion.addPlaceholder( this.getPartInsertionIndex() ); this.setPageByName( part.getId() ); break; } }; /** * Get an index for part insertion. * * @method * @return {number} Index to insert new parts at */ ve.ui.MWTransclusionDialog.prototype.getPartInsertionIndex = function () { var parts = this.transclusion.getParts(), item = this.outlineWidget.getSelectedItem(); if ( item ) { return ve.indexOf( this.transclusion.getPartFromId( item.getData() ), parts ) + 1; } return parts.length; }; /** * Set the page by name. * * Page names are always the ID of the part or param they represent. * * @method * @param {string} name Page name */ ve.ui.MWTransclusionDialog.prototype.setPageByName = function ( name ) { this.outlineWidget.selectItem( this.outlineWidget.getItemFromData( name ) ); }; /** * Get the page index of an item. * * @method * @param {ve.dm.MWTransclusionPartModel|ve.dm.MWTemplateParameterModel} item Part or parameter * @returns {number} Page index of item */ ve.ui.MWTransclusionDialog.prototype.getPageIndex = function ( item ) { // Build pages from parts var i, iLen, j, jLen, part, names, parts = this.transclusion.getParts(), index = 0; // Populate pages for ( i = 0, iLen = parts.length; i < iLen; i++ ) { part = parts[i]; if ( part === item ) { return index; } index++; if ( part instanceof ve.dm.MWTemplateModel ) { names = part.getParameterNames(); for ( j = 0, jLen = names.length; j < jLen; j++ ) { if ( part.getParameter( names[j] ) === item ) { return index; } index++; } } } return -1; }; /** * Synchronize pages with transclusion. * * @method */ ve.ui.MWTransclusionDialog.prototype.setupPages = function () { // Build pages from parts var i, iLen, j, jLen, part, param, names, parts = this.transclusion.getParts(); // Populate pages for ( i = 0, iLen = parts.length; i < iLen; i++ ) { part = parts[i]; if ( part instanceof ve.dm.MWTemplateModel ) { // Add template page this.addPage( part.getId(), this.getTemplatePage( part ) ); // Listen for changes to parameters part.connect( this, { 'add': 'onAddParameter', 'remove': 'onRemoveParameter' } ); // Add parameter pages names = part.getParameterNames(); for ( j = 0, jLen = names.length; j < jLen; j++ ) { param = part.getParameter( names[j] ); this.addPage( param.getId(), this.getParameterPage( param ) ); } } else if ( part instanceof ve.dm.MWTransclusionContentModel ) { // Add wikitext page this.addPage( part.getId(), this.getContentPage( part ) ); } else if ( part instanceof ve.dm.MWTemplatePlaceholderModel ) { // Add template placeholder page this.addPage( part.getId(), this.getPlaceholderPage( part ) ); } } // Listen for changes to parts this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 'onRemovePart' } ); }; /** * Get page for transclusion content. * * @method * @param {ve.dm.MWTransclusionContentModel} content Content model */ ve.ui.MWTransclusionDialog.prototype.getContentPage = function ( content ) { var valueFieldset, textInput, optionsFieldset, removeButton; valueFieldset = new ve.ui.FieldsetLayout( { '$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-transclusion-content' ), 'icon': 'source' } ); textInput = new ve.ui.TextInputWidget( { '$$': this.frame.$$, 'multiline': true } ); textInput.setValue( content.getValue() ); textInput.connect( this, { 'change': function () { content.setValue( textInput.getValue() ); } } ); textInput.$.addClass( 've-ui-mwTransclusionDialog-input' ); valueFieldset.$.append( textInput.$ ); optionsFieldset = new ve.ui.FieldsetLayout( { '$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-transclusion-options' ), 'icon': 'settings' } ); removeButton = new ve.ui.ButtonWidget( { '$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-transclusion-remove-content' ), 'flags': ['destructive'] } ); removeButton.connect( this, { 'click': function () { content.remove(); } } ); optionsFieldset.$.append( removeButton.$ ); return { 'label': ve.msg( 'visualeditor-dialog-transclusion-content' ), 'icon': 'source', '$content': valueFieldset.$.add( optionsFieldset.$ ), 'moveable': true }; }; /** * Get page for a template. * * @method * @param {ve.dm.MWTemplateModel} template Template model */ ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) { var infoFieldset, addParameterFieldset, addParameterInput, addParameterButton, optionsFieldset, removeButton, spec = template.getSpec(), label = spec.getLabel(), description = spec.getDescription(); function addParameter() { var param = template.addParameter( addParameterInput.getValue() ); addParameterInput.setValue(); this.setPageByName( param.getId() ); } infoFieldset = new ve.ui.FieldsetLayout( { '$$': this.frame.$$, 'label': label, 'icon': 'template' } ); if ( description ) { infoFieldset.$.append( $( '