mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-09-23 18:29:35 +00:00
0b2961c318
ext.popups.experiment depends on .core as it initialized the mw.popups namespace and .core depends on .experiments for mw.popups#getEnabledState. By merging the experiment module into core, we can eliminate any circular dependencies. Changes: * Move ext.popups.experiment.js code into ext.popups.core.js * Remove mw.popups.experiment module and any references to it Note: ext.popups.experiment.test.js was left in its own file for cleaner QUnit module setups and easier removal later. I'm not entirely happy with doing it this way, but I'm not sure changing the mw.config within the mw.popups.core QUnit module is worth merging the files. Bug: T146035 Change-Id: I1f024567010acaa61c1d613c6e59c998198a5976
328 lines
8.5 KiB
JavaScript
328 lines
8.5 KiB
JavaScript
( function ( $, mw ) {
|
|
'use strict';
|
|
var previewCountStorageKey = 'ext.popups.core.previewCount',
|
|
popupsEnabledStorageKey = 'mwe-popups-enabled';
|
|
|
|
/**
|
|
* @class mw.popups
|
|
* @singleton
|
|
*/
|
|
mw.popups = {};
|
|
|
|
/**
|
|
* The API object used for all this extension's requests
|
|
* @property {Object} api
|
|
*/
|
|
mw.popups.api = new mw.Api();
|
|
|
|
/**
|
|
* Whether the page is being scrolled.
|
|
* @property {boolean} scrolled
|
|
*/
|
|
mw.popups.scrolled = false;
|
|
|
|
/**
|
|
* List of classes of which links are ignored
|
|
* @property {Array} IGNORE_CLASSES
|
|
*/
|
|
mw.popups.IGNORE_CLASSES = [
|
|
'.extiw',
|
|
'.image',
|
|
'.new',
|
|
'.internal',
|
|
'.external',
|
|
'.oo-ui-buttonedElement-button',
|
|
'.cancelLink a'
|
|
];
|
|
|
|
/**
|
|
* Temporarily remove the title attribute of the links so that
|
|
* the yellow tooltips don't show up alongside the Hovercard.
|
|
*
|
|
* @method removeTooltips
|
|
*/
|
|
mw.popups.removeTooltips = function ( $elements ) {
|
|
$elements
|
|
.filter( '[title]:not([title=""])' )
|
|
.on( 'mouseenter focus', function () {
|
|
// 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', '' );
|
|
}
|
|
} )
|
|
.on( 'mouseleave blur', function () {
|
|
$( this )
|
|
.attr( 'title', $( this ).data( 'title' ) );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Register a hover event that may render a popup on an appropriate link.
|
|
*
|
|
* @method setupTriggers
|
|
* @param {jQuery.Object} $elements to bind events to
|
|
* @param {string} events to bind to
|
|
*/
|
|
mw.popups.setupTriggers = function ( $elements, events ) {
|
|
$elements.on( events, function ( event ) {
|
|
if ( mw.popups.scrolled ) {
|
|
return;
|
|
}
|
|
|
|
mw.popups.render.render( $( this ), event, mw.now(), mw.popups.getRandomToken() );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* 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 ) {
|
|
var title, titleRegex, matches, linkHref;
|
|
|
|
// Skip every URI that mw.Uri cannot parse
|
|
try {
|
|
linkHref = new mw.Uri( href );
|
|
} catch ( e ) {
|
|
return undefined;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
titleRegex = new RegExp( mw.RegExp.escape( mw.config.get( 'wgArticlePath' ) )
|
|
.replace( '\\$1', '(.+)' ) );
|
|
matches = titleRegex.exec( linkHref.path );
|
|
return matches ? decodeURIComponent( matches[ 1 ] ) : undefined;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns links that can have Popups
|
|
*
|
|
* @method selectPopupElements
|
|
*/
|
|
mw.popups.selectPopupElements = function () {
|
|
var contentNamespaces = mw.config.get( 'wgContentNamespaces' );
|
|
return mw.popups.$content
|
|
.find( 'a[href][title]:not(' + mw.popups.IGNORE_CLASSES.join( ', ' ) + ')' )
|
|
.filter( function () {
|
|
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 );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* 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';
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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() );
|
|
};
|
|
|
|
/**
|
|
* Save the popups enabled state via device storage
|
|
*
|
|
* @param {boolean} isEnabled
|
|
*/
|
|
mw.popups.saveEnabledState = function ( isEnabled ) {
|
|
mw.storage.set( popupsEnabledStorageKey, isEnabled ? '1' : '0' );
|
|
};
|
|
|
|
/**
|
|
* Retrieve the popups enabled state via device storage or 'wgPopupsExperiment'
|
|
* 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 ) ) {
|
|
return mw.storage.get( popupsEnabledStorageKey ) !== '0';
|
|
} else {
|
|
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 );
|
|
}
|
|
};
|
|
|
|
} )( jQuery, mediaWiki );
|