Refactor Transclusion and Meta dialogs to use BookletLayout

Use OOJS-UI's newly-extended paged dialogs (in e08eb2a03b) to refactor
how the Transclusion and Meta dialogs work, splitting out the code for
each of the panels into its own file and simplifying extensibility.

The Meta dialog (ve.ui.MWMetaDialog) now has two self-managing panels:
* ve.ui.MWCategoriesPage for categories and the default sort key
* ve.ui.MWLanguagesPage for language links

The Transclusion dialog (ve.ui.MWTransclusionDialog) now has four:
* ve.ui.MWTemplatePage for a template's primary panel
* ve.ui.MWTemplateParameterPage for each parameter of a template
* ve.ui.MWTemplatePlaceholderPage for a placeholder to insert a template
* ve.ui.MWTransclusionContentPage for non-template transclusion

Additionally, the Transclusion dialog has been slightly cleaned up:
* Replace add/remove events with replace events in transclusion model
* Actually return and resolve a promise (as documented)
* Get rid of "origin" info in template models
* Add method for adding required parts

TODO:

* Decide how and when we will choose between advanced transclusion and
  template dialogs
* Work out design issues with how template descriptions will be visible
  and how adding parameters will work if only showing parameters in
  outline
* Add preview to template dialog
* Consider ways to further improve pages for use in continuous mode

WARNING:

* Right now the template dialog gets overridden by the advanced
  transclusion dialog because they have the same symbolic name and the
  latter is registered later than the former. To test the template
  dialog, just change the symbolic name of the advanced transclusion
  dialog.

Change-Id: I51e74b322aec9a4c3918e6f792bdb3d318060979
This commit is contained in:
Trevor Parscal 2013-12-02 12:10:55 -08:00 committed by Roan Kattouw
parent 49d108003d
commit 51e096d6f2
17 changed files with 1193 additions and 895 deletions

View file

@ -134,6 +134,10 @@
{
"name": "Dialogs",
"classes": ["ve.ui.*Dialog"]
},
{
"name": "Pages",
"classes": ["ve.ui.*Page"]
}
]
},

View file

