/*! * 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.categoryOptionsFieldset = new ve.ui.FieldsetLayout( { '$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-meta-categories-options' ), '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.categoryOptionsFieldset.$ ); this.categoriesFieldset.$.append( this.categoryWidget.$ ); this.categoryOptionsFieldset.$.append( this.defaultSortLabel.$, this.defaultSortInput.$ ); this.pages.languages.$.append( this.languagesFieldset.$ ); this.languagesFieldset.$.append( this.frame.$$( '' ) .text( ve.msg( 'visualeditor-dialog-meta-languages-readonlynote' ) ) ); languagePromise = this.getAllLanguageItems(); languagePromise.done( ve.bind( function ( languages ) { var i, $languagesTable = this.frame.$$( '' ), languageslength = languages.length; $languagesTable .addClass( 've-ui-mwMetaDialog-languages-table' ) .append( this.frame.$$( '' ) .append( this.frame.$$( '' ) .append( this.frame.$$( '
' ) .append( ve.msg( 'visualeditor-dialog-meta-languages-code-label' ) ) ) .append( this.frame.$$( '' ) .append( ve.msg( 'visualeditor-dialog-meta-languages-link-label' ) ) ) ); for ( i = 0; i < languageslength; i++ ) { $languagesTable .append( this.frame.$$( '
' ).append( languages[i].lang ) ) .append( this.frame.$$( '' ).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 hasTransactions, 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 hasTransactions = surfaceModel.breakpoint(); // Undo everything done in the dialog and prevent redoing those changes if ( action === 'cancel' && hasTransactions ) { 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 {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', 'success': ve.bind( this.onAllLanuageItemsSuccess, this, deferred ), 'error': ve.bind( this.onAllLanuageItemsError, this, deferred ) } ); return deferred.promise(); }; 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 ); }; // 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 );