2014-04-02 09:37:13 +00:00
|
|
|
( function ( $, mw ) {
|
2014-05-19 22:48:32 +00:00
|
|
|
'use strict';
|
2016-05-16 20:32:11 +00:00
|
|
|
var previewCountStorageKey = 'ext.popups.core.previewCount',
|
|
|
|
popupsEnabledStorageKey = 'mwe-popups-enabled';
|
2014-04-22 14:16:54 +00:00
|
|
|
|
|
|
|
/**
|
2014-04-25 11:43:42 +00:00
|
|
|
* @class mw.popups
|
|
|
|
* @singleton
|
2014-04-22 14:16:54 +00:00
|
|
|
*/
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups = {};
|
2014-04-22 14:16:54 +00:00
|
|
|
|
|
|
|
/**
|
2014-04-25 11:43:42 +00:00
|
|
|
* The API object used for all this extension's requests
|
|
|
|
* @property {Object} api
|
2014-04-22 14:16:54 +00:00
|
|
|
*/
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups.api = new mw.Api();
|
2014-04-22 14:16:54 +00:00
|
|
|
|
|
|
|
/**
|
2014-04-25 11:43:42 +00:00
|
|
|
* Whether the page is being scrolled.
|
2015-08-26 10:15:16 +00:00
|
|
|
* @property {boolean} scrolled
|
2014-04-22 14:16:54 +00:00
|
|
|
*/
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups.scrolled = false;
|
2014-04-22 14:16:54 +00:00
|
|
|
|
|
|
|
/**
|
2014-04-25 11:43:42 +00:00
|
|
|
* List of classes of which links are ignored
|
|
|
|
* @property {Array} IGNORE_CLASSES
|
2014-04-22 14:16:54 +00:00
|
|
|
*/
|
2014-04-25 11:43:42 +00:00
|
|
|
mw.popups.IGNORE_CLASSES = [
|
2014-05-19 22:30:29 +00:00
|
|
|
'.extiw',
|
2014-04-25 11:43:42 +00:00
|
|
|
'.image',
|
|
|
|
'.new',
|
2014-06-03 08:13:40 +00:00
|
|
|
'.internal',
|
2014-06-04 04:13:50 +00:00
|
|
|
'.external',
|
2015-12-23 13:38:33 +00:00
|
|
|
'.oo-ui-buttonedElement-button',
|
|
|
|
'.cancelLink a'
|
2014-04-25 11:43:42 +00:00
|
|
|
];
|
2014-04-22 14:16:54 +00:00
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
/**
|
2015-04-21 17:10:29 +00:00
|
|
|
* Temporarily remove the title attribute of the links so that
|
|
|
|
* the yellow tooltips don't show up alongside the Hovercard.
|
2014-04-25 11:43:42 +00:00
|
|
|
*
|
|
|
|
* @method removeTooltips
|
|
|
|
*/
|
2014-05-19 22:35:58 +00:00
|
|
|
mw.popups.removeTooltips = function ( $elements ) {
|
|
|
|
$elements
|
2014-05-19 22:42:41 +00:00
|
|
|
.filter( '[title]:not([title=""])' )
|
2014-03-23 02:32:37 +00:00
|
|
|
.on( 'mouseenter focus', function () {
|
2015-04-21 17:10:29 +00:00
|
|
|
// We shouldn't empty the title attribute of links that
|
|
|
|
// can't have Hovercards, ie. TextExtracts didn't return
|
|
|
|
// anything. Its set in the article.init after attempting
|
|
|
|
// to make an API request.
|
|
|
|
if ( $( this ).data( 'dont-empty-title' ) !== true ) {
|
|
|
|
$( this )
|
|
|
|
.data( 'title', $( this ).attr( 'title' ) )
|
|
|
|
.attr( 'title', '' );
|
|
|
|
}
|
2014-03-23 02:32:37 +00:00
|
|
|
} )
|
|
|
|
.on( 'mouseleave blur', function () {
|
|
|
|
$( this )
|
2014-04-25 11:43:42 +00:00
|
|
|
.attr( 'title', $( this ).data( 'title' ) );
|
2014-03-23 02:32:37 +00:00
|
|
|
} );
|
2014-04-25 11:43:42 +00:00
|
|
|
};
|
2014-02-06 11:15:05 +00:00
|
|
|
|
2014-04-25 11:43:42 +00:00
|
|
|
/**
|
2014-07-23 02:18:13 +00:00
|
|
|
* Register a hover event that may render a popup on an appropriate link.
|
2014-04-25 11:43:42 +00:00
|
|
|
*
|
|
|
|
* @method setupTriggers
|
2016-05-13 23:16:32 +00:00
|
|
|
* @param {jQuery.Object} $elements to bind events to
|
|
|
|
* @param {string} events to bind to
|
2014-04-25 11:43:42 +00:00
|
|
|
*/
|
2016-05-13 23:16:32 +00:00
|
|
|
mw.popups.setupTriggers = function ( $elements, events ) {
|
|
|
|
$elements.on( events, function ( event ) {
|
2014-07-15 15:41:58 +00:00
|
|
|
if ( mw.popups.scrolled ) {
|
2014-02-06 10:49:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-16 20:32:11 +00:00
|
|
|
mw.popups.render.render( $( this ), event, mw.now(), mw.popups.getRandomToken() );
|
2014-04-25 11:43:42 +00:00
|
|
|
} );
|
|
|
|
};
|
2014-02-06 10:49:28 +00:00
|
|
|
|
2015-03-25 20:53:27 +00:00
|
|
|
/**
|
|
|
|
* Given an href string for the local wiki, return the title, or undefined if
|
|
|
|
* the link is external, has extra query parameters, or contains no title.
|
|
|
|
*
|
|
|
|
* Note that the returned title is not sanitized (may contain underscores).
|
|
|
|
*
|
|
|
|
* @param {string} href
|
|
|
|
* @return {string|undefined}
|
|
|
|
*/
|
|
|
|
mw.popups.getTitle = function ( href ) {
|
2015-04-09 01:16:09 +00:00
|
|
|
var title, titleRegex, matches, linkHref;
|
|
|
|
|
|
|
|
// Skip every URI that mw.Uri cannot parse
|
|
|
|
try {
|
2015-03-25 20:53:27 +00:00
|
|
|
linkHref = new mw.Uri( href );
|
2015-04-09 01:16:09 +00:00
|
|
|
} catch ( e ) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2015-03-25 20:53:27 +00:00
|
|
|
|
|
|
|
// External links
|
|
|
|
if ( linkHref.host !== location.hostname ) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( linkHref.query.hasOwnProperty( 'title' ) ) {
|
|
|
|
// linkHref is not a pretty URL, e.g. /w/index.php?title=Foo
|
|
|
|
|
|
|
|
title = linkHref.query.title;
|
|
|
|
// Return undefined if there are query parameters other than title
|
|
|
|
delete linkHref.query.title;
|
|
|
|
return $.isEmptyObject( linkHref.query ) ? title : undefined;
|
|
|
|
} else {
|
|
|
|
// linkHref is a pretty URL, e.g. /wiki/Foo
|
|
|
|
|
|
|
|
// Return undefined if there are any query parameters
|
|
|
|
if ( !$.isEmptyObject( linkHref.query ) ) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2015-06-26 07:48:18 +00:00
|
|
|
titleRegex = new RegExp( mw.RegExp.escape( mw.config.get( 'wgArticlePath' ) )
|
2015-03-25 20:53:27 +00:00
|
|
|
.replace( '\\$1', '(.+)' ) );
|
|
|
|
matches = titleRegex.exec( linkHref.path );
|
2015-08-26 10:15:16 +00:00
|
|
|
return matches ? decodeURIComponent( matches[ 1 ] ) : undefined;
|
2015-03-25 20:53:27 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-05-19 22:35:58 +00:00
|
|
|
/**
|
|
|
|
* Returns links that can have Popups
|
|
|
|
*
|
|
|
|
* @method selectPopupElements
|
|
|
|
*/
|
|
|
|
mw.popups.selectPopupElements = function () {
|
2015-03-25 20:53:27 +00:00
|
|
|
var contentNamespaces = mw.config.get( 'wgContentNamespaces' );
|
2015-02-17 14:03:13 +00:00
|
|
|
return mw.popups.$content
|
2015-08-26 10:15:16 +00:00
|
|
|
.find( 'a[href][title]:not(' + mw.popups.IGNORE_CLASSES.join( ', ' ) + ')' )
|
2014-07-15 15:41:58 +00:00
|
|
|
.filter( function () {
|
2015-03-25 20:53:27 +00:00
|
|
|
var title,
|
|
|
|
titleText = mw.popups.getTitle( this.href );
|
|
|
|
if ( !titleText ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Is titleText in a content namespace?
|
|
|
|
title = mw.Title.newFromText( titleText );
|
|
|
|
return title && ( $.inArray( title.namespace, contentNamespaces ) >= 0 );
|
2014-07-15 15:41:58 +00:00
|
|
|
} );
|
2014-05-19 22:35:58 +00:00
|
|
|
};
|
|
|
|
|
2016-05-12 22:37:19 +00:00
|
|
|
/**
|
|
|
|
* Get action based on click event
|
|
|
|
*
|
|
|
|
* @method getAction
|
|
|
|
* @param {Object} event
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
mw.popups.getAction = function ( event ) {
|
|
|
|
if ( event.which === 2 ) { // middle click
|
|
|
|
return 'opened in new tab';
|
|
|
|
} else if ( event.which === 1 ) {
|
|
|
|
if ( event.ctrlKey || event.metaKey ) {
|
|
|
|
return 'opened in new tab';
|
|
|
|
} else if ( event.shiftKey ) {
|
|
|
|
return 'opened in new window';
|
|
|
|
} else {
|
|
|
|
return 'opened in same tab';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-16 20:32:11 +00:00
|
|
|
/**
|
|
|
|
* Get a random token.
|
|
|
|
* Append the current timestamp to make the return value more unique.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
mw.popups.getRandomToken = function () {
|
|
|
|
return mw.user.generateRandomSessionId() + Math.round( mw.now() ).toString();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return edit count bucket based on the number of edits.
|
|
|
|
* The returned value is "unknown" is `window.localStorage` is not supported.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
mw.popups.getPreviewCountBucket = function () {
|
|
|
|
var bucket,
|
|
|
|
previewCount = mw.storage.get( previewCountStorageKey );
|
|
|
|
|
|
|
|
// no support for localStorage
|
|
|
|
if ( previewCount === false ) {
|
|
|
|
return 'unknown';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to 0 if this is the first time.
|
|
|
|
previewCount = parseInt( previewCount || 0, 10 );
|
|
|
|
|
|
|
|
if ( previewCount === 0 ) {
|
|
|
|
bucket = '0';
|
|
|
|
} else if ( previewCount >= 1 && previewCount <= 4 ) {
|
|
|
|
bucket = '1-4';
|
|
|
|
} else if ( previewCount >= 5 && previewCount <= 20 ) {
|
|
|
|
bucket = '5-20';
|
|
|
|
} else if ( previewCount >= 21 ) {
|
|
|
|
bucket = '21+';
|
|
|
|
}
|
|
|
|
|
|
|
|
return bucket + ' previews';
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Increment the preview count and save it to localStorage.
|
|
|
|
*/
|
|
|
|
mw.popups.incrementPreviewCount = function () {
|
|
|
|
var previewCount = parseInt( mw.storage.get( previewCountStorageKey ) || 0, 10 );
|
|
|
|
|
|
|
|
mw.storage.set( previewCountStorageKey, ( previewCount + 1 ).toString() );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-05-13 20:15:36 +00:00
|
|
|
* Save the popups enabled state via device storage
|
2016-05-16 20:32:11 +00:00
|
|
|
*
|
|
|
|
* @param {boolean} isEnabled
|
|
|
|
*/
|
|
|
|
mw.popups.saveEnabledState = function ( isEnabled ) {
|
2016-05-13 20:15:36 +00:00
|
|
|
mw.storage.set( popupsEnabledStorageKey, isEnabled ? '1' : '0' );
|
2016-05-16 20:32:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-05-13 20:15:36 +00:00
|
|
|
* Retrieve the popups enabled state via device storage or 'wgPopupsExperiment'
|
2016-05-16 20:32:11 +00:00
|
|
|
* config variable.
|
|
|
|
* If the experiment isn't running, then continue to enable Popups
|
|
|
|
* by default during initialisation. In this case the return value
|
|
|
|
* defaults to `true` if the setting hasn't been saved before.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
mw.popups.getEnabledState = function () {
|
|
|
|
if ( !mw.config.get( 'wgPopupsExperiment', false ) ) {
|
2016-05-13 20:15:36 +00:00
|
|
|
return mw.storage.get( popupsEnabledStorageKey ) !== '0';
|
2016-05-16 20:32:11 +00:00
|
|
|
} else {
|
2016-09-21 13:04:12 +00:00
|
|
|
return this.isUserInCondition();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
function getToken() {
|
|
|
|
var key = 'PopupsExperimentID',
|
|
|
|
id = mw.storage.get( key );
|
|
|
|
|
|
|
|
if ( !id ) {
|
|
|
|
id = mw.user.generateRandomSessionId();
|
|
|
|
|
|
|
|
mw.storage.set( key, id );
|
|
|
|
}
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Has the user previously enabled Popups by clicking "Enable previews" in the
|
|
|
|
* footer menu?
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
function hasUserEnabledFeature() {
|
|
|
|
var value = mw.storage.get( 'mwe-popups-enabled' );
|
|
|
|
|
|
|
|
return Boolean( value ) && value !== '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Has the user previously disabled Popups by clicking "Disable previews" in the settings
|
|
|
|
* overlay?
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
function hasUserDisabledFeature() {
|
|
|
|
return mw.storage.get( 'mwe-popups-enabled' ) === '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets whether or not the user has Popups enabled, i.e. whether they are in the experiment
|
|
|
|
* condition.
|
|
|
|
*
|
|
|
|
* The user is in the experiment condition if:
|
|
|
|
* * they've enabled Popups by click "Enable previews" in the footer menu, or
|
|
|
|
* * they've enabled Popups as a beta feature, or
|
|
|
|
* * they aren't in the control bucket of the experiment
|
|
|
|
*
|
|
|
|
* N.B. that the user isn't entered into the experiment, i.e. they aren't assigned or a bucket,
|
|
|
|
* if the experiment isn't configured.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
mw.popups.isUserInCondition = function isUserInCondition() {
|
|
|
|
var config = mw.config.get( 'wgPopupsExperimentConfig' );
|
|
|
|
|
|
|
|
// The first two tests deal with whether the user has /explicitly/ enable or disabled via its
|
|
|
|
// settings.
|
|
|
|
if ( hasUserEnabledFeature() ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( hasUserDisabledFeature() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( mw.user.isAnon() ) {
|
|
|
|
if ( !config ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: mw.experiments should expose the CONTROL_BUCKET constant, e.g.
|
|
|
|
// `mw.experiments.CONTROL_BUCKET`.
|
|
|
|
return mw.experiments.getBucket( config, getToken() ) !== 'control';
|
|
|
|
} else {
|
|
|
|
// Logged in users are in condition depending on the beta feature flag
|
|
|
|
// instead of bucketing
|
|
|
|
return mw.config.get( 'wgPopupsExperimentIsBetaFeatureEnabled', false );
|
2016-05-16 20:32:11 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-08-26 10:15:16 +00:00
|
|
|
} )( jQuery, mediaWiki );
|