@ -474,12 +474,21 @@ $wgResourceModules += array(
've-mw/ui/widgets/ve.ui.MWReferenceResultWidget.js',
've-mw/ui/widgets/ve.ui.MWTitleInputWidget.js',
've-mw/ui/pages/ve.ui.MWCategoriesPage.js',
've-mw/ui/pages/ve.ui.MWLanguagesPage.js',
've-mw/ui/pages/ve.ui.MWTemplatePage.js',
've-mw/ui/pages/ve.ui.MWTemplateParameterPage.js',
've-mw/ui/pages/ve.ui.MWTemplatePlaceholderPage.js',
've-mw/ui/pages/ve.ui.MWTransclusionContentPage.js',
've-mw/ui/dialogs/ve.ui.MWSaveDialog.js',
've-mw/ui/dialogs/ve.ui.MWMetaDialog.js',
've-mw/ui/dialogs/ve.ui.MWBetaWelcomeDialog.js',
've-mw/ui/dialogs/ve.ui.MWMediaInsertDialog.js',
've-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js',
've-mw/ui/dialogs/ve.ui.MWTransclusionDialog.js',
've-mw/ui/dialogs/ve.ui.MWTemplateDialog.js',
've-mw/ui/dialogs/ve.ui.MWAdvancedTransclusionDialog.js',
've-mw/ui/dialogs/ve.ui.MWReferenceListDialog.js',
've-mw/ui/dialogs/ve.ui.MWReferenceDialog.js',

View file

@ -18,15 +18,13 @@
* @param {Object} target Template target
* @param {string} target.wt Original wikitext of target
* @param {string} [target.href] Hypertext reference to target
* @param {string} [origin] Origin of part, e.g. 'data' or 'user'
*/
ve.dm.MWTemplateModel = function VeDmMWTemplateModel( transclusion, target, origin ) {
ve.dm.MWTemplateModel = function VeDmMWTemplateModel( transclusion, target ) {
// Parent constructor
ve.dm.MWTransclusionPartModel.call( this, transclusion );
// Properties
this.target = target;
this.origin = origin;
// TODO: Either here or in uses of this constructor we need to validate the title
this.title = ( target.href && target.href.replace( /^(\.\.?\/)*/, '' ) ) || null;
@ -106,26 +104,15 @@ ve.dm.MWTemplateModel.newFromName = function ( transclusion, name ) {
/**
* Get template target.
*
* @method
* @returns {Object} Template target
*/
ve.dm.MWTemplateModel.prototype.getTarget = function () {
return this.target;
};
/**
* Get template origin, e.g. 'user' or 'data'.
*
* @returns {string} Origin
*/
ve.dm.MWTemplateModel.prototype.getOrigin = function () {
return this.origin;
};
/**
* Get template title.
*
* @method
* @returns {string|null} Template title, if available
*/
ve.dm.MWTemplateModel.prototype.getTitle = function () {
@ -135,7 +122,6 @@ ve.dm.MWTemplateModel.prototype.getTitle = function () {
/**
* Get template specification.
*
* @method
* @returns {ve.dm.MWTemplateSpecModel} Template specification
*/
ve.dm.MWTemplateModel.prototype.getSpec = function () {
@ -145,7 +131,6 @@ ve.dm.MWTemplateModel.prototype.getSpec = function () {
/**
* Get all params.
*
* @method
* @returns {Object.<string,ve.dm.MWTemplateParameterModel>} Parameters keyed by name
*/
ve.dm.MWTemplateModel.prototype.getParameters = function () {
@ -155,7 +140,6 @@ ve.dm.MWTemplateModel.prototype.getParameters = function () {
/**
* Get a parameter.
*
* @method
* @param {string} name Parameter name
* @returns {ve.dm.MWTemplateParameterModel} Parameter
*/
@ -166,7 +150,6 @@ ve.dm.MWTemplateModel.prototype.getParameter = function ( name ) {
/**
* Check if a parameter exists.
*
* @method
* @param {string} name Parameter name
* @returns {boolean} Parameter exists
*/
@ -203,7 +186,6 @@ ve.dm.MWTemplateModel.prototype.hasParameter = function ( name ) {
* Numeric names, whether strings or real numbers, are placed at the begining, followed by
* alphabetically sorted names.
*
* @method
* @returns {string[]} List of parameter names
*/
ve.dm.MWTemplateModel.prototype.getParameterNames = function () {
@ -233,7 +215,6 @@ ve.dm.MWTemplateModel.prototype.getParameterNames = function () {
/**
* Add a parameter to template.
*
* @method
* @param {ve.dm.MWTemplateParameterModel} param Parameter to add
* @fires add
*/
@ -248,7 +229,6 @@ ve.dm.MWTemplateModel.prototype.addParameter = function ( param ) {
/**
* Remove parameter from template.
*
* @method
* @param {ve.dm.MWTemplateParameterModel} param Parameter to remove
* @fires remove
*/
@ -261,9 +241,25 @@ ve.dm.MWTemplateModel.prototype.removeParameter = function ( param ) {
};
/**
* Set original data, to be used as a base for serialization.
* Add all non-existing required parameters, if any.
*
* @method
*/
ve.dm.MWTemplateModel.prototype.addRequiredParameters = function () {
var i, len,
spec = this.getSpec(),
names = spec.getParameterNames();
for ( i = 0, len = names.length; i < len; i++ ) {
if ( !this.params[name] && spec.isParameterRequired( names[i] ) ) {
this.addParameter( new ve.dm.MWTemplateParameterModel( this, names[i] ) );
}
}
};
/**
* Set original data, to be used as a base for serialization.
*
* @returns {Object} Template data
*/
ve.dm.MWTemplateModel.prototype.setOriginalData = function ( data ) {

View file

@ -37,13 +37,9 @@ OO.mixinClass( ve.dm.MWTransclusionModel, OO.EventEmitter );
/* Events */
/**
* @event add
* @param {ve.dm.MWTransclusionPartModel} part Added part
*/
/**
* @event remove
* @param {ve.dm.MWTransclusionPartModel} part Removed part
* @event replace
* @param {ve.dm.MWTransclusionPartModel|null} removed Removed part
* @param {ve.dm.MWTransclusionPartModel|null} added Added part
*/
/* Methods */
@ -51,12 +47,12 @@ OO.mixinClass( ve.dm.MWTransclusionModel, OO.EventEmitter );
/**
* Load from transclusion data, and fetch spec from server.
*
* @method
* @param {Object} data Transclusion data
* @returns {jQuery.Promise} Promise, resolved when spec is loaded
*/
ve.dm.MWTransclusionModel.prototype.load = function ( data ) {
var i, len, part;
var i, len, part, deferred,
promises = [];
// Convert single part format to multi-part format
// Parsoid doesn't use this format any more, but we accept it for backwards compatibility
@ -68,48 +64,66 @@ ve.dm.MWTransclusionModel.prototype.load = function ( data ) {
for ( i = 0, len = data.parts.length; i < len; i++ ) {
part = data.parts[i];
if ( part.template ) {
this.queue.push(
{ 'part': ve.dm.MWTemplateModel.newFromData( this, part.template ) }
);
deferred = $.Deferred();
promises.push( deferred.promise() );
this.queue.push( {
'add': ve.dm.MWTemplateModel.newFromData( this, part.template ),
'deferred': deferred
} );
} else if ( typeof part === 'string' ) {
this.queue.push(
{ 'part': new ve.dm.MWTransclusionContentModel( this, part, 'data' ) }
);
deferred = $.Deferred();
promises.push( deferred.promise() );
this.queue.push( {
'add': new ve.dm.MWTransclusionContentModel( this, part, 'data' ),
'deferred': deferred
} );
}
}
setTimeout( ve.bind( this.fetch, this ) );
}
return $.when.apply( $, promises );
};
/**
* Process one or more queue items.
*
* @method
* @param {Object[]} queue List of objects containing parts to add and optionally indexes to add
* them at, if no index is given parts will be added at the end
* @fires add For each item added
* @fires replace For each item added
*/
ve.dm.MWTransclusionModel.prototype.process = function ( queue ) {
var i, len, item, title, index;
var i, len, item, title, index,
remove = 0;
for ( i = 0, len = queue.length; i < len; i++ ) {
item = queue[i];
if ( item.part instanceof ve.dm.MWTemplateModel ) {
title = item.part.getTitle();
if ( item.add instanceof ve.dm.MWTemplateModel ) {
title = item.add.getTitle();
if ( hasOwn.call( specCache, title ) && specCache[title] ) {
item.part.getSpec().extend( specCache[title] );
item.add.getSpec().extend( specCache[title] );
}
}
// Auto-remove if already existing
index = ve.indexOf( item.part, this.parts );
index = ve.indexOf( item.add, this.parts );
if ( index !== -1 ) {
this.parts.splice( index, 1 );
this.emit( 'remove', item.part );
this.emit( 'replace', item.add, null );
}
// Add at index, or end if none was given
index = item.index === undefined ? this.parts.length : item.index;
this.parts.splice( index, 0, item.part );
this.emit( 'add', item.part );
if ( item.index !== undefined ) {
index = item.index;
} else if ( item.remove ) {
index = ve.indexOf( item.remove, this.parts );
if ( index !== -1 ) {
remove = 1;
}
}
if ( index === undefined || index === -1 ) {
index = this.parts.length;
}
this.parts.splice( index, remove, item.add );
this.emit( 'replace', item.remove || null, item.add );
// Resolve promises
if ( item.deferred ) {
item.deferred.resolve();
@ -134,8 +148,8 @@ ve.dm.MWTransclusionModel.prototype.fetch = function () {
// Get unique list of template titles that aren't already loaded
for ( i = 0, len = queue.length; i < len; i++ ) {
item = queue[i];
if ( item.part instanceof ve.dm.MWTemplateModel ) {
title = item.part.getTitle();
if ( item.add instanceof ve.dm.MWTemplateModel ) {
title = item.add.getTitle();
if (
// Skip titles that don't have a resolvable href
title &&
@ -227,7 +241,6 @@ ve.dm.MWTransclusionModel.prototype.abortRequests = function () {
/**
* Get plain object representation of template transclusion.
*
* @method
* @returns {Object|null} Plain object representation, or null if empty
*/
ve.dm.MWTransclusionModel.prototype.getPlainObject = function () {
@ -254,19 +267,45 @@ ve.dm.MWTransclusionModel.prototype.getPlainObject = function () {
*
* This is used to give parts unique IDs, and returns a different value each time it's called.
*
* @method
* @returns {number} Unique ID
*/
ve.dm.MWTransclusionModel.prototype.getUniquePartId = function () {
return this.uid++;
};
/**
* Replace part.
*
* Replace asynchonously.
*
* @param {ve.dm.MWTransclusionPartModel} remove Part to remove
* @param {ve.dm.MWTransclusionPartModel} add Part to add
* @throws {Error} If part to remove is not valid
* @throws {Error} If part to add is not valid
* @returns {jQuery.Promise} Promise, resolved when part is added
*/
ve.dm.MWTransclusionModel.prototype.replacePart = function ( remove, add ) {
var deferred = $.Deferred();
if (
!( remove instanceof ve.dm.MWTransclusionPartModel ) ||
!( add instanceof ve.dm.MWTransclusionPartModel )
) {
throw new Error( 'Invalid transclusion part' );
}
this.queue.push( { 'remove': remove, 'add': add, 'deferred': deferred } );
// Fetch on next yield to process items in the queue together, subsequent calls to fetch will
// have no effect because the queue will be clear
setTimeout( ve.bind( this.fetch, this ) );
return deferred.promise();
};
/**
* Add part.
*
* Added asynchronously, but order is preserved.
*
* @method
* @param {ve.dm.MWTransclusionPartModel} part Part to add
* @param {number} [index] Specific index to add content at, defaults to the end
* @throws {Error} If part is not valid
@ -277,7 +316,7 @@ ve.dm.MWTransclusionModel.prototype.addPart = function ( part, index ) {
if ( !( part instanceof ve.dm.MWTransclusionPartModel ) ) {
throw new Error( 'Invalid transclusion part' );
}
this.queue.push( { 'part': part, 'index': index, 'deferred': deferred } );
this.queue.push( { 'add': part, 'index': index, 'deferred': deferred } );
// Fetch on next yield to process items in the queue together, subsequent calls to fetch will
// have no effect because the queue will be clear
@ -289,22 +328,20 @@ ve.dm.MWTransclusionModel.prototype.addPart = function ( part, index ) {
/**
* Remove a part.
*
* @method
* @param {ve.dm.MWTransclusionPartModel} part Part to remove
* @fires remove
* @fires replace
*/
ve.dm.MWTransclusionModel.prototype.removePart = function ( part ) {
var index = ve.indexOf( part, this.parts );
if ( index !== -1 ) {
this.parts.splice( index, 1 );
this.emit( 'remove', part );
this.emit( 'replace', part, null );
}
};
/**
* Get all parts.
*
* @method
* @returns {ve.dm.MWTransclusionPartModel[]} Parts in transclusion
*/
ve.dm.MWTransclusionModel.prototype.getParts = function () {
@ -316,7 +353,6 @@ ve.dm.MWTransclusionModel.prototype.getParts = function () {
*
* Matching is performed against the first section of the `id`, delimited by a '/'.
*
* @method
* @param {string} id Part ID
* @returns {ve.dm.MWTransclusionPartModel|null} Part with matching ID, if found
*/
@ -334,4 +370,36 @@ ve.dm.MWTransclusionModel.prototype.getPartFromId = function ( id ) {
return null;
};
/**
* Get the index of a part or parameter.
*
* Indexes are linear depth-first addresses in the transclusion tree.
*
* @param {ve.dm.MWTransclusionPartModel|ve.dm.MWTemplateParameterModel} model Part or parameter
* @returns {number} Page index of model
*/
ve.dm.MWTransclusionModel.prototype.getIndex = function ( model ) {
var i, iLen, j, jLen, part, names,
parts = this.parts,
index = 0;
for ( i = 0, iLen = parts.length; i < iLen; i++ ) {
part = parts[i];
if ( part === model ) {
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] ) === model ) {
return index;
}
index++;
}
}
}
return -1;
};
}() );

View file

@ -0,0 +1,131 @@
/*
* VisualEditor user interface MWAdvancedTransclusionDialog class.
*
* @copyright 2011-2013 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.MWTransclusionDialog
*
* @constructor
* @param {ve.ui.WindowSet} windowSet Window set this dialog is part of
* @param {Object} [config] Configuration options
*/
ve.ui.MWAdvancedTransclusionDialog = function VeUiMWAdvancedTransclusionDialog( windowSet, config ) {
// Parent constructor
ve.ui.MWTransclusionDialog.call( this, windowSet, config );
// Properties
this.node = null;
this.transclusion = null;
this.loaded = false;
};
/* Inheritance */
OO.inheritClass( ve.ui.MWAdvancedTransclusionDialog, ve.ui.MWTransclusionDialog );
/* Static Properties */
ve.ui.MWAdvancedTransclusionDialog.static.name = 'transclusion';
ve.ui.MWAdvancedTransclusionDialog.static.titleMessage = 'visualeditor-dialog-transclusion-title';
ve.ui.MWAdvancedTransclusionDialog.static.icon = 'template';
/* Methods */
/**
* Handle outline controls move events.
*
* @param {number} places Number of places to move the selected item
*/
ve.ui.MWAdvancedTransclusionDialog.prototype.onOutlineControlsMove = function ( places ) {
var part, index, name, promise,
parts = this.transclusion.getParts(),
item = this.bookletLayout.getOutline().getSelectedItem();
if ( item ) {
name = item.getData();
part = this.transclusion.getPartFromId( name );
index = ve.indexOf( part, parts );
// Auto-removes part from old location
promise = this.transclusion.addPart( part, index + places );
if ( this.loaded ) {
promise.done( ve.bind( this.setPageByName, this, part.getId() ) );
}
}
};
/**
* Handle outline controls add events.
*
* @param {string} type Type of item to add
*/
ve.ui.MWAdvancedTransclusionDialog.prototype.onOutlineControlsAdd = function ( type ) {
var part, parts, item, index, promise;
if ( type === 'content' ) {
part = new ve.dm.MWTransclusionContentModel( this.transclusion, '', 'user' );
} else if ( type === 'template' ) {
part = new ve.dm.MWTemplatePlaceholderModel( this.transclusion, 'user' );
}
if ( part ) {
parts = this.transclusion.getParts();
item = this.bookletLayout.getOutline().getSelectedItem();
index = item ?
ve.indexOf( this.transclusion.getPartFromId( item.getData() ), parts ) + 1 :
parts.length;
promise = this.transclusion.addPart( part, index );
if ( this.loaded ) {
promise.done( ve.bind( this.setPageByName, this, part.getId() ) );
}
}
};
/**
* @inheritdoc
*/
ve.ui.MWAdvancedTransclusionDialog.prototype.getBookletLayout = function () {
return new OO.ui.BookletLayout( {
'$': this.$,
'continuous': true,
'autoFocus': true,
'outlined': true,
'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' )
}
]
} );
};
/**
* @inheritdoc
*/
ve.ui.MWAdvancedTransclusionDialog.prototype.initialize = function () {
// Parent method
ve.ui.MWTransclusionDialog.prototype.initialize.call( this );
// Events
this.bookletLayout.getOutlineControls().connect( this, {
'move': 'onOutlineControlsMove',
'add': 'onOutlineControlsAdd'
} );
};
/* Registration */
ve.ui.dialogFactory.register( ve.ui.MWAdvancedTransclusionDialog );

View file

@ -5,8 +5,6 @@
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw*/
/**
* Dialog for editing MediaWiki page meta information.
*
@ -20,17 +18,6 @@
ve.ui.MWMetaDialog = function VeUiMWMetaDialog( windowSet, config ) {
// Parent constructor
ve.ui.MWDialog.call( this, windowSet, config );
// Properties
this.metaList = this.surface.getModel().metaList;
this.defaultSortKeyTouched = false;
this.fallbackDefaultSortKey = mw.config.get( 'wgTitle' );
// Events
this.metaList.connect( this, {
'insert': 'onMetaListInsert',
'remove': 'onMetaListRemove'
} );
};
/* Inheritance */
@ -47,341 +34,32 @@ ve.ui.MWMetaDialog.static.icon = 'settings';
/* Methods */
/**
* Handle language items being loaded.
*/
ve.ui.MWMetaDialog.prototype.onAllLanuageItemsSuccess = function ( deferred, response ) {
var i, iLen, languages = [], langlinks = response.query.pages[response.query.pageids[0]].langlinks;
if ( langlinks ) {
for ( i = 0, iLen = langlinks.length; i < iLen; i++ ) {
languages.push( {
'lang': langlinks[i].lang,
'title': langlinks[i]['*'],
'metaItem': null
} );
}
}
deferred.resolve( languages );
};
/**
* Handle language items failing to be loaded.
*
* TODO: This error function should probably not be empty.
*/
ve.ui.MWMetaDialog.prototype.onAllLanuageItemsError = function () {};
/**
* Handle category default sort change events.
*
* @param {string} value Default sort value
*/
ve.ui.MWMetaDialog.prototype.onDefaultSortChange = function ( value ) {
this.categoryWidget.setDefaultSortKey( value === '' ? this.fallbackDefaultSortKey : value );
this.defaultSortKeyTouched = true;
};
/**
* Inserts new category into meta list
*
* @param {Object} item
*/
ve.ui.MWMetaDialog.prototype.onNewCategory = function ( item ) {
// Insert new metaList item
this.insertMetaListItem( this.getCategoryItemForInsertion( item ) );
};
/**
* Removes and re-inserts updated category widget item
*
* @param {Object} item
*/
ve.ui.MWMetaDialog.prototype.onUpdateSortKey = function ( item ) {
// Replace meta item with updated one
item.metaItem.replaceWith( this.getCategoryItemForInsertion( item, item.metaItem.getElement() ) );
};
/**
* Bound to MetaList insert event for adding meta dialog components.
*
* @param {ve.dm.MetaItem} metaItem
*/
ve.ui.MWMetaDialog.prototype.onMetaListInsert = function ( metaItem ) {
// Responsible for adding UI components
if ( metaItem.element.type === 'mwCategory' ) {
this.categoryWidget.addItems(
[ this.getCategoryItemFromMetaListItem( metaItem ) ],
this.metaList.findItem( metaItem.getOffset(), metaItem.getIndex(), 'mwCategory' )
);
}
};
/**
* Bound to MetaList insert event for removing meta dialog components.
*
* @param {ve.dm.MetaItem} metaItem
*/
ve.ui.MWMetaDialog.prototype.onMetaListRemove = function ( metaItem ) {
var item;
if ( metaItem.element.type === 'mwCategory' ) {
item = this.getCategoryItemFromMetaListItem( metaItem );
this.categoryWidget.removeItems( [item.value] );
}
};
/**
* Get default sort key item.
*
* @returns {string} Default sort key item
*/
ve.ui.MWMetaDialog.prototype.getDefaultSortKeyItem = function () {
var items = this.metaList.getItemsInGroup( 'mwDefaultSort' );
return items.length ? items[0] : null;
};
/**
* Get array of category items from meta list
*
* @returns {Object[]} items
*/
ve.ui.MWMetaDialog.prototype.getCategoryItems = function () {
var i,
items = [],
categories = this.metaList.getItemsInGroup( 'mwCategory' );
// Loop through MwCategories and build out items
for ( i = 0; i < categories.length; i++ ) {
items.push( this.getCategoryItemFromMetaListItem( categories[i] ) );
}
return items;
};
/**
* Gets category item from meta list item
*
* @param {ve.dm.MWCategoryMetaItem} metaItem
* @returns {Object} item
*/
ve.ui.MWMetaDialog.prototype.getCategoryItemFromMetaListItem = function ( metaItem ) {
var title = mw.Title.newFromText( metaItem.element.attributes.category ),
value = title ? title.getMainText() : '';
return {
'name': metaItem.element.attributes.category,
'value': value,
// TODO: sortkey is lcase, make consistent throughout CategoryWidget
'sortKey': metaItem.element.attributes.sortkey,
'metaItem': metaItem
};
};
/**
* Get metaList like object to insert from item
*
* @param {Object} item category widget item
* @param {Object} [oldData] Metadata object that was previously associated with this item, if any
* @returns {Object} metaBase
*/
ve.ui.MWMetaDialog.prototype.getCategoryItemForInsertion = function ( item, oldData ) {
var newData = {
'attributes': { 'category': item.name, 'sortkey': item.sortKey || '' },
'type': 'mwCategory'
};
if ( oldData ) {
return ve.extendObject( {}, oldData, newData );
}
return newData;
};
/**
* Gets language item from meta list item
*
* @param {ve.dm.MWLanguageMetaItem} metaItem
* @returns {Object} item
*/
ve.ui.MWMetaDialog.prototype.getLanguageItemFromMetaListItem = function ( metaItem ) {
// TODO: get real values from metaItem once Parsoid actually provides them - bug 48970
return {
'lang': 'lang',
'title': 'title',
'metaItem': metaItem
};
};
/**
* Get array of language items from meta list
*
* @returns {Object[]} items
*/
ve.ui.MWMetaDialog.prototype.getLocalLanguageItems = function () {
var i,
items = [],
languages = this.metaList.getItemsInGroup( 'mwLanguage' ),
languageslength = languages.length;
// Loop through MWLanguages and build out items
for ( i = 0; i < languageslength; i++ ) {
items.push( this.getLanguageItemFromMetaListItem( languages[i] ) );
}
return items;
};
/**
* Get array of language items from meta list
*
* @returns {jQuery.Promise}
*/
ve.ui.MWMetaDialog.prototype.getAllLanguageItems = function () {
var deferred = $.Deferred();
// TODO: Detect paging token if results exceed limit
$.ajax( {
'url': mw.util.wikiScript( 'api' ),
'data': {
'action': 'query',
'prop': 'langlinks',
'lllimit': 500,
'titles': mw.config.get( 'wgTitle' ),
'indexpageids': 1,
'format': 'json'
},
'dataType': 'json',
'type': 'POST',
// Wait up to 100 seconds before giving up
'timeout': 100000,
'cache': 'false'
} )
.done( ve.bind( this.onAllLanuageItemsSuccess, this, deferred ) )
.fail( ve.bind( this.onAllLanuageItemsError, this, deferred ) );
return deferred.promise();
};
/**
* Inserts a meta list item
*
* @param {Object} metaBase meta list insert object
*/
ve.ui.MWMetaDialog.prototype.insertMetaListItem = function ( metaBase ) {
this.metaList.insertMeta( metaBase );
};
/**
* @inheritdoc
*/
ve.ui.MWMetaDialog.prototype.initialize = function () {
var languagePromise;
// Parent method
ve.ui.MWDialog.prototype.initialize.call( this );
// Properties
this.pagedOutlineLayout = new OO.ui.PagedOutlineLayout( { '$': this.$ } );
this.categoriesFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-categories-data-label' ),
'icon': 'tag'
} );
this.categoryOptionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-categories-options' ),
'icon': 'settings'
} );
this.categoryWidget = new ve.ui.MWCategoryWidget( {
'$': this.$, '$overlay': this.$overlay
} );
this.defaultSortInput = new OO.ui.TextInputWidget( {
'$': this.$, 'placeholder': this.fallbackDefaultSortKey
} );
this.defaultSortLabel = new OO.ui.InputLabelWidget( {
'$': this.$,
'input': this.defaultSortInput,
'label': ve.msg( 'visualeditor-dialog-meta-categories-defaultsort-label' )
} );
this.languagesFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ),
'icon': 'language'
} );
this.bookletLayout = new OO.ui.BookletLayout( { '$': this.$, 'outlined': true } );
this.applyButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-action-apply' ),
'flags': ['primary']
} );
this.categoriesPage = new ve.ui.MWCategoriesPage( this.surface, 'categories', {
'$': this.$, '$overlay': this.$overlay
} );
this.languagesPage = new ve.ui.MWLanguagesPage( 'languages', { '$': this.$ } );
// Events
this.categoryWidget.connect( this, {
'newCategory': 'onNewCategory',
'updateSortkey': 'onUpdateSortKey'
} );
this.defaultSortInput.connect( this, {
'change': 'onDefaultSortChange'
} );
this.applyButton.connect( this, { 'click': [ 'close', { 'action': 'apply' } ] } );
// Initialization
this.categoryWidget.addItems( this.getCategoryItems() );
this.$body.append( this.pagedOutlineLayout.$element );
this.$body.append( this.bookletLayout.$element );
this.$foot.append( this.applyButton.$element );
this.pagedOutlineLayout.addPage( 'categories', {
'$content': [ this.categoriesFieldset.$element, this.categoryOptionsFieldset.$element ],
'label': ve.msg( 'visualeditor-dialog-meta-categories-section' ),
'icon': 'tag'
} ).addPage( 'languages', {
'$content': this.languagesFieldset.$element,
'label': ve.msg( 'visualeditor-dialog-meta-languages-section' ),
'icon': 'language'
} );
this.categoriesFieldset.$element.append( this.categoryWidget.$element );
this.categoryOptionsFieldset.$element.append(
this.defaultSortLabel.$element,
this.defaultSortInput.$element
);
this.languagesFieldset.$element.append(
this.$( '<span>' )
.text( ve.msg( 'visualeditor-dialog-meta-languages-readonlynote' ) )
);
languagePromise = this.getAllLanguageItems();
languagePromise.done( ve.bind( function ( languages ) {
var i, $languagesTable = this.$( '<table>' ), languageslength = languages.length;
$languagesTable
.addClass( 've-ui-mwMetaDialog-languages-table' )
.append( this.$( '<tr>' )
.append(
this.$( '<th>' )
.append( ve.msg( 'visualeditor-dialog-meta-languages-code-label' ) )
)
.append(
this.$( '<th>' )
.append( ve.msg( 'visualeditor-dialog-meta-languages-link-label' ) )
)
);
for ( i = 0; i < languageslength; i++ ) {
languages[i].safelang = languages[i].lang;
languages[i].dir = 'auto';
if ( $.uls ) {
// site codes don't always represent official language codes
// using real language code instead of a dummy ('redirect' in ULS' terminology)
languages[i].safelang = $.uls.data.isRedirect( languages[i].lang ) || languages[i].lang;
languages[i].dir = $.uls.data.getDir( languages[i].safelang );
}
$languagesTable
.append( this.$( '<tr>' )
.append( this.$( '<td>' ).append( languages[i].lang ) )
.append( this.$( '<td>' ).append( languages[i].title )
.attr( 'lang', languages[i].safelang )
.attr( 'dir', languages[i].dir ) )
);
}
this.languagesFieldset.$element.append( $languagesTable );
}, this ) );
this.bookletLayout.addPages( [ this.categoriesPage, this.languagesPage ] );
};
/**
@ -394,49 +72,25 @@ ve.ui.MWMetaDialog.prototype.setup = function ( data ) {
// Data initialization
data = data || {};
var surfaceModel = this.surface.getModel(),
categoryWidget = this.categoryWidget,
defaultSortKeyItem = this.getDefaultSortKeyItem();
var surfaceModel = this.surface.getModel();
if ( data.page && this.pagedOutlineLayout.getPage( data.page ) ) {
this.pagedOutlineLayout.setPage( data.page );
if ( data.page && this.bookletLayout.getPage( data.page ) ) {
this.bookletLayout.setPage( data.page );
}
this.defaultSortInput.setValue(
defaultSortKeyItem ? defaultSortKeyItem.getAttribute( 'content' ) : ''
);
this.defaultSortKeyTouched = false;
// Force all previous transactions to be separate from this history state
surfaceModel.breakpoint();
surfaceModel.stopHistoryTracking();
// Update input position once visible
setTimeout( function () {
categoryWidget.fitInput();
} );
this.categoriesPage.setup( data );
};
/**
* @inheritdoc
*/
ve.ui.MWMetaDialog.prototype.teardown = function ( data ) {
// Data initialization
data = data || {};
var hasTransactions,
surfaceModel = this.surface.getModel(),
// Category sort key items
currentDefaultSortKeyItem = this.getDefaultSortKeyItem(),
newDefaultSortKey = this.defaultSortInput.getValue(),
newDefaultSortKeyData = {
'type': 'mwDefaultSort',
'attributes': { 'content': newDefaultSortKey }
};
// Place transactions made while dialog was open in a common history state
hasTransactions = surfaceModel.breakpoint();
var surfaceModel = this.surface.getModel(),
hasTransactions = surfaceModel.breakpoint();
// Undo everything done in the dialog and prevent redoing those changes
if ( data.action === 'cancel' && hasTransactions ) {
@ -444,28 +98,10 @@ ve.ui.MWMetaDialog.prototype.teardown = function ( data ) {
surfaceModel.truncateUndoStack();
}
// Alter the default sort key iff it's been touched & is actually different
if ( this.defaultSortKeyTouched ) {
if ( newDefaultSortKey === '' ) {
if ( currentDefaultSortKeyItem ) {
currentDefaultSortKeyItem.remove();
}
} else {
if ( !currentDefaultSortKeyItem ) {
this.metaList.insertMeta( newDefaultSortKeyData );
} else if ( currentDefaultSortKeyItem.getValue() !== newDefaultSortKey ) {
currentDefaultSortKeyItem.replaceWith(
ve.extendObject( true, {},
currentDefaultSortKeyItem.getElement(),
newDefaultSortKeyData
)
);
}
}
}
this.categoriesPage.teardown( data );
// Return to normal tracking behavior
surfaceModel.startHistoryTracking();
this.surface.getModel().startHistoryTracking();
// Parent method
ve.ui.MWDialog.prototype.teardown.call( this, data );

View file

@ -140,7 +140,7 @@ ve.ui.MWReferenceDialog.prototype.initialize = function () {
ve.ui.MWDialog.prototype.initialize.call( this );
// Properties
this.panels = new OO.ui.StackPanelLayout( { '$': this.$ } );
this.panels = new OO.ui.StackLayout( { '$': this.$ } );
this.editPanel = new OO.ui.PanelLayout( {
'$': this.$, 'scrollable': true, 'padded': true
} );
@ -187,14 +187,14 @@ ve.ui.MWReferenceDialog.prototype.initialize = function () {
this.backButton.$element.show();
this.insertButton.$element.hide();
this.selectButton.$element.hide();
this.panels.showItem( this.searchPanel );
this.panels.setItem( this.searchPanel );
this.search.getQuery().$input.focus().select();
} } );
this.backButton.connect( this, { 'click': function () {
this.backButton.$element.hide();
this.insertButton.$element.show();
this.selectButton.$element.show();
this.panels.showItem( this.editPanel );
this.panels.setItem( this.editPanel );
this.editPanel.$element.find( '.ve-ce-documentNode' ).focus();
} } );
this.search.connect( this, { 'select': 'onSearchSelect' } );
@ -234,7 +234,7 @@ ve.ui.MWReferenceDialog.prototype.setup = function ( data ) {
this.selectButton.$element.show();
}
this.backButton.$element.hide();
this.panels.showItem( this.editPanel );
this.panels.setItem( this.editPanel );
this.useReference( ref );
this.search.buildIndex();
this.selectButton.setDisabled( !this.search.getResults().getItems().length );

View file

@ -159,7 +159,7 @@ ve.ui.MWSaveDialog.prototype.swapPanel = function ( panel ) {
}
// Show the target panel
this.panel.showItem( panelObj );
this.panel.setItem( panelObj );
mw.hook( 've.saveDialog.stateChanged' ).fire();
@ -344,7 +344,7 @@ ve.ui.MWSaveDialog.prototype.initialize = function () {
this.nochangesPanel.$element.append( this.$noChanges );
// Panel stack
this.panel = new OO.ui.StackPanelLayout( { '$': this.$, 'scrollable': true } );
this.panel = new OO.ui.StackLayout( { '$': this.$, 'scrollable': true } );
this.panel.$element.addClass( 've-ui-mwSaveDialog-panel' );
this.panel.addItems( [this.savePanel, this.reviewPanel, this.conflictPanel, this.nochangesPanel], 0 );

View file

@ -0,0 +1,56 @@
/*
* VisualEditor user interface MWTemplateDialog class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Dialog for editing MediaWiki templates.
*
* @class
* @extends ve.ui.MWTransclusionDialog
*
* @constructor
* @param {ve.ui.WindowSet} windowSet Window set this dialog is part of
* @param {Object} [config] Configuration options
*/
ve.ui.MWTemplateDialog = function VeUiMWTemplateDialog( windowSet, config ) {
// Parent constructor
ve.ui.MWTransclusionDialog.call( this, windowSet, config );
// Properties
this.node = null;
this.transclusion = null;
this.loaded = false;
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTemplateDialog, ve.ui.MWTransclusionDialog );
/* Static Properties */
ve.ui.MWTemplateDialog.static.name = 'transclusion';
ve.ui.MWTemplateDialog.static.titleMessage = 'visualeditor-dialog-transclusion-title';
ve.ui.MWTemplateDialog.static.icon = 'template';
/* Methods */
/**
* @inheritdoc
*/
ve.ui.MWTemplateDialog.prototype.getBookletLayout = function () {
return new OO.ui.BookletLayout( {
'$': this.$,
'continuous': true,
'autoFocus': true,
'outlined': true
} );
};
/* Registration */
ve.ui.dialogFactory.register( ve.ui.MWTemplateDialog );

View file

@ -1,4 +1,4 @@
/*!
/*
* VisualEditor user interface MWTransclusionDialog class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
@ -22,7 +22,7 @@ ve.ui.MWTransclusionDialog = function VeUiMWTransclusionDialog( windowSet, confi
// Properties
this.node = null;
this.transclusion = null;
this.pending = [];
this.loaded = false;
};
/* Inheritance */
@ -31,457 +31,105 @@ OO.inheritClass( ve.ui.MWTransclusionDialog, ve.ui.MWDialog );
/* Static Properties */
ve.ui.MWTransclusionDialog.static.name = 'transclusion';
ve.ui.MWTransclusionDialog.static.titleMessage = 'visualeditor-dialog-transclusion-title';
ve.ui.MWTransclusionDialog.static.icon = 'template';
/* Methods */
/**
* Handle add part events.
* Handle parts being replaced.
*
* @method
* @param {ve.dm.MWTransclusionPartModel} part Added part
* @param {ve.dm.MWTransclusionPartModel} removed Removed part
* @param {ve.dm.MWTransclusionPartModel} added Added part
*/
ve.ui.MWTransclusionDialog.prototype.onAddPart = function ( part ) {
var i, len, page, params, names, pending, item, spec;
ve.ui.MWTransclusionDialog.prototype.onReplacePart = function ( removed, added ) {
var i, len, page, name, names, params, selected,
removePages = [],
select = false;
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.pagedOutlineLayout.addPage( part.getId(), page );
// Add existing params to templates
if ( part instanceof ve.dm.MWTemplateModel ) {
names = part.getParameterNames();
params = part.getParameters();
for ( i = 0, len = names.length; i < len; i++ ) {
this.onAddParameter( params[names[i]] );
if ( removed ) {
// Remove parameter pages of removed templates
if ( removed instanceof ve.dm.MWTemplateModel ) {
params = removed.getParameters();
for ( name in params ) {
removePages.push( this.bookletLayout.getPage( params[name].getId() ) );
}
part.connect( this, { 'add': 'onAddParameter', 'remove': 'onRemoveParameter' } );
removed.disconnect( this );
}
}
// Add required params to user created templates
if ( part instanceof ve.dm.MWTemplateModel && part.getOrigin() === 'user' ) {
spec = part.getSpec();
names = spec.getParameterNames();
for ( i = 0, len = names.length; i < len; i++ ) {
// Only add required params
if ( spec.isParameterRequired( names[i] ) ) {
part.addParameter( new ve.dm.MWTemplateParameterModel( part, names[i] ) );
}
}
}
// Resolve pending placeholder
for ( i = 0, len = this.pending.length; i < len; i++ ) {
pending = this.pending[i];
if ( pending.part === part ) {
if ( this.outlined ) {
// Auto-select new part if placeholder is still selected
item = this.pagedOutlineLayout.getOutline().getSelectedItem();
if ( item.getData() === pending.placeholder.getId() ) {
this.setPageByName( part.getId() );
selected = this.bookletLayout.getOutline().getSelectedItem();
if ( selected && removed.getId() === selected.getData() ) {
select = true;
}
}
removePages.push( this.bookletLayout.getPage( removed.getId() ) );
this.bookletLayout.removePages( removePages );
}
if ( added ) {
if ( added instanceof ve.dm.MWTemplateModel ) {
page = new ve.ui.MWTemplatePage( added, added.getId(), { '$': this.$ } );
} else if ( added instanceof ve.dm.MWTransclusionContentModel ) {
page = new ve.ui.MWTransclusionContentPage( added, added.getId(), { '$': this.$ } );
} else if ( added instanceof ve.dm.MWTemplatePlaceholderModel ) {
page = new ve.ui.MWTemplatePlaceholderPage( added, added.getId(), { '$': this.$ } );
}
if ( page ) {
this.bookletLayout.addPages( [ page ], this.transclusion.getIndex( added ) );
if ( select && this.loaded ) {
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();
for ( i = 0, len = names.length; i < len; i++ ) {
this.onAddParameter( params[names[i]] );
}
added.connect( this, { 'add': 'onAddParameter', 'remove': 'onRemoveParameter' } );
}
// Add required params to user created templates
if ( added instanceof ve.dm.MWTemplateModel && this.loaded ) {
added.addRequiredParameters();
}
// Cleanup
pending.placeholder.remove();
this.pending.splice( i, 1 );
break;
}
}
};
/**
* 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.pagedOutlineLayout.removePage( params[name].getId() );
}
part.disconnect( this );
}
this.pagedOutlineLayout.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.pagedOutlineLayout.addPage( param.getId(), page );
var page = new ve.ui.MWTemplateParameterPage( param, param.getId(), { '$': this.$ } );
this.bookletLayout.addPages( [ page ], this.transclusion.getIndex( param ) );
if ( this.loaded ) {
this.setPageByName( param.getId() );
}
};
/**
* Handle remove param events.
*
* @method
* @param {ve.dm.MWTemplateParameterModel} param Removed param
*/
ve.ui.MWTransclusionDialog.prototype.onRemoveParameter = function ( param ) {
this.pagedOutlineLayout.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.pagedOutlineLayout.getOutline().getSelectedItem();
if ( item ) {
name = item.getData();
part = this.transclusion.getPartFromId( name );
index = ve.indexOf( part, parts );
// Auto-removes part from old location
this.transclusion.addPart( part, index + places )
.done( ve.bind( this.setPageByName, this, part.getId() ) );
this.bookletLayout.removePages( [ this.bookletLayout.getPage( param.getId() ) ] );
if ( this.loaded ) {
// Return to template page
this.setPageByName( param.getTemplate().getId() );
}
};
/**
* Handle outline controls add events.
* Get an outlined booklet layout widget.
*
* @method
* @param {string} type Type of item to add
* @return {OO.ui.BookletLayout} Configured widget
*/
ve.ui.MWTransclusionDialog.prototype.onOutlineControlsAdd = function ( type ) {
var part;
if ( type === 'content' ) {
part = new ve.dm.MWTransclusionContentModel( this.transclusion, '', 'user' );
} else if ( type === 'template' ) {
part = new ve.dm.MWTemplatePlaceholderModel( this.transclusion, 'user' );
}
if ( part ) {
this.transclusion.addPart( part, this.getPartInsertionIndex() )
.done( ve.bind( this.setPageByName, this, part.getId() ) );
}
};
/**
* Get an index for part insertion.
*
* @method
* @returns {number} Index to insert new parts at
*/
ve.ui.MWTransclusionDialog.prototype.getPartInsertionIndex = function () {
var parts = this.transclusion.getParts(),
item = this.pagedOutlineLayout.getOutline().getSelectedItem();
if ( item ) {
return ve.indexOf( this.transclusion.getPartFromId( item.getData() ), parts ) + 1;
}
return parts.length;
};
/**
* 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;
};
/**
* 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 OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-content' ),
'icon': 'source'
} );
textInput = new OO.ui.TextInputWidget( { '$': this.$, 'multiline': true } );
textInput.setValue( content.getValue() );
textInput.connect( this, { 'change': function () {
content.setValue( textInput.getValue() );
} } );
textInput.$element.addClass( 've-ui-mwTransclusionDialog-input' );
valueFieldset.$element.append( textInput.$element );
optionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-options' ),
'icon': 'settings'
} );
removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-content' ),
'flags': ['destructive']
} );
removeButton.connect( this, { 'click': function () {
content.remove();
} } );
optionsFieldset.$element.append( removeButton.$element );
return {
'label': ve.msg( 'visualeditor-dialog-transclusion-content' ),
'icon': 'source',
'$content': valueFieldset.$element.add( optionsFieldset.$element ),
'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, addParameterSearch, optionsFieldset,
removeButton,
spec = template.getSpec(),
label = spec.getLabel(),
description = spec.getDescription();
function addParameter( name ) {
var param;
if ( name ) {
param = new ve.dm.MWTemplateParameterModel( template, name );
template.addParameter( param );
addParameterSearch.query.setValue();
this.setPageByName( param.getId() );
}
}
infoFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': label,
'icon': 'template'
} );
if ( description ) {
infoFieldset.$element.append( this.$( '<div>' ).text( description ) );
}
addParameterFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-add-param' ),
'icon': 'parameter'
} );
addParameterFieldset.$element.addClass( 've-ui-mwTransclusionDialog-addParameterFieldset' );
addParameterSearch = new ve.ui.MWParameterSearchWidget( template, { '$': this.$ } );
addParameterSearch.connect( this, { 'select': addParameter } );
addParameterFieldset.$element.append( addParameterSearch.$element );
optionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-options' ),
'icon': 'settings'
} );
removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-template' ),
'flags': ['destructive']
} );
removeButton.connect( this, { 'click': function () {
template.remove();
} } );
optionsFieldset.$element.append( removeButton.$element );
return {
'label': label,
'icon': 'template',
'$content': infoFieldset.$element.add( addParameterFieldset.$element ).add( optionsFieldset.$element ),
'moveable': true
};
};
/**
* Get page for a parameter.
*
* @method
* @param {ve.dm.MWTemplateParameterModel} parameter Parameter model
*/
ve.ui.MWTransclusionDialog.prototype.getParameterPage = function ( parameter ) {
var valueFieldset, optionsFieldset, textInput, inputLabel, removeButton,
spec = parameter.getTemplate().getSpec(),
name = parameter.getName(),
label = spec.getParameterLabel( name ),
description = spec.getParameterDescription( name );
valueFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': label,
'icon': 'parameter'
} );
if ( description ) {
inputLabel = new OO.ui.InputLabelWidget( {
'$': this.$,
'input': textInput,
'label': description
} );
valueFieldset.$element.append( inputLabel.$element );
}
textInput = new OO.ui.TextInputWidget( { '$': this.$, 'multiline': true } );
textInput.setValue( parameter.getValue() );
textInput.connect( this, { 'change': function () {
parameter.setValue( textInput.getValue() );
} } );
textInput.$element.addClass( 've-ui-mwTransclusionDialog-input' );
valueFieldset.$element.append( textInput.$element );
optionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-options' ),
'icon': 'settings'
} );
removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-param' ),
'flags': ['destructive']
} );
removeButton.connect( this, { 'click': function () {
parameter.remove();
} } );
optionsFieldset.$element.append( removeButton.$element );
// TODO: Use spec.required
// TODO: Use spec.deprecation
// TODO: Use spec.default
// TODO: Use spec.type
return {
'label': label,
'icon': 'parameter',
'level': 1,
'$content': valueFieldset.$element.add( optionsFieldset.$element )
};
};
/**
* Get page for a parameter.
*
* @method
* @param {ve.dm.MWTemplateParameterModel} parameter Parameter model
*/
ve.ui.MWTransclusionDialog.prototype.getPlaceholderPage = function ( placeholder ) {
var addTemplateFieldset, addTemplateInput, addTemplateButton, optionsFieldset, removeButton,
label = ve.msg( 'visualeditor-dialog-transclusion-placeholder' );
function addTemplate() {
var parts = placeholder.getTransclusion().getParts(),
part = ve.dm.MWTemplateModel.newFromName(
this.transclusion, addTemplateInput.getValue()
);
this.transclusion.addPart( part, ve.indexOf( placeholder, parts ) );
this.pending.push( { 'part': part, 'placeholder': placeholder } );
addTemplateInput.pushPending();
addTemplateButton.setDisabled( true );
removeButton.setDisabled( true );
}
addTemplateFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': label,
'icon': 'template'
} );
addTemplateFieldset.$element.addClass( 've-ui-mwTransclusionDialog-addTemplateFieldset' );
addTemplateInput = new ve.ui.MWTitleInputWidget( {
'$': this.$, '$overlay': this.$overlay, 'namespace': 10
} );
addTemplateButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-add-template' ),
'flags': ['constructive'],
'disabled': true
} );
addTemplateInput.connect( this, {
'change': function () {
addTemplateButton.setDisabled( addTemplateInput.getValue() === '' );
},
'enter': addTemplate
} );
addTemplateButton.connect( this, { 'click': addTemplate } );
addTemplateFieldset.$element.append( addTemplateInput.$element, addTemplateButton.$element );
optionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-options' ),
'icon': 'settings'
} );
removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-template' ),
'flags': ['destructive']
} );
removeButton.connect( this, { 'click': function () {
placeholder.remove();
} } );
optionsFieldset.$element.append( removeButton.$element );
return {
'label': this.$( '<span>' )
.addClass( 've-ui-mwTransclusionDialog-placeholder-label' )
.text( label ),
'icon': 'template',
'$content': addTemplateFieldset.$element.add( optionsFieldset.$element )
};
ve.ui.MWTransclusionDialog.prototype.getBookletLayout = function () {
return new OO.ui.BookletLayout( { '$': this.$, 'continuous': true } );
};
/**
@ -489,13 +137,16 @@ ve.ui.MWTransclusionDialog.prototype.getPlaceholderPage = function ( placeholder
*
* 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.pagedOutlineLayout.getOutline().selectItem(
this.pagedOutlineLayout.getOutline().getItemFromData( name )
);
if ( this.outlined ) {
this.bookletLayout.getOutline().selectItem(
this.bookletLayout.getOutline().getItemFromData( name )
);
} else {
this.bookletLayout.setPage( name );
}
};
/**
@ -507,34 +158,17 @@ ve.ui.MWTransclusionDialog.prototype.initialize = function () {
// Properties
this.applyButton = new OO.ui.PushButtonWidget( {
'$': this.$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
this.pagedOutlineLayout = new OO.ui.PagedOutlineLayout( {
'$': this.$,
'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' )
}
]
'label': ve.msg( 'visualeditor-dialog-action-apply' ),
'flags': ['primary']
} );
this.bookletLayout = this.getBookletLayout();
// Events
this.pagedOutlineLayout.getOutlineControls().connect( this, {
'move': 'onOutlineControlsMove',
'add': 'onOutlineControlsAdd'
} );
this.applyButton.connect( this, { 'click': [ 'close', { 'action': 'apply' } ] } );
// Initialization
this.$body.append( this.pagedOutlineLayout.$element );
this.$body.append( this.bookletLayout.$element );
this.$foot.append( this.applyButton.$element );
};
@ -550,17 +184,21 @@ ve.ui.MWTransclusionDialog.prototype.setup = function ( data ) {
// Properties
this.transclusion = new ve.dm.MWTransclusionModel();
this.loaded = false;
// Events
this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 'onRemovePart' } );
this.transclusion.connect( this, { 'replace': 'onReplacePart' } );
// Initialization
if ( this.node instanceof ve.ce.MWTransclusionNode ) {
this.transclusion.load( ve.copy( this.node.getModel().getAttribute( 'mw' ) ) );
this.transclusion
.load( ve.copy( this.node.getModel().getAttribute( 'mw' ) ) )
.always( ve.bind( function () {
this.loaded = true;
}, this ) );
} else {
this.transclusion.addPart(
new ve.dm.MWTemplatePlaceholderModel( this.transclusion, 'user' )
);
this.loaded = true;
this.transclusion.addPart( new ve.dm.MWTemplatePlaceholderModel( this.transclusion ) );
}
};
@ -598,7 +236,7 @@ ve.ui.MWTransclusionDialog.prototype.teardown = function ( data ) {
this.transclusion.disconnect( this );
this.transclusion.abortRequests();
this.transclusion = null;
this.pagedOutlineLayout.clearPages();
this.bookletLayout.clearPages();
this.node = null;
this.content = null;
@ -606,6 +244,3 @@ ve.ui.MWTransclusionDialog.prototype.teardown = function ( data ) {
ve.ui.MWDialog.prototype.teardown.call( this, data );
};
/* Registration */
ve.ui.dialogFactory.register( ve.ui.MWTransclusionDialog );

View file

@ -0,0 +1,264 @@
/*!
* VisualEditor user interface MWCategoriesPage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw*/
/**
* MediaWiki meta dialog categories page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {ve.ui.Surface} surface Surface being worked on
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
* @cfg {jQuery} [$overlay] Overlay to render category settings popups in
*/
ve.ui.MWCategoriesPage = function VeUiMWCategoriesPage( surface, name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'tag' } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.surface = surface;
this.metaList = this.surface.getModel().metaList;
this.defaultSortKeyTouched = false;
this.fallbackDefaultSortKey = mw.config.get( 'wgTitle' );
this.label = ve.msg( 'visualeditor-dialog-meta-categories-section' );
this.categoriesFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-categories-data-label' ),
'icon': 'tag'
} );
this.categoryOptionsFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-categories-options' ),
'icon': 'settings'
} );
this.categoryWidget = new ve.ui.MWCategoryWidget( {
'$': this.$, '$overlay': config.$overlay
} );
this.defaultSortInput = new OO.ui.TextInputWidget( {
'$': this.$, 'placeholder': this.fallbackDefaultSortKey
} );
this.defaultSortLabel = new OO.ui.InputLabelWidget( {
'$': this.$,
'input': this.defaultSortInput,
'label': ve.msg( 'visualeditor-dialog-meta-categories-defaultsort-label' )
} );
// Events
this.metaList.connect( this, {
'insert': 'onMetaListInsert',
'remove': 'onMetaListRemove'
} );
this.categoryWidget.connect( this, {
'newCategory': 'onNewCategory',
'updateSortkey': 'onUpdateSortKey'
} );
this.defaultSortInput.connect( this, {
'change': 'onDefaultSortChange'
} );
// Initialization
this.categoryWidget.addItems( this.getCategoryItems() );
this.categoriesFieldset.$element.append( this.categoryWidget.$element );
this.categoryOptionsFieldset.$element.append(
this.defaultSortLabel.$element,
this.defaultSortInput.$element
);
this.$element.append( this.categoriesFieldset.$element, this.categoryOptionsFieldset.$element );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWCategoriesPage, OO.ui.PageLayout );
/* Methods */
/**
* Handle category default sort change events.
*
* @param {string} value Default sort value
*/
ve.ui.MWCategoriesPage.prototype.onDefaultSortChange = function ( value ) {
this.categoryWidget.setDefaultSortKey( value === '' ? this.fallbackDefaultSortKey : value );
this.defaultSortKeyTouched = true;
};
/**
* Inserts new category into meta list
*
* @param {Object} item
*/
ve.ui.MWCategoriesPage.prototype.onNewCategory = function ( item ) {
// Insert new metaList item
this.insertMetaListItem( this.getCategoryItemForInsertion( item ) );
};
/**
* Removes and re-inserts updated category widget item
*
* @param {Object} item
*/
ve.ui.MWCategoriesPage.prototype.onUpdateSortKey = function ( item ) {
// Replace meta item with updated one
item.metaItem.replaceWith( this.getCategoryItemForInsertion( item, item.metaItem.getElement() ) );
};
/**
* Bound to MetaList insert event for adding meta dialog components.
*
* @param {Object} ve.dm.MetaItem
*/
ve.ui.MWCategoriesPage.prototype.onMetaListInsert = function ( metaItem ) {
// Responsible for adding UI components
if ( metaItem.element.type === 'mwCategory' ) {
this.categoryWidget.addItems(
[ this.getCategoryItemFromMetaListItem( metaItem ) ],
this.metaList.findItem( metaItem.getOffset(), metaItem.getIndex(), 'mwCategory' )
);
}
};
/**
* Bound to MetaList insert event for removing meta dialog components.
*
* @param {Object} ve.dm.MetaItem
*/
ve.ui.MWCategoriesPage.prototype.onMetaListRemove = function ( metaItem ) {
var item;
if ( metaItem.element.type === 'mwCategory' ) {
item = this.getCategoryItemFromMetaListItem( metaItem );
this.categoryWidget.removeItems( [item.value] );
}
};
/**
* Get default sort key item.
*
* @returns {string} Default sort key item
*/
ve.ui.MWCategoriesPage.prototype.getDefaultSortKeyItem = function () {
var items = this.metaList.getItemsInGroup( 'mwDefaultSort' );
return items.length ? items[0] : null;
};
/**
* Get array of category items from meta list
*
* @returns {Object[]} items
*/
ve.ui.MWCategoriesPage.prototype.getCategoryItems = function () {
var i,
items = [],
categories = this.metaList.getItemsInGroup( 'mwCategory' );
// Loop through MwCategories and build out items
for ( i = 0; i < categories.length; i++ ) {
items.push( this.getCategoryItemFromMetaListItem( categories[i] ) );
}
return items;
};
/**
* Gets category item from meta list item
*
* @param {Object} ve.dm.MWCategoryMetaItem
* @returns {Object} item
*/
ve.ui.MWCategoriesPage.prototype.getCategoryItemFromMetaListItem = function ( metaItem ) {
var title = mw.Title.newFromText( metaItem.element.attributes.category ),
value = title ? title.getMainText() : '';
return {
'name': metaItem.element.attributes.category,
'value': value,
// TODO: sortkey is lcase, make consistent throughout CategoryWidget
'sortKey': metaItem.element.attributes.sortkey,
'metaItem': metaItem
};
};
/**
* Get metaList like object to insert from item
*
* @param {Object} item category widget item
* @param {Object} [oldData] Metadata object that was previously associated with this item, if any
* @returns {Object} metaBase
*/
ve.ui.MWCategoriesPage.prototype.getCategoryItemForInsertion = function ( item, oldData ) {
var newData = {
'attributes': { 'category': item.name, 'sortkey': item.sortKey || '' },
'type': 'mwCategory'
};
if ( oldData ) {
return ve.extendObject( {}, oldData, newData );
}
return newData;
};
/**
* Inserts a meta list item
*
* @param {Object} metaBase meta list insert object
*/
ve.ui.MWCategoriesPage.prototype.insertMetaListItem = function ( metaBase ) {
this.metaList.insertMeta( metaBase );
};
/**
* Set up the page. This is called when the MWMetaDialog is set up.
*/
ve.ui.MWCategoriesPage.prototype.setup = function () {
var defaultSortKeyItem = this.getDefaultSortKeyItem();
this.defaultSortInput.setValue(
defaultSortKeyItem ? defaultSortKeyItem.getAttribute( 'content' ) : ''
);
this.defaultSortKeyTouched = false;
// Update input position once visible
setTimeout( ve.bind( function () {
this.categoryWidget.fitInput();
}, this ) );
};
/**
* Tear down the page. This is called when the MWMetaDialog is torn down.
*/
ve.ui.MWCategoriesPage.prototype.teardown = function () {
var currentDefaultSortKeyItem = this.getDefaultSortKeyItem(),
newDefaultSortKey = this.defaultSortInput.getValue(),
newDefaultSortKeyData = {
'type': 'mwDefaultSort',
'attributes': { 'content': newDefaultSortKey }
};
// Alter the default sort key iff it's been touched & is actually different
if ( this.defaultSortKeyTouched ) {
if ( newDefaultSortKey === '' ) {
if ( currentDefaultSortKeyItem ) {
currentDefaultSortKeyItem.remove();
}
} else {
if ( !currentDefaultSortKeyItem ) {
this.metaList.insertMeta( newDefaultSortKeyData );
} else if ( currentDefaultSortKeyItem.getAttribute( 'content' ) !== newDefaultSortKey ) {
currentDefaultSortKeyItem.replaceWith(
ve.extendObject( true, {},
currentDefaultSortKeyItem.getElement(),
newDefaultSortKeyData
) );
}
}
}
};

View file

@ -0,0 +1,173 @@
/*!
* VisualEditor user interface MWLanguagesPage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw*/
/**
* MediaWiki meta dialog Languages page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
*/
ve.ui.MWLanguagesPage = function VeUiMWLanguagesPage( name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'language' } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.label = ve.msg( 'visualeditor-dialog-meta-languages-section' );
this.languagesFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ),
'icon': 'language'
} );
// Initialization
this.languagesFieldset.$element.append(
this.$( '<span>' )
.text( ve.msg( 'visualeditor-dialog-meta-languages-readonlynote' ) )
);
this.$element.append( this.languagesFieldset.$element );
this.getAllLanguageItems().done( ve.bind( this.onLoadLanguageData, this ) );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWLanguagesPage, OO.ui.PageLayout );
/* Methods */
ve.ui.MWLanguagesPage.prototype.onLoadLanguageData = function ( languages ) {
var i, $languagesTable = this.$( '<table>' ), languageslength = languages.length;
$languagesTable
.addClass( 've-ui-MWLanguagesPage-languages-table' )
.append( this.$( '<tr>' )
.append(
this.$( '<th>' )
.append( ve.msg( 'visualeditor-dialog-meta-languages-code-label' ) )
)
.append(
this.$( '<th>' )
.append( ve.msg( 'visualeditor-dialog-meta-languages-link-label' ) )
)
);
for ( i = 0; i < languageslength; i++ ) {
languages[i].safelang = languages[i].lang;
languages[i].dir = 'auto';
if ( $.uls ) {
// site codes don't always represent official language codes
// using real language code instead of a dummy ('redirect' in ULS' terminology)
languages[i].safelang = $.uls.data.isRedirect( languages[i].lang ) || languages[i].lang;
languages[i].dir = $.uls.data.getDir( languages[i].safelang );
}
$languagesTable
.append( this.$( '<tr>' )
.append( this.$( '<td>' ).append( languages[i].lang ) )
.append( this.$( '<td>' ).append( languages[i].title )
.attr( 'lang', languages[i].safelang )
.attr( 'dir', languages[i].dir ) )
);
}
this.languagesFieldset.$element.append( $languagesTable );
};
/**
* Handle language items being loaded.
*/
ve.ui.MWLanguagesPage.prototype.onAllLanuageItemsSuccess = function ( deferred, response ) {
var i, iLen, languages = [], langlinks = response.query.pages[response.query.pageids[0]].langlinks;
if ( langlinks ) {
for ( i = 0, iLen = langlinks.length; i < iLen; i++ ) {
languages.push( {
'lang': langlinks[i].lang,
'title': langlinks[i]['*'],
'metaItem': null
} );
}
}
deferred.resolve( languages );
};
/**
* Gets language item from meta list item
*
* @param {Object} ve.dm.MWLanguageMetaItem
* @returns {Object} item
*/
ve.ui.MWLanguagesPage.prototype.getLanguageItemFromMetaListItem = function ( metaItem ) {
// TODO: get real values from metaItem once Parsoid actually provides them - bug 48970
return {
'lang': 'lang',
'title': 'title',
'metaItem': metaItem
};
};
/**
* Get array of language items from meta list
*
* @returns {Object[]} items
*/
ve.ui.MWLanguagesPage.prototype.getLocalLanguageItems = function () {
var i,
items = [],
languages = this.metaList.getItemsInGroup( 'mwLanguage' ),
languageslength = languages.length;
// Loop through MWLanguages and build out items
for ( i = 0; i < languageslength; i++ ) {
items.push( this.getLanguageItemFromMetaListItem( languages[i] ) );
}
return items;
};
/**
* Get array of language items from meta list
*
* @returns {jQuery.Promise}
*/
ve.ui.MWLanguagesPage.prototype.getAllLanguageItems = function () {
var deferred = $.Deferred();
// TODO: Detect paging token if results exceed limit
$.ajax( {
'url': mw.util.wikiScript( 'api' ),
'data': {
'action': 'query',
'prop': 'langlinks',
'lllimit': 500,
'titles': mw.config.get( 'wgTitle' ),
'indexpageids': 1,
'format': 'json'
},
'dataType': 'json',
'type': 'POST',
// Wait up to 100 seconds before giving up
'timeout': 100000,
'cache': 'false'
} )
.done( ve.bind( this.onAllLanuageItemsSuccess, this, deferred ) )
.fail( ve.bind( this.onAllLanuageItemsError, this, deferred ) );
return deferred.promise();
};
/**
* Handle language items failing to be loaded.
*
* TODO: This error function should probably not be empty.
*/
ve.ui.MWLanguagesPage.prototype.onAllLanuageItemsError = function () {};

