mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 18:39:52 +00:00
983d9287e9
When editing the default sort key or a category's sort key, we would just build a brand new meta item and replace the original item with it. This destroys whitespace information tracked in the .internal property though, so the resulting diffs looked pretty bad. Instead, use ve.extendObject() to base the new meta item on the old one, changing only what we need to change and keeping .internal (and htmlAttributes and anything else that may be hiding in there) intact. Change-Id: I40f4403ea2f2d13542d2e3c8c53e2d7f79515381
426 lines
12 KiB
JavaScript
426 lines
12 KiB
JavaScript
/*!
|
|
* VisualEditor user interface MWMetaDialog class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/*global mw*/
|
|
|
|
/**
|
|
* Document dialog.
|
|
*
|
|
* @class
|
|
* @extends ve.ui.PagedDialog
|
|
*
|
|
* @constructor
|
|
* @param {ve.ui.Surface} surface
|
|
* @param {Object} [config] Config options
|
|
*/
|
|
ve.ui.MWMetaDialog = function VeUiMWMetaDialog( surface, config ) {
|
|
// Parent constructor
|
|
ve.ui.PagedDialog.call( this, surface, config );
|
|
|
|
// Properties
|
|
this.metaList = surface.getModel().metaList;
|
|
this.defaultSortKeyChanged = false;
|
|
this.fallbackDefaultSortKey = mw.config.get( 'wgTitle' );
|
|
|
|
// Events
|
|
this.metaList.connect( this, {
|
|
'insert': 'onMetaListInsert',
|
|
'remove': 'onMetaListRemove'
|
|
} );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ui.MWMetaDialog, ve.ui.PagedDialog );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ui.MWMetaDialog.static.titleMessage = 'visualeditor-dialog-meta-title';
|
|
|
|
ve.ui.MWMetaDialog.static.icon = 'settings';
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handle frame ready events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.initialize = function () {
|
|
var languagePromise;
|
|
|
|
// Call parent method
|
|
ve.ui.PagedDialog.prototype.initialize.call( this );
|
|
|
|
// Properties
|
|
this.categoriesFieldset = new ve.ui.FieldsetLayout( {
|
|
'$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-meta-categories-data-label' ), 'icon': 'tag'
|
|
} );
|
|
this.categorySettingsFieldset = new ve.ui.FieldsetLayout( {
|
|
'$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-meta-categories-settings-label' ), 'icon': 'settings'
|
|
} );
|
|
this.categoryWidget = new ve.ui.MWCategoryWidget( {
|
|
'$$': this.frame.$$, '$overlay': this.$overlay
|
|
} );
|
|
this.defaultSortInput = new ve.ui.TextInputWidget( {
|
|
'$$': this.frame.$$, 'placeholder': this.fallbackDefaultSortKey
|
|
} );
|
|
this.defaultSortLabel = new ve.ui.InputLabelWidget( {
|
|
'$$': this.frame.$$,
|
|
'input': this.defaultSortInput,
|
|
'label': ve.msg( 'visualeditor-dialog-meta-categories-defaultsort-label' )
|
|
} );
|
|
this.languagesFieldset = new ve.ui.FieldsetLayout( {
|
|
'$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ), 'icon': 'language'
|
|
} );
|
|
|
|
// Events
|
|
this.categoryWidget.connect( this, {
|
|
'newCategory': 'onNewCategory',
|
|
'updateSortkey': 'onUpdateSortKey'
|
|
} );
|
|
this.defaultSortInput.connect( this, {
|
|
'change': 'onDefaultSortChange'
|
|
} );
|
|
|
|
// Initialization
|
|
this.categoryWidget.addItems( this.getCategoryItems() );
|
|
this.addPage( 'categories', {
|
|
'label': ve.msg( 'visualeditor-dialog-meta-categories-section' ),
|
|
'icon': 'tag'
|
|
} ).addPage( 'languages', {
|
|
'label': ve.msg( 'visualeditor-dialog-meta-languages-section' ),
|
|
'icon': 'language'
|
|
} );
|
|
|
|
this.pages.categories.$.append( this.categoriesFieldset.$, this.categorySettingsFieldset.$ );
|
|
this.categoriesFieldset.$.append( this.categoryWidget.$ );
|
|
this.categorySettingsFieldset.$.append( this.defaultSortLabel.$, this.defaultSortInput.$ );
|
|
|
|
this.pages.languages.$.append( this.languagesFieldset.$ );
|
|
|
|
this.languagesFieldset.$
|
|
.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-dialog-meta-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++ ) {
|
|
$languagesTable
|
|
.append( $( '<tr>' )
|
|
.append( $( '<td>' ).append( languages[i].lang ) )
|
|
.append( $( '<td>' ).append( languages[i].title ) )
|
|
);
|
|
}
|
|
|
|
this.languagesFieldset.$.append( $languagesTable );
|
|
}, this ) );
|
|
};
|
|
|
|
/**
|
|
* Handle frame ready events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.onOpen = function () {
|
|
var surfaceModel = this.surface.getModel(),
|
|
categoryWidget = this.categoryWidget,
|
|
defaultSortKeyItem = this.getDefaultSortKeyItem();
|
|
|
|
this.defaultSortInput.setValue(
|
|
defaultSortKeyItem ? defaultSortKeyItem.getAttribute( 'content' ) : ''
|
|
);
|
|
this.defaultSortKeyChanged = false;
|
|
|
|
// Force all previous transactions to be separate from this history state
|
|
surfaceModel.breakpoint();
|
|
surfaceModel.stopHistoryTracking();
|
|
|
|
// Parent method
|
|
ve.ui.PagedDialog.prototype.onOpen.call( this );
|
|
|
|
// Update input position once visible
|
|
setTimeout( function () {
|
|
categoryWidget.fitInput();
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Handle frame ready events.
|
|
*
|
|
* @method
|
|
* @param {string} action Action that caused the window to be closed
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.onClose = function ( action ) {
|
|
var newDefaultSortKeyItem, newDefaultSortKeyItemData,
|
|
surfaceModel = this.surface.getModel(),
|
|
currentDefaultSortKeyItem = this.getDefaultSortKeyItem();
|
|
|
|
// Parent method
|
|
ve.ui.PagedDialog.prototype.onClose.call( this );
|
|
|
|
// Place transactions made while dialog was open in a common history state
|
|
surfaceModel.breakpoint();
|
|
|
|
// Undo everything done in the dialog and prevent redoing those changes
|
|
if ( action === 'cancel' ) {
|
|
surfaceModel.undo();
|
|
surfaceModel.truncateUndoStack();
|
|
}
|
|
|
|
if ( this.defaultSortKeyChanged ) {
|
|
newDefaultSortKeyItemData = {
|
|
'type': 'mwDefaultSort',
|
|
'attributes': { 'content': this.defaultSortInput.getValue() }
|
|
};
|
|
if ( currentDefaultSortKeyItem ) {
|
|
newDefaultSortKeyItem = new ve.dm.MWDefaultSortMetaItem(
|
|
ve.extendObject( {}, currentDefaultSortKeyItem.getElement(), newDefaultSortKeyItemData )
|
|
);
|
|
currentDefaultSortKeyItem.replaceWith( newDefaultSortKeyItem );
|
|
} else {
|
|
newDefaultSortKeyItem = new ve.dm.MWDefaultSortMetaItem( newDefaultSortKeyItemData );
|
|
this.metaList.insertMeta( newDefaultSortKeyItem );
|
|
}
|
|
}
|
|
|
|
// Return to normal tracking behavior
|
|
surfaceModel.startHistoryTracking();
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @method
|
|
* @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
|
|
*
|
|
* @method
|
|
* @param {Object} ve.dm.MWCategoryMetaItem
|
|
* @returns {Object} item
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.getCategoryItemFromMetaListItem = function ( metaItem ) {
|
|
return {
|
|
'name': metaItem.element.attributes.category,
|
|
'value': metaItem.element.attributes.category.split( ':' )[1],
|
|
// TODO: sortkey is lcase, make consistent throughout CategoryWidget
|
|
'sortKey': metaItem.element.attributes.sortkey,
|
|
'metaItem': metaItem
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get metaList like object to insert from item
|
|
*
|
|
* @method
|
|
* @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
|
|
*
|
|
* @method
|
|
* @param {Object} ve.dm.MWLanguageMetaItem
|
|
* @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
|
|
*
|
|
* @method
|
|
* @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
|
|
*
|
|
* @method
|
|
* @returns {Object[]} items
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.getAllLanguageItems = function () {
|
|
var promise = $.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',
|
|
'success': ve.bind( this.onAllLanuageItemsSuccess, this, promise ),
|
|
'error': ve.bind( this.onAllLanuageItemsError, this, promise )
|
|
} );
|
|
return promise;
|
|
};
|
|
|
|
ve.ui.MWMetaDialog.prototype.onAllLanuageItemsSuccess = function ( promise, 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
|
|
} );
|
|
}
|
|
}
|
|
promise.resolve( languages );
|
|
};
|
|
|
|
// 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.defaultSortKeyChanged = true;
|
|
};
|
|
|
|
/**
|
|
* Inserts new category into meta list
|
|
*
|
|
* @method
|
|
* @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
|
|
*
|
|
* @method
|
|
* @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.
|
|
*
|
|
* @method
|
|
* @param {Object} ve.dm.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.
|
|
*
|
|
* @method
|
|
* @param {Object} ve.dm.MetaItem
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.onMetaListRemove = function ( metaItem ) {
|
|
var item;
|
|
|
|
if ( metaItem.element.type === 'mwCategory' ) {
|
|
item = this.getCategoryItemFromMetaListItem( metaItem );
|
|
this.categoryWidget.removeItems( [item.value] );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Inserts a meta list item
|
|
*
|
|
* @param {Object} metaBase meta list insert object
|
|
*/
|
|
ve.ui.MWMetaDialog.prototype.insertMetaListItem = function ( metaBase ) {
|
|
var offset = this.surface.getModel().getDocument().getData().length;
|
|
this.metaList.insertMeta( metaBase, offset );
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.dialogFactory.register( 'mwMeta', ve.ui.MWMetaDialog );
|