2013-10-15 12:18:11 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor DataModel MWImageNode class.
|
|
|
|
*
|
2014-01-05 12:05:05 +00:00
|
|
|
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
|
2013-10-15 12:18:11 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2014-08-19 12:25:40 +00:00
|
|
|
* DataModel MediaWiki image node.
|
2013-10-15 12:18:11 +00:00
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @abstract
|
|
|
|
* @extends ve.dm.GeneratedContentNode
|
2014-09-17 00:33:39 +00:00
|
|
|
* @mixins ve.dm.FocusableNode
|
2014-04-10 00:26:48 +00:00
|
|
|
* @mixins ve.dm.ResizableNode
|
|
|
|
*
|
2013-10-15 12:18:11 +00:00
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode = function VeDmMWImageNode() {
|
|
|
|
// Parent constructor
|
|
|
|
ve.dm.GeneratedContentNode.call( this );
|
2014-09-17 00:33:39 +00:00
|
|
|
|
|
|
|
// Mixin constructors
|
2014-04-10 00:26:48 +00:00
|
|
|
ve.dm.ResizableNode.call( this );
|
2014-09-17 00:33:39 +00:00
|
|
|
ve.dm.FocusableNode.call( this );
|
2014-04-10 00:26:48 +00:00
|
|
|
|
|
|
|
this.scalablePromise = null;
|
|
|
|
|
|
|
|
// Use 'bitmap' as default media type until we can
|
|
|
|
// fetch the actual media type from the API
|
|
|
|
this.mediaType = 'BITMAP';
|
|
|
|
// Get wiki defaults
|
|
|
|
this.svgMaxSize = mw.config.get( 'wgVisualEditor' ).svgMaxSize;
|
|
|
|
|
|
|
|
// Initialize
|
2014-07-15 18:43:54 +00:00
|
|
|
this.constructor.static.syncScalableToType(
|
|
|
|
this.getAttribute( 'type' ),
|
|
|
|
this.mediaType,
|
|
|
|
this.getScalable()
|
|
|
|
);
|
2014-04-10 00:26:48 +00:00
|
|
|
|
|
|
|
// Events
|
2014-08-22 20:50:48 +00:00
|
|
|
this.connect( this, { attributeChange: 'onAttributeChange' } );
|
2013-10-15 12:18:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2013-10-11 21:44:09 +00:00
|
|
|
OO.inheritClass( ve.dm.MWImageNode, ve.dm.GeneratedContentNode );
|
2013-10-15 12:18:11 +00:00
|
|
|
|
2014-09-17 00:33:39 +00:00
|
|
|
OO.mixinClass( ve.dm.MWImageNode, ve.dm.FocusableNode );
|
|
|
|
|
2014-04-10 00:26:48 +00:00
|
|
|
OO.mixinClass( ve.dm.MWImageNode, ve.dm.ResizableNode );
|
|
|
|
|
2014-08-19 12:25:40 +00:00
|
|
|
/* Static methods */
|
|
|
|
|
|
|
|
ve.dm.MWImageNode.static.getHashObject = function ( dataElement ) {
|
|
|
|
return {
|
2014-08-22 20:50:48 +00:00
|
|
|
type: dataElement.type,
|
|
|
|
resource: dataElement.attributes.resource,
|
|
|
|
width: dataElement.attributes.width,
|
|
|
|
height: dataElement.attributes.height
|
2014-08-19 12:25:40 +00:00
|
|
|
};
|
|
|
|
};
|
2014-01-26 15:02:07 +00:00
|
|
|
|
2014-07-25 02:28:07 +00:00
|
|
|
/**
|
|
|
|
* Take the given dimensions and scale them to thumbnail size.
|
|
|
|
* @param {Object} dimensions Width and height of the image
|
|
|
|
* @param {string} [mediaType] Media type 'DRAWING' or 'BITMAP'
|
|
|
|
* @return {Object} The new width and height of the scaled image
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.static.scaleToThumbnailSize = function ( dimensions, mediaType ) {
|
|
|
|
var defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' ).defaultUserOptions.defaultthumbsize;
|
|
|
|
|
|
|
|
mediaType = mediaType || 'BITMAP';
|
|
|
|
|
|
|
|
if ( dimensions.width && dimensions.height ) {
|
|
|
|
// Use dimensions
|
|
|
|
// Resize to default thumbnail size, but only if the image itself
|
|
|
|
// isn't smaller than the default size
|
|
|
|
// For svg/drawings, the default wiki size is always applied
|
|
|
|
if ( dimensions.width > defaultThumbSize || mediaType === 'DRAWING' ) {
|
|
|
|
return ve.dm.Scalable.static.getDimensionsFromValue( {
|
|
|
|
width: defaultThumbSize
|
|
|
|
}, dimensions.width / dimensions.height );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dimensions;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Translate the image dimensions into new ones according to the bounding box.
|
|
|
|
* @param {Object} imageDimension Width and height of the image
|
|
|
|
* @param {Object} boundingBox The limit of the bounding box
|
|
|
|
* @param {boolean} adhereToWidth Resize the image using width consideration only
|
|
|
|
* @returns {Object|null} The new width and height of the scaled image or null if
|
|
|
|
* the given dimensions are missing width or height values and cannot be computed.
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.static.resizeToBoundingBox = function ( imageDimensions, boundingBox, adhereToWidth ) {
|
|
|
|
var limitNumber, dimCalcObject;
|
|
|
|
|
|
|
|
if ( $.isEmptyObject( boundingBox ) ) {
|
|
|
|
return imageDimensions;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( imageDimensions.width && imageDimensions.height) {
|
|
|
|
// First, find the bounding box number (which is the bigger
|
|
|
|
// of the two values)
|
|
|
|
if ( boundingBox.width > boundingBox.height || adhereToWidth ) {
|
|
|
|
limitNumber = boundingBox.width;
|
|
|
|
} else {
|
|
|
|
limitNumber = boundingBox.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second, check which of the image dimensions is bigger and apply
|
|
|
|
// the limit to it
|
|
|
|
if ( imageDimensions.width >= imageDimensions.height || adhereToWidth ) {
|
|
|
|
// Check if the width is not smaller than the limit number
|
|
|
|
if ( imageDimensions.width <= limitNumber ) {
|
|
|
|
return imageDimensions;
|
|
|
|
}
|
|
|
|
// Limit by width
|
|
|
|
dimCalcObject = { width: limitNumber };
|
|
|
|
} else {
|
|
|
|
// Check if the height is not smaller than the limit number
|
|
|
|
if ( imageDimensions.height <= limitNumber ) {
|
|
|
|
return imageDimensions;
|
|
|
|
}
|
|
|
|
dimCalcObject = { height: limitNumber };
|
|
|
|
}
|
|
|
|
|
|
|
|
return ve.dm.Scalable.static.getDimensionsFromValue(
|
|
|
|
dimCalcObject,
|
|
|
|
imageDimensions.width / imageDimensions.height
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-01-26 15:02:07 +00:00
|
|
|
/**
|
2014-06-04 18:20:37 +00:00
|
|
|
* Update image scalable properties according to the image type.
|
2014-01-26 15:02:07 +00:00
|
|
|
*
|
2014-04-10 00:26:48 +00:00
|
|
|
* @param {string} type The new image type
|
2014-07-15 18:43:54 +00:00
|
|
|
* @param {string} mediaType Image media type 'DRAWING' or 'BITMAP'
|
|
|
|
* @param {ve.dm.Scalable} scalable The scalable object to update
|
2014-01-26 15:02:07 +00:00
|
|
|
*/
|
2014-07-15 18:43:54 +00:00
|
|
|
ve.dm.MWImageNode.static.syncScalableToType = function ( type, mediaType, scalable ) {
|
2014-04-10 00:26:48 +00:00
|
|
|
var originalDimensions, dimensions,
|
2014-07-15 18:43:54 +00:00
|
|
|
defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' ).defaultUserOptions.defaultthumbsize;
|
2014-03-25 16:01:04 +00:00
|
|
|
|
2014-04-10 00:26:48 +00:00
|
|
|
originalDimensions = scalable.getOriginalDimensions();
|
|
|
|
|
2014-09-04 15:29:51 +00:00
|
|
|
// We can only set default dimensions if we have the original ones
|
|
|
|
if ( originalDimensions ) {
|
|
|
|
if ( type === 'thumb' || type === 'frameless' ) {
|
|
|
|
// Set the default size to that in the wiki configuration if
|
|
|
|
// 1. The original image width is not smaller than the default
|
|
|
|
// 2. If the image is an SVG drawing
|
|
|
|
if ( originalDimensions.width >= defaultThumbSize || mediaType === 'DRAWING' ) {
|
|
|
|
dimensions = ve.dm.Scalable.static.getDimensionsFromValue( {
|
|
|
|
width: defaultThumbSize
|
|
|
|
}, scalable.getRatio() );
|
|
|
|
} else {
|
|
|
|
dimensions = ve.dm.Scalable.static.getDimensionsFromValue(
|
|
|
|
originalDimensions,
|
|
|
|
scalable.getRatio()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
scalable.setDefaultDimensions( dimensions );
|
2014-04-10 00:26:48 +00:00
|
|
|
} else {
|
|
|
|
scalable.setDefaultDimensions( originalDimensions );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deal with maximum dimensions for images and drawings
|
2014-07-15 18:43:54 +00:00
|
|
|
if ( mediaType !== 'DRAWING' ) {
|
2014-04-10 00:26:48 +00:00
|
|
|
if ( originalDimensions ) {
|
|
|
|
scalable.setMaxDimensions( originalDimensions );
|
|
|
|
scalable.setEnforcedMax( true );
|
|
|
|
} else {
|
|
|
|
scalable.setEnforcedMax( false );
|
|
|
|
}
|
|
|
|
}
|
2014-06-26 19:40:20 +00:00
|
|
|
// TODO: Some day, when svgMaxSize works properly in MediaWiki
|
|
|
|
// we can add it back as max dimension consideration.
|
2014-04-10 00:26:48 +00:00
|
|
|
};
|
2014-07-17 19:55:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the scalable promise which fetches original dimensions from the API
|
|
|
|
* @param {string} filename The image filename whose details the scalable will represent
|
|
|
|
* @returns {jQuery.Promise} Promise which resolves after the image size details are fetched from the API
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.static.getScalablePromise = function ( filename ) {
|
|
|
|
var scalablePromise = $.Deferred();
|
|
|
|
// On the first call set off an async call to update the scalable's
|
|
|
|
// original dimensions from the API.
|
|
|
|
if ( ve.init.target ) {
|
|
|
|
ve.init.target.constructor.static.apiRequest(
|
|
|
|
{
|
2014-08-22 20:50:48 +00:00
|
|
|
action: 'query',
|
|
|
|
prop: 'imageinfo',
|
|
|
|
indexpageids: '1',
|
|
|
|
iiprop: 'size|mediatype',
|
|
|
|
titles: filename
|
2014-07-17 19:55:24 +00:00
|
|
|
},
|
2014-08-22 20:50:48 +00:00
|
|
|
{ type: 'POST' }
|
2014-07-17 19:55:24 +00:00
|
|
|
)
|
|
|
|
.done( function ( response ) {
|
|
|
|
var page = response.query && response.query.pages[response.query.pageids[0]],
|
|
|
|
info = page && page.imageinfo && page.imageinfo[0];
|
|
|
|
if ( info ) {
|
|
|
|
scalablePromise.resolve( info );
|
|
|
|
} else {
|
|
|
|
scalablePromise.reject();
|
|
|
|
}
|
|
|
|
} )
|
|
|
|
.fail( function () {
|
|
|
|
scalablePromise.reject();
|
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
scalablePromise.reject();
|
|
|
|
}
|
|
|
|
return scalablePromise;
|
|
|
|
};
|
|
|
|
|
2014-08-19 12:25:40 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2014-04-10 00:26:48 +00:00
|
|
|
/**
|
|
|
|
* Respond to attribute change.
|
|
|
|
* Update the rendering of the 'align', src', 'width' and 'height' attributes
|
|
|
|
* when they change in the model.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string} key Attribute key
|
|
|
|
* @param {string} from Old value
|
|
|
|
* @param {string} to New value
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.onAttributeChange = function ( key, from, to ) {
|
|
|
|
if ( key === 'type' ) {
|
2014-07-15 18:43:54 +00:00
|
|
|
this.constructor.static.syncScalableToType( to, this.mediaType, this.getScalable() );
|
2014-01-26 15:02:07 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the normalised filename of the image
|
|
|
|
*
|
|
|
|
* @returns {string} Filename
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.getFilename = function () {
|
2014-05-28 19:21:06 +00:00
|
|
|
// Strip ./ stuff and decode URI encoding
|
2014-09-24 19:43:57 +00:00
|
|
|
var resource = this.getAttribute( 'resource' ) || '',
|
2014-08-21 01:16:13 +00:00
|
|
|
filename = resource.replace( /^(\.+\/)*/, '' );
|
2014-05-28 19:21:06 +00:00
|
|
|
|
|
|
|
// Protect against decodeURIComponent() throwing exceptions
|
|
|
|
try {
|
|
|
|
filename = decodeURIComponent( filename );
|
|
|
|
} catch ( e ) {
|
|
|
|
ve.log( 'URI decoding exception', e );
|
|
|
|
}
|
|
|
|
return filename;
|
2014-01-26 15:02:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the store hash for the original dimensions of the image
|
|
|
|
*
|
|
|
|
* @returns {string} Store hash
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.getSizeHash = function () {
|
|
|
|
return 'MWImageOriginalSize:' + this.getFilename();
|
|
|
|
};
|
|
|
|
|
2014-04-10 00:26:48 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.getScalable = function () {
|
2014-10-29 01:43:03 +00:00
|
|
|
var imageNode = this;
|
2014-07-17 19:55:24 +00:00
|
|
|
if ( !this.scalablePromise ) {
|
|
|
|
this.scalablePromise = ve.dm.MWImageNode.static.getScalablePromise( this.getFilename() )
|
2014-10-29 01:43:03 +00:00
|
|
|
.done( function ( info ) {
|
2014-07-17 19:55:24 +00:00
|
|
|
if ( info ) {
|
2014-10-29 01:43:03 +00:00
|
|
|
imageNode.getScalable().setOriginalDimensions( {
|
2014-08-22 20:50:48 +00:00
|
|
|
width: info.width,
|
|
|
|
height: info.height
|
2014-07-17 19:55:24 +00:00
|
|
|
} );
|
|
|
|
// Update media type
|
2014-10-29 01:43:03 +00:00
|
|
|
imageNode.mediaType = info.mediatype;
|
2014-07-17 19:55:24 +00:00
|
|
|
// Update according to type
|
2014-10-29 01:43:03 +00:00
|
|
|
imageNode.constructor.static.syncScalableToType(
|
|
|
|
imageNode.getAttribute( 'type' ),
|
|
|
|
imageNode.mediaType,
|
|
|
|
imageNode.getScalable()
|
2014-07-17 19:55:24 +00:00
|
|
|
);
|
|
|
|
}
|
2014-10-29 01:43:03 +00:00
|
|
|
} );
|
2014-07-17 19:55:24 +00:00
|
|
|
}
|
2014-04-10 00:26:48 +00:00
|
|
|
// Parent method
|
|
|
|
return ve.dm.ResizableNode.prototype.getScalable.call( this );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.createScalable = function () {
|
|
|
|
return new ve.dm.Scalable( {
|
2014-08-22 20:50:48 +00:00
|
|
|
currentDimensions: {
|
|
|
|
width: this.getAttribute( 'width' ),
|
|
|
|
height: this.getAttribute( 'height' )
|
2014-04-10 00:26:48 +00:00
|
|
|
},
|
2014-08-22 20:50:48 +00:00
|
|
|
minDimensions: {
|
|
|
|
width: 1,
|
|
|
|
height: 1
|
2014-04-10 00:26:48 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
};
|
2014-06-04 18:20:37 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get symbolic name of media type.
|
|
|
|
*
|
|
|
|
* Example values: "BITMAP" for JPEG or PNG images; "DRAWING" for SVG graphics
|
|
|
|
*
|
|
|
|
* @return {string|undefined} Symbolic media type name, or undefined if empty
|
|
|
|
*/
|
|
|
|
ve.dm.MWImageNode.prototype.getMediaType = function () {
|
|
|
|
return this.mediaType;
|
|
|
|
};
|