View file

@ -0,0 +1,79 @@
/*!
* VisualEditor user interface MWTemplatePage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki transclusion dialog template page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {ve.dm.MWTemplateModel} parameter Template
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
*/
ve.ui.MWTemplatePage = function VeUiMWTemplatePage( template, name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'template', 'moveable': true, 'level': 0 } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.template = template;
this.spec = this.template.getSpec();
this.label = this.spec.getLabel();
this.addParameterSearch = new ve.ui.MWParameterSearchWidget( this.template, { '$': this.$ } )
.connect( this, { 'select': 'onParameterSelect' } );
this.removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-template' ),
'flags': ['destructive'],
'classes': [ 've-ui-mwTransclusionDialog-removeButton' ]
} )
.connect( this, { 'click': 'onRemoveButtonClick' } );
this.infoFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': this.spec.getLabel(),
'icon': 'template',
'$content': this.$( '<div>' ).text( this.template.getSpec().getDescription() || '' )
} );
this.addParameterFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-add-param' ),
'icon': 'parameter',
'classes': [ 've-ui-mwTransclusionDialog-addParameterFieldset' ],
'$content': this.addParameterSearch.$element
} );
// Initialization
this.$element.append(
this.infoFieldset.$element,
this.addParameterFieldset.$element,
this.removeButton.$element
);
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTemplatePage, OO.ui.PageLayout );
/* Methods */
ve.ui.MWTemplatePage.prototype.onParameterSelect = function ( name ) {
var param;
if ( name ) {
param = new ve.dm.MWTemplateParameterModel( this.template, name );
this.template.addParameter( param );
this.addParameterSearch.query.setValue();
}
};
ve.ui.MWTemplatePage.prototype.onRemoveButtonClick = function () {
this.template.remove();
};

