2014-04-25 11:43:42 +00:00
|
|
|
( function ( $, mw ) {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class mw.popups.render.article
|
|
|
|
* @singleton
|
|
|
|
*/
|
|
|
|
var article = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Size constants for popup images
|
|
|
|
* @property SIZES
|
|
|
|
*/
|
|
|
|
article.SIZES = {
|
|
|
|
portraitImage: {
|
|
|
|
h: 250, // Exact height
|
|
|
|
w: 203 // Max width
|
|
|
|
},
|
|
|
|
landscapeImage: {
|
|
|
|
h: 200, // Max height
|
|
|
|
w: 300 // Exact Width
|
|
|
|
},
|
|
|
|
landscapePopupWidth: 450, // Exact width of a landscape popup
|
|
|
|
portraitPopupWidth: 300 // Exact width of a portrait popup
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send an API request and cache the jQuery element
|
|
|
|
*
|
|
|
|
* @param {jQuery} link
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
*/
|
|
|
|
article.init = function ( link ) {
|
|
|
|
var
|
|
|
|
href = link.attr( 'href' ),
|
|
|
|
title = link.data( 'title' ),
|
|
|
|
deferred = $.Deferred();
|
|
|
|
|
|
|
|
mw.popups.render.currentRequest = mw.popups.api.get( {
|
|
|
|
action: 'query',
|
|
|
|
prop: 'extracts|pageimages|revisions|info',
|
|
|
|
redirects: 'true',
|
|
|
|
exintro: 'true',
|
|
|
|
exsentences: 2,
|
|
|
|
// there is an added geometric limit on .mwe-popups-extract
|
|
|
|
// so that text does not overflow from the card
|
|
|
|
explaintext: 'true',
|
|
|
|
piprop: 'thumbnail',
|
|
|
|
pithumbsize: 300,
|
|
|
|
rvprop: 'timestamp',
|
|
|
|
inprop: 'watched',
|
|
|
|
indexpageids: true,
|
|
|
|
titles: title
|
|
|
|
} );
|
|
|
|
|
|
|
|
mw.popups.render.currentRequest.done( function ( re ) {
|
|
|
|
mw.popups.render.currentRequest = undefined;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!re.query.pages[ re.query.pageids[ 0 ] ].extract ||
|
|
|
|
re.query.pages[ re.query.pageids[ 0 ] ].extract === ''
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
mw.popups.render.cache[ href ] = {};
|
|
|
|
mw.popups.render.cache[ href ].popup = article.createPopup( re, href );
|
|
|
|
mw.popups.render.cache[ href ].getOffset = article.getOffset;
|
|
|
|
mw.popups.render.cache[ href ].getClasses = article.getClasses;
|
|
|
|
mw.popups.render.cache[ href ].process = article.processPopup;
|
|
|
|
deferred.resolve();
|
|
|
|
} );
|
|
|
|
|
|
|
|
return deferred.promise();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a thumbnail object based on the ratio of the image
|
|
|
|
* Uses an SVG image where available to add the triangle/pokey
|
|
|
|
* mask on the image. Crops and resizes the SVG image so that
|
|
|
|
* is fits inside a rectangle of a particular size.
|
|
|
|
*
|
|
|
|
* @method createPopup
|
|
|
|
* @param {Object} re
|
|
|
|
* @param {String} href
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
article.createPopup = function ( re, href ) {
|
|
|
|
var $a,
|
|
|
|
page = re.query.pages[ re.query.pageids[ 0 ] ],
|
|
|
|
$contentbox = $( '<div>' )
|
|
|
|
.addClass( 'mwe-popups-extract' )
|
|
|
|
.text( page.extract ),
|
|
|
|
thumbnail = page.thumbnail,
|
|
|
|
tall = thumbnail && thumbnail.height > thumbnail.width,
|
|
|
|
$thumbnail = article.createThumbnail( thumbnail, tall ),
|
|
|
|
timestamp = new Date( page.revisions[ 0 ].timestamp ),
|
|
|
|
timediff = new Date() - timestamp,
|
|
|
|
oneDay = 1000 * 60 * 60 * 24,
|
|
|
|
timestampclass = ( timediff < oneDay ) ?
|
|
|
|
'mwe-popups-timestamp-recent' :
|
|
|
|
'mwe-popups-timestamp-older',
|
|
|
|
$timestamp = $( '<div>' )
|
|
|
|
.addClass( timestampclass )
|
|
|
|
.append(
|
|
|
|
$( '<span>' ).text( mw.message( 'popups-last-edited',
|
|
|
|
moment( timestamp ).fromNow() ).text() )
|
|
|
|
);
|
|
|
|
|
|
|
|
$a = $( '<a>' )
|
|
|
|
.append( $thumbnail, $contentbox, $timestamp )
|
|
|
|
.attr( 'href', href )
|
|
|
|
.on( 'click', mw.popups.eventLogging.logClick );
|
|
|
|
|
|
|
|
mw.popups.render.cache[ href ].settings = {
|
|
|
|
'tall': ( tall === undefined ) ? false : tall,
|
|
|
|
'thumbnail': ( thumbnail === undefined ) ? false : thumbnail
|
|
|
|
};
|
|
|
|
|
|
|
|
return $a;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use createElementNS to create the svg:image tag as jQuery
|
|
|
|
* uses createElement instead. Some browsers map the `image` tag
|
|
|
|
* to `img` tag, thus an `svg:image` is required.
|
|
|
|
*
|
|
|
|
* @method createSVGTag
|
|
|
|
* @param {String} tag
|
|
|
|
* @return {Object}
|
|
|
|
*/
|
|
|
|
article.createSVGTag = function ( tag ) {
|
|
|
|
return document.createElementNS( 'http://www.w3.org/2000/svg', tag );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a thumbnail object based on the ratio of the image
|
|
|
|
* Uses an SVG image where available to add the triangle/pokey
|
|
|
|
* mask on the image. Crops and resizes the SVG image so that
|
|
|
|
* is fits inside a rectangle of a particular size.
|
|
|
|
*
|
|
|
|
* @method createThumbnail
|
|
|
|
* @param {Object} thumbnail
|
|
|
|
* @param {boolean} tall
|
|
|
|
* @return {Object} jQuery DOM element of the thumbnail
|
|
|
|
*/
|
|
|
|
article.createThumbnail = function ( thumbnail, tall ) {
|
2014-04-29 08:47:50 +00:00
|
|
|
var svg = mw.popups.supportsSVG;
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
if ( !thumbnail ) {
|
|
|
|
return $( '<span>' );
|
|
|
|
}
|
|
|
|
|
2014-04-29 08:47:50 +00:00
|
|
|
if ( tall && svg ) {
|
|
|
|
return article.createSvgImageThumbnail(
|
|
|
|
'mwe-popups-is-not-tall',
|
|
|
|
thumbnail.source,
|
|
|
|
( thumbnail.width > article.SIZES.portraitImage.w ) ?
|
|
|
|
( ( thumbnail.width - article.SIZES.portraitImage.w ) / -2 ) :
|
|
|
|
( article.SIZES.portraitImage.w - thumbnail.width ),
|
|
|
|
( thumbnail.height > article.SIZES.portraitImage.h ) ?
|
|
|
|
( ( thumbnail.height - article.SIZES.portraitImage.h ) / -2 ) :
|
|
|
|
0,
|
|
|
|
thumbnail.width,
|
|
|
|
thumbnail.height,
|
|
|
|
article.SIZES.portraitImage.w,
|
|
|
|
article.SIZES.portraitImage.h
|
|
|
|
);
|
|
|
|
}
|
2014-04-25 11:43:42 +00:00
|
|
|
|
2014-04-29 08:47:50 +00:00
|
|
|
if ( tall && !svg ) {
|
|
|
|
return article.createImgThumbnail( 'mwe-popups-is-tall', thumbnail.source );
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
|
2014-04-29 08:47:50 +00:00
|
|
|
if ( !tall && svg ) {
|
|
|
|
return article.createSvgImageThumbnail(
|
|
|
|
'mwe-popups-is-not-tall',
|
|
|
|
thumbnail.source,
|
|
|
|
0,
|
|
|
|
( thumbnail.height > article.SIZES.landscapeImage.h ) ?
|
|
|
|
( ( thumbnail.height - article.SIZES.landscapeImage.h ) / -2 ) :
|
|
|
|
0,
|
|
|
|
thumbnail.width,
|
|
|
|
thumbnail.height,
|
|
|
|
article.SIZES.landscapeImage.w + 3,
|
|
|
|
( thumbnail.height > article.SIZES.landscapeImage.h ) ?
|
|
|
|
article.SIZES.landscapeImage.h :
|
|
|
|
thumbnail.height,
|
|
|
|
'mwe-popups-mask'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !tall && !svg ) {
|
|
|
|
return article.createImgThumbnail( 'mwe-popups-is-not-tall', thumbnail.source );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the `svg:image` object for thumbnail
|
|
|
|
*
|
|
|
|
* @method createSvgImageThumbnail
|
|
|
|
* @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}
|
|
|
|
*/
|
|
|
|
article.createSvgImageThumbnail = function (
|
|
|
|
className, url, x, y, thumbnailWidth, thumbnailHeight, width, height, clipPath
|
|
|
|
) {
|
|
|
|
var $thumbnailSVGImage, $thumbnail;
|
|
|
|
|
|
|
|
$thumbnailSVGImage = $( article.createSVGTag( 'image' ) );
|
|
|
|
$thumbnailSVGImage
|
|
|
|
.addClass( className )
|
|
|
|
.attr( {
|
|
|
|
'xlink:href': url,
|
|
|
|
x: x,
|
|
|
|
y: y,
|
|
|
|
width: thumbnailWidth,
|
|
|
|
height: thumbnailHeight,
|
|
|
|
'clip-path': 'url(#' + clipPath + ')'
|
|
|
|
} );
|
|
|
|
|
|
|
|
$thumbnail = $( '<svg>' )
|
|
|
|
.attr( {
|
|
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
|
|
width: width,
|
|
|
|
height: height
|
|
|
|
} )
|
|
|
|
.append( $thumbnailSVGImage );
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
return $thumbnail;
|
|
|
|
};
|
|
|
|
|
2014-04-29 08:47:50 +00:00
|
|
|
/**
|
|
|
|
* Returns the `img` object for thumbnail
|
|
|
|
*
|
|
|
|
* @method createImgThumbnail
|
|
|
|
* @param {String} className
|
|
|
|
* @param {String} url
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
article.createImgThumbnail = function ( className, url ) {
|
|
|
|
return $( '<div>' )
|
|
|
|
.addClass( className )
|
|
|
|
.css( 'background-image', 'url(' + url + ')' );
|
|
|
|
};
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
/**
|
|
|
|
* Positions the popup based on the mouse position and popup size
|
|
|
|
*
|
|
|
|
* @method getOffset
|
|
|
|
* @param {jQuery} link
|
|
|
|
* @param {Object} event
|
|
|
|
* @return {Object} This can be passed to `.css()` to position the element
|
|
|
|
*/
|
|
|
|
article.getOffset = function ( link, event ) {
|
|
|
|
var
|
|
|
|
href = link.attr( 'href' ),
|
|
|
|
flipped = false,
|
|
|
|
settings = mw.popups.render.cache[ href ].settings,
|
|
|
|
offsetTop = ( event.pageY ) ?
|
|
|
|
event.pageY + 20 :
|
|
|
|
link.offset().top + link.height() + 9,
|
|
|
|
offsetLeft = ( event.pageX ) ?
|
|
|
|
event.pageX :
|
|
|
|
link.offset().left;
|
|
|
|
|
|
|
|
if ( offsetLeft > ( $( window ).width() / 2 ) ) {
|
|
|
|
offsetLeft += ( !event.pageX ) ? link.width() : 0;
|
|
|
|
offsetLeft -= ( !settings.tall ) ?
|
|
|
|
article.SIZES.portraitPopupWidth :
|
|
|
|
article.SIZES.landscapePopupWidth;
|
|
|
|
flipped = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( event.pageX ) {
|
|
|
|
offsetLeft += ( flipped ) ? 20 : -20;
|
|
|
|
}
|
|
|
|
|
|
|
|
mw.popups.render.cache[ href ].settings.flipped = flipped;
|
|
|
|
|
|
|
|
return {
|
|
|
|
top: offsetTop + 'px',
|
|
|
|
left: offsetLeft + 'px'
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of classes based on the size and setting of the popup
|
|
|
|
*
|
|
|
|
* @method getClassses
|
|
|
|
* @param {jQuery} link
|
|
|
|
* @return {Array} List of classes to applied to the parent `div`
|
|
|
|
*/
|
|
|
|
article.getClasses = function ( link ) {
|
|
|
|
var
|
|
|
|
classes = [],
|
|
|
|
cache = mw.popups.render.cache [ link.attr( 'href' ) ],
|
|
|
|
tall = cache.settings.tall,
|
|
|
|
thumbnail = cache.settings.thumbnail,
|
|
|
|
flipped = cache.settings.flipped;
|
|
|
|
|
|
|
|
if ( flipped ) {
|
|
|
|
classes.push( 'flipped' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !thumbnail || tall ) {
|
|
|
|
classes.push( 'mwe-popups-no-image-tri' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( thumbnail && !tall ) {
|
|
|
|
classes.push( 'mwe-popups-image-tri' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( tall ) {
|
|
|
|
classes.push( 'mwe-popups-is-tall' );
|
|
|
|
} else {
|
|
|
|
classes.push( 'mwe-popups-is-not-tall' );
|
|
|
|
}
|
|
|
|
|
|
|
|
return classes;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Processed the popup div after it has been displayed
|
|
|
|
* to correctly render the triangle/pokeys
|
|
|
|
*
|
|
|
|
* @method processPopups
|
|
|
|
* @param {jQuery} link
|
|
|
|
*/
|
|
|
|
article.processPopup = function ( link ) {
|
|
|
|
var
|
|
|
|
cache = mw.popups.render.cache [ link.attr( 'href' ) ],
|
|
|
|
tall = cache.settings.tall,
|
|
|
|
thumbnail = cache.settings.thumbnail,
|
|
|
|
flipped = cache.settings.flipped;
|
|
|
|
|
|
|
|
if ( flipped && thumbnail ) {
|
|
|
|
if ( !tall ) {
|
|
|
|
mw.popups.$popup
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );
|
|
|
|
} else {
|
|
|
|
mw.popups.$popup
|
|
|
|
.removeClass( 'mwe-popups-no-image-tri' )
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
mw.popups.render.article = article;
|
|
|
|
|
|
|
|
} ) ( jQuery, mediaWiki );
|