2014-04-25 11:43:42 +00:00
|
|
|
( function ( $, mw ) {
|
2014-05-19 22:48:32 +00:00
|
|
|
'use strict';
|
2014-04-25 11:43:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @class mw.popups.render.article
|
|
|
|
* @singleton
|
|
|
|
*/
|
2015-10-06 16:07:05 +00:00
|
|
|
var article = {},
|
|
|
|
$window = $( window );
|
2014-04-25 11:43:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2014-11-20 06:16:37 +00:00
|
|
|
portraitPopupWidth: 300, // Exact width of a portrait popup
|
|
|
|
pokeySize: 8 // Height of the triangle used to point at the link
|
2014-04-25 11:43:42 +00:00
|
|
|
};
|
|
|
|
|
2014-11-11 05:49:07 +00:00
|
|
|
/**
|
|
|
|
* Survey link, if any, for this renderer
|
|
|
|
* @property surveyLink
|
|
|
|
*/
|
|
|
|
article.surveyLink = mw.config.get( 'wgPopupsSurveyLink' );
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
/**
|
|
|
|
* Send an API request and cache the jQuery element
|
|
|
|
*
|
|
|
|
* @param {jQuery} link
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
*/
|
|
|
|
article.init = function ( link ) {
|
2015-03-25 20:53:27 +00:00
|
|
|
var href = link.attr( 'href' ),
|
|
|
|
title = mw.popups.getTitle( href ),
|
2015-05-03 12:28:59 +00:00
|
|
|
deferred = $.Deferred(),
|
|
|
|
scaledThumbSize = 300 * $.bracketedDevicePixelRatio();
|
2014-04-25 11:43:42 +00:00
|
|
|
|
2014-05-19 22:42:41 +00:00
|
|
|
if ( !title ) {
|
|
|
|
return deferred.reject().promise();
|
|
|
|
}
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups.render.currentRequest = mw.popups.api.get( {
|
|
|
|
action: 'query',
|
2015-11-04 16:53:47 +00:00
|
|
|
prop: 'info|extracts|pageimages|revisions',
|
2015-06-26 10:47:21 +00:00
|
|
|
formatversion: 2,
|
2015-06-26 07:07:06 +00:00
|
|
|
redirects: true,
|
|
|
|
exintro: true,
|
2015-04-06 06:01:31 +00:00
|
|
|
exsentences: 5,
|
2014-04-25 11:43:42 +00:00
|
|
|
// there is an added geometric limit on .mwe-popups-extract
|
|
|
|
// so that text does not overflow from the card
|
2015-06-26 07:07:06 +00:00
|
|
|
explaintext: true,
|
2014-04-25 11:43:42 +00:00
|
|
|
piprop: 'thumbnail',
|
2015-05-03 12:28:59 +00:00
|
|
|
pithumbsize: scaledThumbSize,
|
2014-04-25 11:43:42 +00:00
|
|
|
rvprop: 'timestamp',
|
2015-07-02 12:47:15 +00:00
|
|
|
titles: title,
|
|
|
|
smaxage: 300,
|
|
|
|
maxage: 300,
|
|
|
|
uselang: 'content'
|
2014-04-25 11:43:42 +00:00
|
|
|
} );
|
|
|
|
|
2014-05-19 22:39:04 +00:00
|
|
|
mw.popups.render.currentRequest.fail( deferred.reject );
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups.render.currentRequest.done( function ( re ) {
|
|
|
|
mw.popups.render.currentRequest = undefined;
|
|
|
|
|
|
|
|
if (
|
2014-06-18 20:57:19 +00:00
|
|
|
!re.query ||
|
2014-05-19 22:30:29 +00:00
|
|
|
!re.query.pages ||
|
2015-08-26 10:15:16 +00:00
|
|
|
!re.query.pages[ 0 ].extract ||
|
|
|
|
re.query.pages[ 0 ].extract === ''
|
2014-04-25 11:43:42 +00:00
|
|
|
) {
|
2015-04-21 17:10:29 +00:00
|
|
|
// Restore the title attribute and set flag
|
|
|
|
if ( link.data( 'dont-empty-title' ) !== true ) {
|
|
|
|
link
|
|
|
|
.attr( 'title', link.data( 'title' ) )
|
|
|
|
.removeData( 'title' )
|
|
|
|
.data( 'dont-empty-title', true );
|
|
|
|
}
|
2014-05-19 22:39:04 +00:00
|
|
|
deferred.reject();
|
|
|
|
return;
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mw.popups.render.cache[ href ] = {};
|
2015-08-26 10:15:16 +00:00
|
|
|
mw.popups.render.cache[ href ].popup = article.createPopup( re.query.pages[ 0 ], href );
|
2014-04-25 11:43:42 +00:00
|
|
|
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
|
2015-06-26 10:47:21 +00:00
|
|
|
* @param {Object} page Information about the linked page
|
2015-08-26 10:15:16 +00:00
|
|
|
* @param {string} href
|
2014-04-25 11:43:42 +00:00
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
2015-06-26 10:47:21 +00:00
|
|
|
article.createPopup = function ( page, href ) {
|
2014-06-17 11:08:05 +00:00
|
|
|
var $div,
|
|
|
|
$contentbox = $( '<a>' )
|
2015-11-04 16:53:47 +00:00
|
|
|
.attr( {
|
|
|
|
href: href,
|
|
|
|
lang: page.pagelanguagehtmlcode,
|
|
|
|
dir: page.pagelanguagedir
|
|
|
|
} )
|
2014-04-25 11:43:42 +00:00
|
|
|
.addClass( 'mwe-popups-extract' )
|
2014-12-02 08:08:47 +00:00
|
|
|
.append(
|
|
|
|
article.getProcessedElements( page.extract, page.title )
|
2014-05-10 14:03:13 +00:00
|
|
|
),
|
2014-04-25 11:43:42 +00:00
|
|
|
thumbnail = page.thumbnail,
|
|
|
|
tall = thumbnail && thumbnail.height > thumbnail.width,
|
|
|
|
$thumbnail = article.createThumbnail( thumbnail, tall ),
|
2015-08-26 10:15:16 +00:00
|
|
|
timestamp = new Date( page.revisions[ 0 ].timestamp ),
|
2014-04-25 11:43:42 +00:00
|
|
|
timediff = new Date() - timestamp,
|
|
|
|
oneDay = 1000 * 60 * 60 * 24,
|
|
|
|
timestampclass = ( timediff < oneDay ) ?
|
|
|
|
'mwe-popups-timestamp-recent' :
|
|
|
|
'mwe-popups-timestamp-older',
|
2014-11-11 05:49:07 +00:00
|
|
|
$settingsImage = $( '<a>' ).addClass( 'mwe-popups-icon mwe-popups-settings-icon' ),
|
|
|
|
$surveyImage,
|
2014-04-25 11:43:42 +00:00
|
|
|
$timestamp = $( '<div>' )
|
|
|
|
.addClass( timestampclass )
|
|
|
|
.append(
|
|
|
|
$( '<span>' ).text( mw.message( 'popups-last-edited',
|
2014-06-17 11:08:05 +00:00
|
|
|
moment( timestamp ).fromNow() ).text() ),
|
|
|
|
$settingsImage
|
2014-11-11 05:49:07 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if ( article.surveyLink ) {
|
|
|
|
$surveyImage = $( '<a>' )
|
|
|
|
.attr( 'href', article.surveyLink )
|
|
|
|
.attr( 'target', '_blank' )
|
|
|
|
.attr( 'title', mw.message( 'popups-send-feedback' ) )
|
|
|
|
.addClass( 'mwe-popups-icon mwe-popups-survey-icon' );
|
|
|
|
$timestamp.append( $surveyImage );
|
|
|
|
}
|
|
|
|
|
2014-06-17 11:08:05 +00:00
|
|
|
if ( $thumbnail.prop( 'tagName' ) !== 'SPAN' ) {
|
|
|
|
$thumbnail = $( '<a>' )
|
|
|
|
.addClass( 'mwe-popups-discreet' )
|
2014-04-25 11:43:42 +00:00
|
|
|
.attr( 'href', href )
|
2014-06-17 11:08:05 +00:00
|
|
|
.append( $thumbnail );
|
2014-11-05 08:52:44 +00:00
|
|
|
} else {
|
2015-03-05 07:54:35 +00:00
|
|
|
tall = thumbnail = undefined;
|
2014-06-17 11:08:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$div = $( '<div>' ).append( $thumbnail, $contentbox, $timestamp );
|
2014-04-25 11:43:42 +00:00
|
|
|
|
|
|
|
mw.popups.render.cache[ href ].settings = {
|
2015-08-26 10:15:16 +00:00
|
|
|
title: page.title,
|
|
|
|
tall: ( tall === undefined ) ? false : tall,
|
|
|
|
thumbnail: ( thumbnail === undefined ) ? false : thumbnail
|
2014-04-25 11:43:42 +00:00
|
|
|
};
|
|
|
|
|
2014-06-17 11:08:05 +00:00
|
|
|
return $div;
|
2014-04-25 11:43:42 +00:00
|
|
|
};
|
|
|
|
|
2014-05-10 14:03:13 +00:00
|
|
|
/**
|
2014-12-02 08:08:47 +00:00
|
|
|
* Returns an array of elements to be appended after removing parentheses
|
|
|
|
* and making the title in the extract bold.
|
2014-05-10 14:03:13 +00:00
|
|
|
*
|
2014-12-02 08:08:47 +00:00
|
|
|
* @method getProcessedElements
|
2015-08-26 10:15:16 +00:00
|
|
|
* @param {string} extract Should be unescaped
|
|
|
|
* @param {string} title Should be unescaped
|
2014-12-02 08:08:47 +00:00
|
|
|
* @return {Array} of elements to appended
|
2014-05-10 14:03:13 +00:00
|
|
|
*/
|
2014-12-02 08:08:47 +00:00
|
|
|
article.getProcessedElements = function ( extract, title ) {
|
|
|
|
var elements = [],
|
2015-10-02 20:58:26 +00:00
|
|
|
escapedTitle = mw.RegExp.escape( title ), // Escape RegExp elements
|
2016-04-13 15:54:42 +00:00
|
|
|
regExp = new RegExp( '(^|\\s)(' + escapedTitle + ')(|$)', 'i' ),
|
2014-12-02 08:08:47 +00:00
|
|
|
boldIdentifier = '<bi-' + Math.random() + '>',
|
|
|
|
snip = '<snip-' + Math.random() + '>';
|
|
|
|
|
2014-06-24 08:23:05 +00:00
|
|
|
// Remove text in parentheses along with the parentheses
|
|
|
|
extract = article.removeParensFromText( extract );
|
2016-04-13 15:54:42 +00:00
|
|
|
extract = extract.replace( /\s+/, ' ' ); // Remove extra white spaces
|
2014-12-02 08:08:47 +00:00
|
|
|
|
|
|
|
// Make title bold in the extract text
|
|
|
|
// As the extract is html escaped there can be no such string in it
|
|
|
|
// Also, the title is escaped of RegExp elements thus can't have "*"
|
|
|
|
extract = extract.replace( regExp, '$1' + snip + boldIdentifier + '$2' + snip + '$3' );
|
|
|
|
extract = extract.split( snip );
|
|
|
|
|
|
|
|
$.each( extract, function ( index, part ) {
|
|
|
|
if ( part.indexOf( boldIdentifier ) === 0 ) {
|
|
|
|
elements.push( $( '<b>' ).text( part.substring( boldIdentifier.length ) ) );
|
|
|
|
} else {
|
2015-03-26 08:12:01 +00:00
|
|
|
elements.push( document.createTextNode( part ) );
|
2014-12-02 08:08:47 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
return elements;
|
2014-05-10 14:03:13 +00:00
|
|
|
};
|
|
|
|
|
2014-05-28 12:03:18 +00:00
|
|
|
/**
|
2014-06-24 08:23:05 +00:00
|
|
|
* Removes content in parentheses from a string. Returns the original
|
|
|
|
* string as is if the parentheses are unbalanced or out or order. Does not
|
|
|
|
* remove extra spaces.
|
2014-05-28 12:03:18 +00:00
|
|
|
*
|
2014-06-24 08:23:05 +00:00
|
|
|
* @method removeParensFromText
|
2015-08-26 10:15:16 +00:00
|
|
|
* @param {string} string
|
|
|
|
* @return {string}
|
2014-05-28 12:03:18 +00:00
|
|
|
*/
|
2014-06-24 08:23:05 +00:00
|
|
|
article.removeParensFromText = function ( string ) {
|
2014-05-28 12:03:18 +00:00
|
|
|
var
|
|
|
|
ch,
|
|
|
|
newString = '',
|
2014-06-24 08:23:05 +00:00
|
|
|
level = 0,
|
2014-05-28 12:03:18 +00:00
|
|
|
i = 0;
|
|
|
|
|
2015-08-26 10:15:16 +00:00
|
|
|
for ( i; i < string.length; i++ ) {
|
2014-05-28 12:03:18 +00:00
|
|
|
ch = string.charAt( i );
|
|
|
|
|
2014-06-24 08:23:05 +00:00
|
|
|
if ( ch === ')' && level === 0 ) {
|
2014-05-28 12:03:18 +00:00
|
|
|
return string;
|
|
|
|
}
|
|
|
|
if ( ch === '(' ) {
|
2014-06-24 08:23:05 +00:00
|
|
|
level++;
|
2014-05-28 12:03:18 +00:00
|
|
|
continue;
|
|
|
|
} else if ( ch === ')' ) {
|
2014-06-24 08:23:05 +00:00
|
|
|
level--;
|
2014-05-28 12:03:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
2014-06-24 08:23:05 +00:00
|
|
|
if ( level === 0 ) {
|
2015-01-02 11:31:12 +00:00
|
|
|
// Remove leading spaces before brackets
|
|
|
|
if ( ch === ' ' && string.charAt( i + 1 ) === '(' ) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-05-28 12:03:18 +00:00
|
|
|
newString += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-24 08:23:05 +00:00
|
|
|
return ( level === 0 ) ? newString : string;
|
2014-05-28 12:03:18 +00:00
|
|
|
};
|
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2015-08-26 10:15:16 +00:00
|
|
|
* @param {string} tag
|
2014-04-25 11:43:42 +00:00
|
|
|
* @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 ) {
|
2015-05-03 12:28:59 +00:00
|
|
|
var thumbWidth, thumbHeight,
|
|
|
|
svg = mw.popups.supportsSVG,
|
|
|
|
devicePixelRatio = $.bracketedDevicePixelRatio();
|
|
|
|
|
|
|
|
// No thumbnail
|
|
|
|
if ( !thumbnail ) {
|
|
|
|
return $( '<span>' );
|
|
|
|
}
|
|
|
|
|
|
|
|
thumbWidth = thumbnail.width / devicePixelRatio;
|
|
|
|
thumbHeight = thumbnail.height / devicePixelRatio;
|
2014-04-29 08:47:50 +00:00
|
|
|
|
2014-11-05 08:52:44 +00:00
|
|
|
if (
|
|
|
|
// Image too small for landscape display
|
2015-05-03 12:28:59 +00:00
|
|
|
( !tall && thumbWidth < article.SIZES.landscapeImage.w ) ||
|
|
|
|
// Image too small for portrait display
|
|
|
|
( tall && thumbHeight < article.SIZES.portraitImage.h ) ||
|
2015-03-24 08:01:19 +00:00
|
|
|
// These characters in URL that could inject CSS and thus JS
|
|
|
|
(
|
|
|
|
thumbnail.source.indexOf( '\\' ) > -1 ||
|
|
|
|
thumbnail.source.indexOf( '\'' ) > -1 ||
|
|
|
|
thumbnail.source.indexOf( '\"' ) > -1
|
|
|
|
)
|
2014-11-05 08:52:44 +00:00
|
|
|
) {
|
2014-04-25 11:43:42 +00:00
|
|
|
return $( '<span>' );
|
|
|
|
}
|
|
|
|
|
2014-04-29 08:47:50 +00:00
|
|
|
if ( tall && svg ) {
|
|
|
|
return article.createSvgImageThumbnail(
|
|
|
|
'mwe-popups-is-not-tall',
|
|
|
|
thumbnail.source,
|
2015-05-03 12:28:59 +00:00
|
|
|
( thumbWidth > article.SIZES.portraitImage.w ) ?
|
|
|
|
( ( thumbWidth - article.SIZES.portraitImage.w ) / -2 ) :
|
|
|
|
( article.SIZES.portraitImage.w - thumbWidth ),
|
|
|
|
( thumbHeight > article.SIZES.portraitImage.h ) ?
|
|
|
|
( ( thumbHeight - article.SIZES.portraitImage.h ) / -2 ) :
|
2014-04-29 08:47:50 +00:00
|
|
|
0,
|
2015-05-03 12:28:59 +00:00
|
|
|
thumbWidth,
|
|
|
|
thumbHeight,
|
2014-04-29 08:47:50 +00:00
|
|
|
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,
|
2015-05-03 12:28:59 +00:00
|
|
|
( thumbHeight > article.SIZES.landscapeImage.h ) ?
|
|
|
|
( ( thumbHeight - article.SIZES.landscapeImage.h ) / -2 ) :
|
2014-04-29 08:47:50 +00:00
|
|
|
0,
|
2015-05-03 12:28:59 +00:00
|
|
|
thumbWidth,
|
|
|
|
thumbHeight,
|
2014-04-29 08:47:50 +00:00
|
|
|
article.SIZES.landscapeImage.w + 3,
|
2015-05-03 12:28:59 +00:00
|
|
|
( thumbHeight > article.SIZES.landscapeImage.h ) ?
|
2014-04-29 08:47:50 +00:00
|
|
|
article.SIZES.landscapeImage.h :
|
2015-05-03 12:28:59 +00:00
|
|
|
thumbHeight,
|
2014-04-29 08:47:50 +00:00
|
|
|
'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
|
2015-08-26 10:15:16 +00:00
|
|
|
* @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
|
2014-04-29 08:47:50 +00:00
|
|
|
* @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
|
2015-08-26 10:15:16 +00:00
|
|
|
* @param {string} className
|
|
|
|
* @param {string} url
|
2014-04-29 08:47:50 +00:00
|
|
|
* @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
|
2014-05-27 12:11:52 +00:00
|
|
|
* Default popup positioning is below and to the right of the mouse or link,
|
|
|
|
* unless flippedX or flippedY is true. flippedX and flippedY are cached.
|
2014-04-25 11:43:42 +00:00
|
|
|
*
|
|
|
|
* @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' ),
|
2014-04-30 11:13:05 +00:00
|
|
|
flippedX = false,
|
|
|
|
flippedY = false,
|
2014-04-25 11:43:42 +00:00
|
|
|
settings = mw.popups.render.cache[ href ].settings,
|
2014-05-27 12:11:52 +00:00
|
|
|
offsetTop = ( event.pageY ) ? // If it was a mouse event
|
|
|
|
// Position according to mouse
|
2015-10-06 16:07:05 +00:00
|
|
|
// Since client rectangles are relative to the viewport,
|
|
|
|
// take scroll position into account.
|
|
|
|
getClosestYPosition(
|
|
|
|
event.pageY - $window.scrollTop(),
|
|
|
|
link.get( 0 ).getClientRects(),
|
|
|
|
false
|
|
|
|
) + $window.scrollTop() + article.SIZES.pokeySize :
|
2014-05-27 12:11:52 +00:00
|
|
|
// Position according to link position or size
|
2015-10-06 16:07:05 +00:00
|
|
|
link.offset().top + link.height() + article.SIZES.pokeySize,
|
2014-04-30 11:13:05 +00:00
|
|
|
clientTop = ( event.clientY ) ?
|
|
|
|
event.clientY :
|
|
|
|
offsetTop,
|
2014-04-25 11:43:42 +00:00
|
|
|
offsetLeft = ( event.pageX ) ?
|
|
|
|
event.pageX :
|
|
|
|
link.offset().left;
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
// X Flip
|
2014-04-25 11:43:42 +00:00
|
|
|
if ( offsetLeft > ( $( window ).width() / 2 ) ) {
|
|
|
|
offsetLeft += ( !event.pageX ) ? link.width() : 0;
|
|
|
|
offsetLeft -= ( !settings.tall ) ?
|
|
|
|
article.SIZES.portraitPopupWidth :
|
|
|
|
article.SIZES.landscapePopupWidth;
|
2014-04-30 11:13:05 +00:00
|
|
|
flippedX = true;
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( event.pageX ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
offsetLeft += ( flippedX ) ? 20 : -20;
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.render.cache[ href ].settings.flippedX = flippedX;
|
|
|
|
|
|
|
|
// Y Flip
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( clientTop > ( $( window ).height() / 2 ) ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
flippedY = true;
|
|
|
|
|
2015-10-06 16:07:05 +00:00
|
|
|
// Change the Y position to the top of the link
|
|
|
|
if ( event.pageY ) {
|
|
|
|
// Since client rectangles are relative to the viewport,
|
|
|
|
// take scroll position into account.
|
|
|
|
offsetTop = getClosestYPosition(
|
|
|
|
event.pageY - $window.scrollTop(),
|
|
|
|
link.get( 0 ).getClientRects(),
|
|
|
|
true
|
|
|
|
) + $window.scrollTop() + 2 * article.SIZES.pokeySize;
|
|
|
|
}
|
2014-05-27 12:11:52 +00:00
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.render.cache[ href ].settings.flippedY = flippedY;
|
2014-04-25 11:43:42 +00:00
|
|
|
|
|
|
|
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,
|
2014-04-30 11:13:05 +00:00
|
|
|
flippedY = cache.settings.flippedY,
|
|
|
|
flippedX = cache.settings.flippedX;
|
|
|
|
|
2014-05-27 12:11:52 +00:00
|
|
|
if ( flippedY ) {
|
|
|
|
classes.push( 'mwe-popups-fade-in-down' );
|
|
|
|
} else {
|
|
|
|
classes.push( 'mwe-popups-fade-in-up' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
if ( flippedY && flippedX ) {
|
|
|
|
classes.push( 'flipped_x_y' );
|
|
|
|
}
|
2014-04-25 11:43:42 +00:00
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
if ( flippedY && !flippedX ) {
|
|
|
|
classes.push( 'flipped_y' );
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
if ( flippedX && !flippedY ) {
|
|
|
|
classes.push( 'flipped_x' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ( !thumbnail || tall ) && !flippedY ) {
|
2014-04-25 11:43:42 +00:00
|
|
|
classes.push( 'mwe-popups-no-image-tri' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
if ( ( thumbnail && !tall ) && !flippedY ) {
|
2014-04-25 11:43:42 +00:00
|
|
|
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
|
2014-04-30 16:59:04 +00:00
|
|
|
svg = mw.popups.supportsSVG,
|
2014-04-25 11:43:42 +00:00
|
|
|
cache = mw.popups.render.cache [ link.attr( 'href' ) ],
|
2014-04-30 11:13:05 +00:00
|
|
|
popup = mw.popups.$popup,
|
2014-04-25 11:43:42 +00:00
|
|
|
tall = cache.settings.tall,
|
|
|
|
thumbnail = cache.settings.thumbnail,
|
2014-04-30 11:13:05 +00:00
|
|
|
flippedY = cache.settings.flippedY,
|
|
|
|
flippedX = cache.settings.flippedX;
|
|
|
|
|
2014-06-17 11:08:05 +00:00
|
|
|
popup.find( '.mwe-popups-settings-icon' ).click( function () {
|
|
|
|
mw.popups.settings.open();
|
|
|
|
} );
|
|
|
|
|
2014-05-28 15:38:58 +00:00
|
|
|
if ( !flippedY && !tall && cache.settings.thumbnail.height < article.SIZES.landscapeImage.h ) {
|
2015-08-26 10:15:16 +00:00
|
|
|
$( '.mwe-popups-extract' ).css(
|
2014-05-28 15:38:58 +00:00
|
|
|
'margin-top',
|
2014-11-20 06:16:37 +00:00
|
|
|
cache.settings.thumbnail.height - article.SIZES.pokeySize
|
2014-05-28 15:38:58 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( !svg && flippedY && !tall ) {
|
|
|
|
$( '.mwe-popups-extract' ).css( 'margin-top', '206px' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 11:13:05 +00:00
|
|
|
if ( flippedY ) {
|
|
|
|
popup.css( {
|
2015-10-06 16:07:05 +00:00
|
|
|
top: popup.offset().top - popup.outerHeight()
|
2014-04-30 11:13:05 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( flippedY && thumbnail && svg ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.$popup
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', '' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( flippedY && flippedX && thumbnail && tall && svg ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.$popup
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask-flip)' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( flippedX && !flippedY && thumbnail && !tall && svg ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.$popup
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:59:04 +00:00
|
|
|
if ( flippedX && !flippedY && thumbnail && tall && svg ) {
|
2014-04-30 11:13:05 +00:00
|
|
|
mw.popups.$popup
|
|
|
|
.removeClass( 'mwe-popups-no-image-tri' )
|
|
|
|
.find( 'image' )[ 0 ]
|
|
|
|
.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );
|
2014-04-25 11:43:42 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-08-11 13:45:24 +00:00
|
|
|
mw.popups.render.renderers.article = article;
|
2014-04-25 11:43:42 +00:00
|
|
|
|
2015-10-06 16:07:05 +00:00
|
|
|
/**
|
|
|
|
* Given the rectangular box(es) find the 'y' boundary of the closest
|
|
|
|
* rectangle to the point 'y'. The point 'y' is the location of the mouse
|
|
|
|
* on the 'y' axis and the rectangular box(es) are the borders of the
|
|
|
|
* element over which the mouse is located. There will be more than one
|
|
|
|
* rectangle in case the element spans multiple lines.
|
|
|
|
* In the majority of cases the mouse pointer will be inside a rectangle.
|
|
|
|
* However, some browsers (i.e. Chrome) trigger a hover action even when
|
|
|
|
* the mouse pointer is just outside a bounding rectangle. That's why
|
|
|
|
* we need to look at all rectangles and not just the rectangle that
|
|
|
|
* encloses the point.
|
|
|
|
*
|
|
|
|
* @param {number} y the point for which the closest location is being
|
|
|
|
* looked for
|
|
|
|
* @param {ClientRectList} rects list of rectangles defined by four edges
|
|
|
|
* @param {boolean} [isTop] should the resulting rectangle's top 'y'
|
|
|
|
* boundary be returned. By default the bottom 'y' value is returned.
|
|
|
|
* @return {number}
|
|
|
|
*/
|
|
|
|
function getClosestYPosition( y, rects, isTop ) {
|
|
|
|
var result,
|
|
|
|
deltaY,
|
|
|
|
minY = null;
|
|
|
|
|
|
|
|
$.each( rects, function ( i, rect ) {
|
|
|
|
deltaY = Math.abs( y - rect.top + y - rect.bottom );
|
|
|
|
|
|
|
|
if ( minY === null || minY > deltaY ) {
|
|
|
|
minY = deltaY;
|
|
|
|
// Make sure the resulting point is at or outside the rectangle
|
|
|
|
// boundaries.
|
|
|
|
result = ( isTop ) ? Math.floor( rect.top ) : Math.ceil( rect.bottom );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expose for tests
|
|
|
|
*/
|
|
|
|
mw.popups.render.getClosestYPosition = getClosestYPosition;
|
|
|
|
|
2015-08-26 10:15:16 +00:00
|
|
|
} )( jQuery, mediaWiki );
|