View file

@ -0,0 +1,77 @@
/*!
* VisualEditor user interface MWTemplateParameterPage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki transclusion dialog template page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {ve.dm.MWTemplateParameterModel} parameter Template parameter
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
*/
ve.ui.MWTemplateParameterPage = function VeUiMWTemplateParameter( parameter, name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'parameter', 'moveable': false, 'level': 1 } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.parameter = parameter;
this.spec = parameter.getTemplate().getSpec();
this.label = this.spec.getParameterLabel( this.parameter.getName() );
this.textInput = new OO.ui.TextInputWidget( {
'$': this.$,
'multiline': true,
'classes': [ 've-ui-mwTransclusionDialog-input' ]
} )
.setValue( this.parameter.getValue() )
.connect( this, { 'change': 'onTextInputChange' } );
this.inputLabel = new OO.ui.InputLabelWidget( {
'$': this.$,
'input': this.textInput,
'label': this.spec.getParameterDescription( this.parameter.getName() ) || ''
} );
this.removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-param' ),
'flags': ['destructive'],
'classes': [ 've-ui-mwTransclusionDialog-removeButton' ]
} )
.connect( this, { 'click': 'onRemoveButtonClick' } );
this.valueFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': this.spec.getParameterLabel( this.parameter.getName() ),
'icon': 'parameter',
'$content': this.inputLabel.$element.add( this.textInput.$element )
} );
// TODO: Use spec.required
// TODO: Use spec.deprecation
// TODO: Use spec.default
// TODO: Use spec.type
// Initialization
this.$element.append( this.valueFieldset.$element, this.removeButton.$element );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTemplateParameterPage, OO.ui.PageLayout );
/* Methods */
ve.ui.MWTemplateParameterPage.prototype.onTextInputChange = function () {
this.parameter.setValue( this.textInput.getValue() );
};
ve.ui.MWTemplateParameterPage.prototype.onRemoveButtonClick = function () {
this.parameter.remove();
};

