' )
.addClass( 'mwe-popups-is-not-tall' )
.css( 'background-image', 'url(' + thumbnail.source + ')' );
}
}
return $thumbnail;
}
/**
* @method createBox
* Looks into the `cache` and uses the href to render the popup
* and offsets it to the link. Rebinds the `mouseleave` event
* for the anchor element to `leaveActive`.
* @param {String} href
* @param {Object} $el
*/
function createBox ( href, $el ) {
var bar = cache[ href ],
offsetTop = $el.offset().top + $el.height() + 9,
offsetLeft = $el.offset().left,
flipped = false;
elTime = mw.now();
elAction = 'dismissed';
if ( bar.thumbnail === undefined ) {
bar.thumbnail = false;
}
if ( bar.tall === undefined ) {
bar.tall = false;
}
if ( offsetLeft > ( $( window ).width() / 2 ) ) {
offsetLeft = offsetLeft + $el.width();
offsetLeft -= ( !bar.tall ) ? SIZES.portraitPopupWidth : SIZES.landscapePopupWidth;
flipped = true;
}
$box
.children()
.detach()
// avoid .empty() to keep event handlers
.end()
.removeClass( 'mwe-popups-is-tall mwe-popups-is-not-tall mwe-popups-no-image-tri mwe-popups-image-tri flipped' )
.toggleClass( 'flipped', flipped )
// Add border triangle if there is no image or its landscape
.toggleClass( 'mwe-popups-no-image-tri', ( !bar.thumbnail || bar.tall ) )
// If theres an image and the popup is portrait do the SVG stuff
.toggleClass( 'mwe-popups-image-tri', ( bar.thumbnail && !bar.tall ) )
.addClass( bar.tall ? 'mwe-popups-is-tall' : 'mwe-popups-is-not-tall' )
.css({
top: offsetTop,
left: offsetLeft
})
.append( bar.box )
.attr( 'aria-hidden', 'false' )
.show()
.removeClass( 'mwe-popups-fade-out mwe-popups-fade-in' )
.addClass( 'mwe-popups-fade-in' )
// Hack to 'refresh' the SVG and thus display it
// Elements get added to the DOM and not to the screen because of different namespaces of HTML and SVG
// More information and workarounds here - http://stackoverflow.com/a/13654655/366138
.html( $box.html() );
if ( flipped && bar.thumbnail ) {
if ( !bar.tall ) {
$box.find( 'image' )[0].setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );
} else {
$box
.removeClass( 'mwe-popups-no-image-tri' )
.find( 'image' )[0].setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );
}
}
$el
.off( 'mouseleave blur', leaveInactive )
.on( 'mouseleave blur', leaveActive );
}
/**
* @method leaveActive
* Closes the box after a delay of 100ms
* Delay to give enough time for the use to move the pointer from
* the link to the popup box. Also avoids closing the popup by accident.
*/
function leaveActive () {
closeTimer = setTimeout( closeBox, 100 );
}
/**
* @method leaveInactive
* Unbinds events on the anchor tag and aborts AJAX request.
*/
function leaveInactive () {
$( currentLink ).off( 'mouseleave', leaveInactive );
clearTimeout( openTimer );
if ( curRequest ) {
curRequest.abort();
}
currentLink = openTimer = curRequest = undefined;
}
/**
* @method closeBox
* Removes the hover class from the link and unbinds events
* Hides the popup, clears timers and sets it and the
* `currentLink` to undefined.
*/
function closeBox () {
elDuration = mw.now() - elTime;
$( currentLink ).removeClass( 'mwe-popups-anchor-hover' ).off( 'mouseleave', leaveActive );
$box
.removeClass( 'mwe-popups-fade-out mwe-popups-fade-in' )
.addClass( 'mwe-popups-fade-out' )
.on( 'webkitAnimationEnd oanimationend msAnimationEnd animationend', function () {
if ( $( this ).hasClass( 'mwe-popups-fade-out' ) ) {
$( this )
.off( 'webkitAnimationEnd oanimationend msAnimationEnd animationend' )
.removeClass( 'mwe-popups-fade-out' )
.attr( 'aria-hidden', 'true' )
.hide();
}
} );
if ( closeTimer ){
clearTimeout( closeTimer );
}
logEvent();
currentLink = closeTimer = undefined;
}
/**
* @method logClick
* Logs different actions such as meta and shift click on the popup.
* Is bound to the `click` event.
* @param {Object} e
*/
function logClick ( e ) {
if ( e.which === 2) { // middle click
elAction = 'opened in new tab';
} else if ( e.which === 1) {
if ( e.ctrlKey || e.metaKey ) {
elAction = 'opened in new tab';
} else if ( e.shiftKey ){
elAction = 'opened in new window';
} else {
elAction = 'opened in same tab';
elDuration = mw.now() - elTime;
logEvent( this.href );
e.preventDefault();
}
}
}
/**
* @method logEvent
* Logs the Popup event as defined in the following schema -
* https://meta.wikimedia.org/wiki/Schema:Popups
* If `href` is passed it redirects to that location after the event is logged.
* @param {string} href
*/
function logEvent ( href ) {
if ( typeof mw.eventLog !== 'function' ) {
return false;
}
var dfd = $.Deferred(),
event = {
'duration': Math.round( elDuration ),
'action': elAction
};
if ( elSessionId !== null ) {
event.sessionId = elSessionId;
}
if ( href ) {
dfd.always( function () {
location.href = href;
} );
}
mw.eventLog.logEvent( 'Popups', event ).then( dfd.resolve, dfd.reject );
setTimeout( dfd.reject, 1000 );
elTime = elDuration = elAction = undefined;
}
/**
* @method getSessionId
* Generates a unique sessionId or pulls an existing one from localStorage
* @return {string} sessionId
*/
function getSessionId () {
var sessionId = null;
try {
sessionId = localStorage.getItem( 'popupsSessionId' );
if ( sessionId === null ) {
sessionId = mw.user.generateRandomSessionId();
localStorage.setItem( 'popupsSessionId', sessionId );
}
} catch ( e ) {}
return sessionId;
}
elSessionId = getSessionId();
// Remove title attribute to remove the default yellow tooltip
// Put the title back after the hover
$( '#mw-content-text a' )
.not( '.extiw' )
.not( '.image' )
.not( '.new' )
.not( '[title=""]' )
.on( 'mouseenter focus', function () {
$( this )
.attr( 'data-original-title', $( this ).attr( 'title' ) )
.attr( 'title', '');
} )
.on( 'mouseleave blur', function () {
$( this )
.attr( 'title', $( this ).attr( 'data-original-title' ) )
.attr( 'data-original-title', '');
} );
$( '#mw-content-text a' ).on( 'mouseenter focus', function () {
var $this = $( this ),
href = $this.attr( 'href' ),
title = $this.attr( 'data-original-title' );
// If a popup for the following link can't be shown
if ( !title ||
$this.hasClass( 'extiw' ) ||
$this.hasClass( 'image' ) ||
$this.hasClass( 'new' ) ||
href.indexOf( '?' ) !== -1 ||
href.indexOf( location.origin + location.pathname + '#' ) === 0
) {
return;
}
// This will happen when the mouse goes from the popup box back to the
// anchor tag. In such a case, the timer to close the box is cleared.
if ( currentLink === this ) {
clearTimeout( closeTimer );
return;
}
// If the mouse moves to another link (we already check if its the same
// link in the previous condition), then close the popup.
if ( currentLink ) {
closeBox();
}
currentLink = this;
$this.on( 'mouseleave blur', leaveInactive );
if ( cache[ href ] ){
openTimer = setTimeout( function () {
createBox( href, $this );
}, 150 );
} else {
openTimer = setTimeout( function () {
sendRequest( href, title, $this );
}, 50 ); // sendRequest sooner so that it *hopefully* shows up in 150ms
}
// Delay to avoid triggering the popup and AJAX requests on accidental
// hovers (likes ones during srcolling or moving the pointer elsewhere).
} );
// Container for the popup
$box = $( '
' )
.attr( 'role', 'tooltip' )
.addClass( 'mwe-popups' )
.on( {
mouseleave: leaveActive,
mouseenter: function () {
if ( closeTimer ) {
clearTimeout( closeTimer );
}
}
} )
.appendTo( document.body );
// SVG for masking and creating the triangle/pokey
if ( supportsSVG() ) {
$svg = $( '
' )
.attr( 'id', 'mwe-popups-svg' )
.appendTo( document.body )
.html( '
' +
'' +
' ' +
'' +
' ' +
'' +
' ' +
' ' +
' ' );
}
} );
} ) ( jQuery );