2018-03-08 20:38:23 +00:00
|
|
|
/**
|
|
|
|
* @module thumbnail
|
|
|
|
*/
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const SIZES = {
|
2018-03-08 20:38:23 +00:00
|
|
|
portraitImage: {
|
|
|
|
h: 250, // Exact height
|
|
|
|
w: 203 // Max width
|
|
|
|
},
|
|
|
|
landscapeImage: {
|
|
|
|
h: 200, // Max height
|
|
|
|
w: 320 // Exact Width
|
|
|
|
}
|
|
|
|
},
|
|
|
|
$ = jQuery;
|
|
|
|
|
|
|
|
export { SIZES };
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} ext.popups.Thumbnail
|
|
|
|
* @property {Element} el
|
|
|
|
* @property {Boolean} isTall Whether or not the thumbnail is portrait
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a thumbnail from the representation of a thumbnail returned by the
|
|
|
|
* PageImages MediaWiki API query module.
|
|
|
|
*
|
|
|
|
* If there's no thumbnail, the thumbnail is too small, or the thumbnail's URL
|
|
|
|
* contains characters that could be used to perform an
|
|
|
|
* [XSS attack via CSS](https://www.owasp.org/index.php/Testing_for_CSS_Injection_(OTG-CLIENT-005)),
|
|
|
|
* then `null` is returned.
|
|
|
|
*
|
|
|
|
* Extracted from `mw.popups.renderer.article.createThumbnail`.
|
|
|
|
*
|
|
|
|
* @param {Object} rawThumbnail
|
|
|
|
* @return {ext.popups.Thumbnail|null}
|
|
|
|
*/
|
|
|
|
export function createThumbnail( rawThumbnail ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
const devicePixelRatio = $.bracketedDevicePixelRatio();
|
2018-03-08 20:38:23 +00:00
|
|
|
|
|
|
|
if ( !rawThumbnail ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const tall = rawThumbnail.width < rawThumbnail.height;
|
|
|
|
const thumbWidth = rawThumbnail.width / devicePixelRatio;
|
|
|
|
const thumbHeight = rawThumbnail.height / devicePixelRatio;
|
2018-03-08 20:38:23 +00:00
|
|
|
|
|
|
|
if (
|
|
|
|
// Image too small for landscape display
|
|
|
|
( !tall && thumbWidth < SIZES.landscapeImage.w ) ||
|
|
|
|
// Image too small for portrait display
|
|
|
|
( tall && thumbHeight < SIZES.portraitImage.h ) ||
|
|
|
|
// These characters in URL that could inject CSS and thus JS
|
|
|
|
(
|
|
|
|
rawThumbnail.source.indexOf( '\\' ) > -1 ||
|
|
|
|
rawThumbnail.source.indexOf( '\'' ) > -1 ||
|
|
|
|
rawThumbnail.source.indexOf( '"' ) > -1
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-04-11 20:40:20 +00:00
|
|
|
let x, y, width, height;
|
2018-03-08 20:38:23 +00:00
|
|
|
if ( tall ) {
|
|
|
|
x = ( thumbWidth > SIZES.portraitImage.w ) ?
|
|
|
|
( ( thumbWidth - SIZES.portraitImage.w ) / -2 ) :
|
|
|
|
( SIZES.portraitImage.w - thumbWidth );
|
|
|
|
y = ( thumbHeight > SIZES.portraitImage.h ) ?
|
|
|
|
( ( thumbHeight - SIZES.portraitImage.h ) / -2 ) : 0;
|
|
|
|
width = SIZES.portraitImage.w;
|
|
|
|
height = SIZES.portraitImage.h;
|
|
|
|
} else {
|
|
|
|
x = 0;
|
|
|
|
y = ( thumbHeight > SIZES.landscapeImage.h ) ?
|
|
|
|
( ( thumbHeight - SIZES.landscapeImage.h ) / -2 ) : 0;
|
|
|
|
width = SIZES.landscapeImage.w + 3;
|
|
|
|
height = ( thumbHeight > SIZES.landscapeImage.h ) ?
|
|
|
|
SIZES.landscapeImage.h : thumbHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
el: createThumbnailElement(
|
|
|
|
tall ? 'mwe-popups-is-tall' : 'mwe-popups-is-not-tall',
|
|
|
|
rawThumbnail.source,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
thumbWidth,
|
|
|
|
thumbHeight,
|
|
|
|
width,
|
2018-04-11 20:40:20 +00:00
|
|
|
height
|
2018-03-08 20:38:23 +00:00
|
|
|
),
|
|
|
|
isTall: tall,
|
|
|
|
width: thumbWidth,
|
|
|
|
height: thumbHeight
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates the SVG image element that represents the thumbnail.
|
|
|
|
*
|
|
|
|
* This function is distinct from `createThumbnail` as it abstracts away some
|
|
|
|
* browser issues that are uncovered when manipulating elements across
|
|
|
|
* namespaces.
|
|
|
|
*
|
|
|
|
* @param {String} className
|
|
|
|
* @param {String} url
|
|
|
|
* @param {Number} x
|
|
|
|
* @param {Number} y
|
|
|
|
* @param {Number} thumbnailWidth
|
|
|
|
* @param {Number} thumbnailHeight
|
|
|
|
* @param {Number} width
|
|
|
|
* @param {Number} height
|
|
|
|
* @param {String} clipPath
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
export function createThumbnailElement(
|
2018-04-11 20:40:20 +00:00
|
|
|
className, url, x, y, thumbnailWidth, thumbnailHeight, width, height
|
2018-03-08 20:38:23 +00:00
|
|
|
) {
|
2018-03-19 19:39:41 +00:00
|
|
|
const nsSvg = 'http://www.w3.org/2000/svg',
|
2018-03-08 20:38:23 +00:00
|
|
|
nsXlink = 'http://www.w3.org/1999/xlink';
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const $thumbnailSVGImage = $( document.createElementNS( nsSvg, 'image' ) );
|
2018-03-08 20:38:23 +00:00
|
|
|
$thumbnailSVGImage[ 0 ].setAttributeNS( nsXlink, 'href', url );
|
|
|
|
$thumbnailSVGImage
|
|
|
|
.addClass( className )
|
|
|
|
.attr( {
|
2018-03-14 19:44:22 +00:00
|
|
|
x,
|
|
|
|
y,
|
2018-03-08 20:38:23 +00:00
|
|
|
width: thumbnailWidth,
|
2018-04-11 20:40:20 +00:00
|
|
|
height: thumbnailHeight
|
2018-03-08 20:38:23 +00:00
|
|
|
} );
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const $thumbnail = $( document.createElementNS( nsSvg, 'svg' ) )
|
2018-03-08 20:38:23 +00:00
|
|
|
.attr( {
|
|
|
|
xmlns: nsSvg,
|
2018-03-14 19:44:22 +00:00
|
|
|
width,
|
|
|
|
height
|
2018-03-08 20:38:23 +00:00
|
|
|
} )
|
|
|
|
.append( $thumbnailSVGImage );
|
|
|
|
|
|
|
|
return $thumbnail;
|
|
|
|
}
|