View file

@ -0,0 +1,87 @@
/*!
* VisualEditor user interface MWTemplatePlaceholderPage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki transclusion dialog placeholder page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {ve.dm.MWTemplatePlaceholderModel} placeholder Template placeholder
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
*/
ve.ui.MWTemplatePlaceholderPage = function VeUiMWTemplatePlaceholder( placeholder, name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'template', 'moveable': true, 'level': 0 } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.placeholder = placeholder;
this.label = this.$( '<span>' )
.addClass( 've-ui-mwTransclusionDialog-placeholder-label' )
.text( ve.msg( 'visualeditor-dialog-transclusion-placeholder' ) );
this.addTemplateInput = new ve.ui.MWTitleInputWidget( {
'$': this.$, '$overlay': this.$overlay, 'namespace': 10
} )
.connect( this, {
'change': 'onTemplateInputChange',
'enter': 'onAddTemplate'
} );
this.addTemplateButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-add-template' ),
'flags': ['constructive'],
'disabled': true
} )
.connect( this, { 'click': 'onAddTemplate' } );
this.removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-template' ),
'flags': ['destructive'],
'classes': [ 've-ui-mwTransclusionDialog-removeButton' ]
} )
.connect( this, { 'click': 'onRemoveButtonClick' } );
this.addTemplateFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-placeholder' ),
'icon': 'template',
'classes': [ 've-ui-mwTransclusionDialog-addTemplateFieldset' ],
'$content': this.addTemplateInput.$element.add( this.addTemplateButton.$element )
} );
// Initialization
this.$element.append( this.addTemplateFieldset.$element, this.removeButton.$element );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTemplatePlaceholderPage, OO.ui.PageLayout );
/* Methods */
ve.ui.MWTemplatePlaceholderPage.prototype.onAddTemplate = function () {
var transclusion = this.placeholder.getTransclusion(),
parts = this.placeholder.getTransclusion().getParts(),
part = ve.dm.MWTemplateModel.newFromName( transclusion, this.addTemplateInput.getValue() );
transclusion.replacePart( this.placeholder, part, ve.indexOf( this.placeholder, parts ) );
this.addTemplateInput.pushPending();
this.addTemplateButton.setDisabled( true );
this.removeButton.setDisabled( true );
};
ve.ui.MWTemplatePlaceholderPage.prototype.onTemplateInputChange = function ( value ) {
this.addTemplateButton.setDisabled( value === '' );
};
ve.ui.MWTemplatePlaceholderPage.prototype.onRemoveButtonClick = function () {
this.placeholder.remove();
};

