/*! * VisualEditor user interface MWMediaEditDialog class. * * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /*global mw */ /** * Dialog for editing MediaWiki media objects. * * @class * @extends ve.ui.MWDialog * * @constructor * @param {ve.ui.WindowSet} windowSet Window set this dialog is part of * @param {Object} [config] Configuration options */ ve.ui.MWMediaEditDialog = function VeUiMWMediaEditDialog( windowSet, config ) { // Parent constructor ve.ui.MWDialog.call( this, windowSet, config ); // Properties this.mediaNode = null; this.captionNode = null; this.store = null; this.filename = null; }; /* Inheritance */ OO.inheritClass( ve.ui.MWMediaEditDialog, ve.ui.MWDialog ); /* Static Properties */ ve.ui.MWMediaEditDialog.static.name = 'mediaEdit'; ve.ui.MWMediaEditDialog.static.titleMessage = 'visualeditor-dialog-media-title'; ve.ui.MWMediaEditDialog.static.icon = 'picture'; ve.ui.MWMediaEditDialog.static.toolbarGroups = [ // History { 'include': [ 'undo', 'redo' ] }, // No formatting /* { 'type': 'menu', 'indicator': 'down', 'include': [ { 'group': 'format' } ], 'promote': [ 'paragraph' ], 'demote': [ 'preformatted', 'heading1' ] },*/ // Style { 'type': 'list', 'icon': 'text-style', 'indicator': 'down', 'include': [ { 'group': 'textStyle' }, 'clear' ], 'promote': [ 'bold', 'italic' ], 'demote': [ 'strikethrough', 'code', 'underline', 'clear' ] }, // Link { 'include': [ 'link' ] }, // No structure /* { 'type': 'bar', 'include': [ 'number', 'bullet', 'outdent', 'indent' ] },*/ // Insert { 'label': 'visualeditor-toolbar-insert', 'indicator': 'down', 'include': '*', 'exclude': [ { 'group': 'format' }, { 'group': 'structure' }, 'referenceList', 'gallery' ], 'demote': [ 'specialcharacter' ] } ]; ve.ui.MWMediaEditDialog.static.surfaceCommands = [ 'undo', 'redo', 'bold', 'italic', 'link', 'clear', 'underline', 'subscript', 'superscript', 'pasteSpecial' ]; ve.ui.MWMediaEditDialog.static.pasteRules = ve.extendObject( ve.copy( ve.init.mw.ViewPageTarget.static.pasteRules ), { 'all': { 'blacklist': OO.simpleArrayUnion( ve.getProp( ve.init.mw.ViewPageTarget.static.pasteRules, 'all', 'blacklist' ) || [], [ // Tables (but not lists) are possible in wikitext with a leading // line break but we prevent creating these with the UI 'list', 'listItem', 'definitionList', 'definitionListItem', 'table', 'tableCaption', 'tableSection', 'tableRow', 'tableCell' ] ), // Headings are also possible, but discouraged 'conversions': { 'mwHeading': 'paragraph' } } } ); /* Methods */ /** * @inheritdoc */ ve.ui.MWMediaEditDialog.prototype.initialize = function () { // Parent method ve.ui.MWDialog.prototype.initialize.call( this ); // TODO: Create a ve-wide spinner class instead of the local // classes using spinners this.$spinner = this.$( '
' ).addClass( 've-specialchar-spinner' ); // Set up the booklet layout this.bookletLayout = new OO.ui.BookletLayout( { '$': this.$, 'outlined': true } ); this.generalSettingsPage = new OO.ui.PageLayout( 'general', { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-media-page-general' ), 'icon': 'parameter' } ); this.advancedSettingsPage = new OO.ui.PageLayout( 'advanced', { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-media-page-advanced' ), 'icon': 'parameter' } ); this.bookletLayout.addPages( [ this.generalSettingsPage, this.advancedSettingsPage ] ); // Define fieldsets for image settings // Caption this.captionFieldset = new OO.ui.FieldsetLayout( { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-media-content-section' ), 'icon': 'parameter' } ); // Size this.sizeFieldset = new OO.ui.FieldsetLayout( { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-media-size-section' ), 'icon': 'parameter' } ); this.sizeErrorLabel = new OO.ui.InputLabelWidget( { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-media-size-originalsize-error' ) } ); this.sizeWidget = new ve.ui.MediaSizeWidget( { '$': this.$ } ); this.sizeFieldset.$element.append( [ this.sizeWidget.$element, this.sizeErrorLabel.$element, this.$spinner ] ); this.sizeErrorLabel.$element.hide(); this.$spinner.hide(); this.applyButton = new OO.ui.ButtonWidget( { '$': this.$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary'] } ); // Events this.applyButton.connect( this, { 'click': [ 'close', { 'action': 'apply' } ] } ); // Initialization this.generalSettingsPage.$element.append( this.captionFieldset.$element ); this.advancedSettingsPage.$element.append( this.sizeFieldset.$element ); this.$body.append( this.bookletLayout.$element ); this.$foot.append( this.applyButton.$element ); }; /** * Get the original size of the media object from the API, if it exists * @returns {jQuery.Promise} */ ve.ui.MWMediaEditDialog.prototype.getOriginalDimensions = function () { var index = this.store.indexOfHash( this.constructor.static.getSizeHash( this.filename ) ), deferred = $.Deferred(); if ( index ) { // The image size is already cached deferred.resolve( this.store.value( index ) ); } else { // Look for the media size through the API $.ajax( { 'url': mw.util.wikiScript( 'api' ), 'data': { 'action': 'query', 'prop': 'imageinfo', 'indexpageids': '1', 'iiprop': 'size|mediatype', 'format': 'json', 'titles': this.filename }, 'dataType': 'json', 'type': 'POST', // Wait up to 100 seconds before giving up 'timeout': 100000, 'cache': false } ) .done( function ( resp ) { var imginfo = resp.query.pages[ resp.query.pageids[0] ]; // Resolve with the size parameters deferred.resolve( { 'height': imginfo.imageinfo[0].height, 'width': imginfo.imageinfo[0].width, 'mediatype': imginfo.imageinfo[0].mediatype } ); } ) .fail( function () { deferred.resolve( false ); } ); } return deferred.promise(); }; /** * @inheritdoc */ ve.ui.MWMediaEditDialog.prototype.setup = function ( data ) { var attrs, newDoc, originalSize, resource, dimensions = {}, doc = this.surface.getModel().getDocument(); // Parent method ve.ui.MWDialog.prototype.setup.call( this, data ); // Properties this.mediaNode = this.surface.getView().getFocusedNode().getModel(); this.captionNode = this.mediaNode.getCaptionNode(); this.store = this.surface.getModel().getDocument().getStore(); // Strip the raw filename up to the 'File:' namespage resource = this.mediaNode.getAttribute( 'resource' ); this.filename = resource.substring( resource.indexOf( 'File:' ) ); if ( this.captionNode && this.captionNode.getLength() > 0 ) { newDoc = doc.cloneFromRange( this.captionNode.getRange() ); } else { newDoc = [ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } }, { 'type': '/paragraph' }, { 'type': 'internalList' }, { 'type': '/internalList' } ]; } this.captionSurface = new ve.ui.SurfaceWidget( newDoc, { '$': this.$, 'tools': this.constructor.static.toolbarGroups, 'commands': this.constructor.static.surfaceCommands, 'pasteRules': this.constructor.static.pasteRules } ); attrs = this.mediaNode.getAttributes(); // Show the spinner this.$spinner.show(); // Save original size for later calculations this.getOriginalDimensions().done( ve.bind( function ( sizeObj ) { if ( sizeObj && sizeObj.width && sizeObj.height ) { // Set the original dimensions in the widget this.sizeWidget.setOriginalDimensions( { 'width': sizeObj.width, 'height': sizeObj.height } ); // Check if we need to limit the size if ( sizeObj.mediatype === 'BITMAP' ) { // Set the max dimensions this.sizeWidget.setMaxDimensions( { 'width': sizeObj.width, 'height': sizeObj.height } ); } // Cache the originalSize and mediatype originalSize = { 'height': sizeObj.height, 'width': sizeObj.width, 'mediatype': sizeObj.mediatype }; this.store.index( originalSize, this.constructor.static.getSizeHash( this.filename ) ); } else { // Original dimensions couldn't be fetched. Display an error message this.sizeErrorLabel.$element.hide(); } // Set initial size in inputs dimensions = {}; if ( attrs.height !== undefined && Number( attrs.height ) > 0 ) { dimensions.height = attrs.height; } if ( attrs.width !== undefined && Number( attrs.width ) > 0 ) { dimensions.width = attrs.width; } this.sizeWidget.setDimensions( dimensions ); this.$spinner.hide(); }, this ) ); // Initialization this.captionFieldset.$element.append( this.captionSurface.$element ); this.captionSurface.initialize(); }; /** * @inheritdoc */ ve.ui.MWMediaEditDialog.prototype.teardown = function ( data ) { var newDoc, doc, attrs = {}, surfaceModel = this.surface.getModel(); // Data initialization data = data || {}; if ( data.action === 'apply' ) { newDoc = this.captionSurface.getSurface().getModel().getDocument(); doc = surfaceModel.getDocument(); if ( !this.captionNode ) { // Insert a new caption at the beginning of the image node surfaceModel.getFragment() .adjustRange( 1 ) .collapseRangeToStart() .insertContent( [ { 'type': 'mwImageCaption' }, { 'type': '/mwImageCaption' } ] ); this.captionNode = this.mediaNode.getCaptionNode(); } // Replace the contents of the caption surfaceModel.change( ve.dm.Transaction.newFromRemoval( doc, this.captionNode.getRange(), true ) ); surfaceModel.change( ve.dm.Transaction.newFromDocumentInsertion( doc, this.captionNode.getRange().start, newDoc ) ); // Change attributes only if the values are valid if ( this.sizeWidget.isValid() ) { attrs = this.sizeWidget.getDimensions(); } surfaceModel.change( ve.dm.Transaction.newFromAttributeChanges( doc, this.mediaNode.getOffset(), attrs ) ); } // Clean size values this.sizeWidget.clear(); // Cleanup this.captionSurface.destroy(); this.captionSurface = null; this.captionNode = null; // Parent method ve.ui.MWDialog.prototype.teardown.call( this, data ); }; /* Static methods */ /** * Get the store hash for the original dimensions of a given filename * * @param {string} filename Filename * @returns {string} Store hash */ ve.ui.MWMediaEditDialog.static.getSizeHash = function ( filename ) { return 'MWOriginalSize:' + filename; }; /* Registration */ ve.ui.dialogFactory.register( ve.ui.MWMediaEditDialog );