View file

@ -0,0 +1,67 @@
/*!
* VisualEditor user interface MWTransclusionContentPage class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki transclusion dialog content page.
*
* @class
* @extends OO.ui.PageLayout
*
* @constructor
* @param {ve.dm.MWTransclusionContentModel} content Transclusion content
* @param {string} name Unique symbolic name of page
* @param {Object} [config] Configuration options
*/
ve.ui.MWTransclusionContentPage = function VeUiMWTransclusionContent( content, name, config ) {
// Configuration initialization
config = ve.extendObject( config, { 'icon': 'source', 'moveable': true, 'level': 0 } );
// Parent constructor
OO.ui.PageLayout.call( this, name, config );
// Properties
this.content = content;
this.label = ve.msg( 'visualeditor-dialog-transclusion-content' );
this.textInput = new OO.ui.TextInputWidget( {
'$': this.$,
'multiline': true,
'classes': [ 've-ui-mwTransclusionDialog-input' ]
} )
.setValue( this.content.getValue() )
.connect( this, { 'change': 'onTextInputChange' } );
this.removeButton = new OO.ui.PushButtonWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-remove-content' ),
'flags': [ 'destructive' ],
'classes': [ 've-ui-mwTransclusionDialog-removeButton' ]
} )
.connect( this, { 'click': 'onRemoveButtonClick' } );
this.valueFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-transclusion-content' ),
'icon': 'source',
'$content': this.textInput.$element
} );
// Initialization
this.$element.append( this.valueFieldset.$element, this.removeButton.$element );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTransclusionContentPage, OO.ui.PageLayout );
/* Methods */
ve.ui.MWTransclusionContentPage.prototype.onTextInputChange = function () {
this.content.setValue( this.textInput.getValue() );
};
ve.ui.MWTransclusionContentPage.prototype.onRemoveButtonClick = function () {
this.content.remove();
};

View file

@ -27,6 +27,16 @@
font-style: italic;
}
.oo-ui-fieldsetLayout + .ve-ui-mwTransclusionDialog-addParameterFieldset {
margin-top: 0;
}
.ve-ui-mwTransclusionDialog-addParameterFieldset > legend.oo-ui-labeledElement-label {
font-size: 1.25em;
padding-left: 2em;
line-height: 2em;
}
.ve-ui-mwTransclusionDialog-addParameterFieldset .ve-ui-mwParameterSearchWidget {
position: relative;
overflow: hidden;
@ -39,6 +49,12 @@
padding-bottom: 5em;
}
.ve-ui-mwTransclusionDialog-removeButton {
position: absolute;
right: 1.5em;
top: 1em;
}
/* ve.ui.MWMetaDialog */
.ve-ui-mwMetaDialog-languages-table {