2017-05-26 17:35:07 +00:00
|
|
|
/**
|
|
|
|
* @module popups
|
|
|
|
*/
|
|
|
|
|
2017-07-28 17:32:46 +00:00
|
|
|
import * as Redux from 'redux';
|
|
|
|
import * as ReduxThunk from 'redux-thunk';
|
|
|
|
|
|
|
|
import createGateway from './gateway';
|
|
|
|
import createUserSettings from './userSettings';
|
|
|
|
import createPreviewBehavior from './previewBehavior';
|
2018-03-08 20:57:22 +00:00
|
|
|
import createSettingsDialogRenderer from './ui/settingsDialogRenderer';
|
2017-07-28 17:32:46 +00:00
|
|
|
import registerChangeListener from './changeListener';
|
|
|
|
import createIsEnabled from './isEnabled';
|
|
|
|
import { fromElement as titleFromElement } from './title';
|
|
|
|
import { init as rendererInit } from './ui/renderer';
|
|
|
|
import createExperiments from './experiments';
|
|
|
|
import { isEnabled as isStatsvEnabled } from './instrumentation/statsv';
|
2018-01-18 18:48:16 +00:00
|
|
|
import { isEnabled as isEventLoggingEnabled }
|
|
|
|
from './instrumentation/eventLogging';
|
2017-07-28 17:32:46 +00:00
|
|
|
import changeListeners from './changeListeners';
|
|
|
|
import * as actions from './actions';
|
|
|
|
import reducers from './reducers';
|
2017-08-09 09:14:52 +00:00
|
|
|
import createMediaWikiPopupsObject from './integrations/mwpopups';
|
2017-08-10 17:55:26 +00:00
|
|
|
import getUserBucket from './getUserBucket';
|
2017-07-28 17:32:46 +00:00
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const mw = mediaWiki,
|
2017-02-14 20:19:12 +00:00
|
|
|
$ = jQuery,
|
2017-08-02 19:03:24 +00:00
|
|
|
|
2017-02-14 20:19:12 +00:00
|
|
|
BLACKLISTED_LINKS = [
|
|
|
|
'.extiw',
|
|
|
|
'.image',
|
|
|
|
'.new',
|
|
|
|
'.internal',
|
|
|
|
'.external',
|
|
|
|
'.oo-ui-buttonedElement-button',
|
|
|
|
'.cancelLink a'
|
|
|
|
];
|
|
|
|
|
2017-04-27 12:53:15 +00:00
|
|
|
/**
|
2017-06-14 11:00:01 +00:00
|
|
|
* @typedef {Function} EventTracker
|
2017-04-27 12:53:15 +00:00
|
|
|
*
|
2017-06-14 11:00:01 +00:00
|
|
|
* An analytics event tracker, i.e. `mw.track`.
|
|
|
|
*
|
|
|
|
* @param {String} topic
|
|
|
|
* @param {Object} data
|
|
|
|
*
|
|
|
|
* @global
|
2017-04-27 12:53:15 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the appropriate analytics event tracker for logging metrics to StatsD
|
2017-06-14 11:00:01 +00:00
|
|
|
* via [the "StatsD timers and counters" analytics event protocol][0].
|
2017-04-27 12:53:15 +00:00
|
|
|
*
|
2017-06-14 11:00:01 +00:00
|
|
|
* If logging metrics to StatsD is enabled for the duration of the user's
|
|
|
|
* session, then the appriopriate function is `mw.track`; otherwise it's
|
|
|
|
* `$.noop`.
|
2017-04-27 12:53:15 +00:00
|
|
|
*
|
2017-06-14 11:00:01 +00:00
|
|
|
* [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/29c864a0/modules/ext.wikimediaEvents.statsd.js
|
2017-04-27 12:53:15 +00:00
|
|
|
*
|
|
|
|
* @param {Object} user
|
|
|
|
* @param {Object} config
|
2017-06-14 11:00:01 +00:00
|
|
|
* @param {Experiments} experiments
|
|
|
|
* @return {EventTracker}
|
2017-04-27 12:53:15 +00:00
|
|
|
*/
|
|
|
|
function getStatsvTracker( user, config, experiments ) {
|
2017-07-28 17:32:46 +00:00
|
|
|
return isStatsvEnabled( user, config, experiments ) ? mw.track : $.noop;
|
2017-04-27 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2018-02-08 22:11:44 +00:00
|
|
|
/**
|
2018-02-21 18:28:56 +00:00
|
|
|
* Gets the appropriate analytics event tracker for logging virtual pageviews.
|
2018-02-08 22:11:44 +00:00
|
|
|
*
|
|
|
|
* @param {Object} config
|
|
|
|
* @return {EventTracker}
|
|
|
|
*/
|
|
|
|
function getPageViewTracker( config ) {
|
|
|
|
return config.get( 'wgPopupsVirtualPageViews' ) ? mw.track : $.noop;
|
|
|
|
}
|
|
|
|
|
2017-06-14 11:00:01 +00:00
|
|
|
/**
|
|
|
|
* Gets the appropriate analytics event tracker for logging EventLogging events
|
|
|
|
* via [the "EventLogging subscriber" analytics event protocol][0].
|
|
|
|
*
|
|
|
|
* If logging EventLogging events is enabled for the duration of the user's
|
|
|
|
* session, then the appriopriate function is `mw.track`; otherwise it's
|
|
|
|
* `$.noop`.
|
|
|
|
*
|
|
|
|
* [0]: https://github.com/wikimedia/mediawiki-extensions-EventLogging/blob/d1409759/modules/ext.eventLogging.subscriber.js
|
|
|
|
*
|
|
|
|
* @param {Object} user
|
|
|
|
* @param {Object} config
|
2017-08-10 17:55:26 +00:00
|
|
|
* @param {String} bucket for user
|
2017-06-14 11:00:01 +00:00
|
|
|
* @param {Window} window
|
|
|
|
* @return {EventTracker}
|
|
|
|
*/
|
2017-08-10 17:55:26 +00:00
|
|
|
function getEventLoggingTracker( user, config, bucket, window ) {
|
2017-07-28 17:32:46 +00:00
|
|
|
return isEventLoggingEnabled(
|
2017-06-14 11:00:01 +00:00
|
|
|
user,
|
|
|
|
config,
|
2017-08-10 17:55:26 +00:00
|
|
|
bucket,
|
2017-06-14 11:00:01 +00:00
|
|
|
window
|
|
|
|
) ? mw.track : $.noop;
|
|
|
|
}
|
|
|
|
|
2017-11-16 15:54:34 +00:00
|
|
|
/**
|
|
|
|
* Returns timestamp since the beginning of the current document's origin
|
2018-02-28 18:06:14 +00:00
|
|
|
* as reported by `window.performance.now()`. See
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin
|
|
|
|
* for a detailed explanation of the time origin.
|
|
|
|
*
|
|
|
|
* The value returned by this function is used for [the `timestamp` property
|
|
|
|
* of the Schema:Popups events sent by the EventLogging
|
|
|
|
* instrumentation](./src/changeListeners/eventLogging.js).
|
|
|
|
*
|
2017-11-16 15:54:34 +00:00
|
|
|
* @return {number|null}
|
|
|
|
*/
|
|
|
|
function getCurrentTimestamp() {
|
|
|
|
if ( window.performance && window.performance.now ) {
|
2017-12-04 20:47:31 +00:00
|
|
|
// return an integer; see T182000
|
|
|
|
return Math.round( window.performance.now() );
|
2017-11-16 15:54:34 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-02-14 20:19:12 +00:00
|
|
|
/**
|
|
|
|
* Subscribes the registered change listeners to the
|
|
|
|
* [store](http://redux.js.org/docs/api/Store.html#store).
|
|
|
|
*
|
|
|
|
* @param {Redux.Store} store
|
|
|
|
* @param {Object} actions
|
2017-06-14 11:00:01 +00:00
|
|
|
* @param {UserSettings} userSettings
|
2017-02-14 20:19:12 +00:00
|
|
|
* @param {Function} settingsDialog
|
2017-06-14 11:00:01 +00:00
|
|
|
* @param {PreviewBehavior} previewBehavior
|
|
|
|
* @param {EventTracker} statsvTracker
|
|
|
|
* @param {EventTracker} eventLoggingTracker
|
2018-02-21 18:28:56 +00:00
|
|
|
* @param {EventTracker} pageviewTracker
|
2017-11-16 15:54:34 +00:00
|
|
|
* @param {Function} getCurrentTimestamp
|
2017-02-14 20:19:12 +00:00
|
|
|
*/
|
2018-01-18 18:48:16 +00:00
|
|
|
function registerChangeListeners(
|
|
|
|
store, actions, userSettings, settingsDialog, previewBehavior,
|
2018-02-21 18:28:56 +00:00
|
|
|
statsvTracker, eventLoggingTracker, pageviewTracker, getCurrentTimestamp
|
2018-01-18 18:48:16 +00:00
|
|
|
) {
|
2017-02-14 20:19:12 +00:00
|
|
|
registerChangeListener( store, changeListeners.footerLink( actions ) );
|
|
|
|
registerChangeListener( store, changeListeners.linkTitle() );
|
|
|
|
registerChangeListener( store, changeListeners.render( previewBehavior ) );
|
2018-01-18 18:48:16 +00:00
|
|
|
registerChangeListener(
|
|
|
|
store, changeListeners.statsv( actions, statsvTracker ) );
|
|
|
|
registerChangeListener(
|
|
|
|
store, changeListeners.syncUserSettings( userSettings ) );
|
|
|
|
registerChangeListener(
|
|
|
|
store, changeListeners.settings( actions, settingsDialog ) );
|
|
|
|
registerChangeListener(
|
|
|
|
store,
|
|
|
|
changeListeners.eventLogging(
|
|
|
|
actions, eventLoggingTracker, getCurrentTimestamp
|
|
|
|
) );
|
2018-02-08 22:11:44 +00:00
|
|
|
registerChangeListener( store,
|
2018-02-15 20:15:23 +00:00
|
|
|
changeListeners.pageviews( actions, pageviewTracker )
|
2018-02-08 22:11:44 +00:00
|
|
|
);
|
2017-02-14 20:19:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the application by:
|
|
|
|
* 1. Creating the state store
|
|
|
|
* 2. Binding the actions to such store
|
|
|
|
* 3. Trigger the boot action to bootstrap the system
|
|
|
|
* 4. When the page content is ready:
|
|
|
|
* - Process the eligible links for page previews
|
|
|
|
* - Initialize the renderer
|
|
|
|
* - Bind hover and click events to the eligible links to trigger actions
|
|
|
|
*/
|
|
|
|
mw.requestIdleCallback( function () {
|
2018-03-19 19:39:41 +00:00
|
|
|
let compose = Redux.compose;
|
|
|
|
const
|
2017-02-14 20:19:12 +00:00
|
|
|
// So-called "services".
|
|
|
|
generateToken = mw.user.generateRandomSessionId,
|
2018-03-19 19:39:41 +00:00
|
|
|
gateway = createGateway( mw.config );
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const userBucket = getUserBucket(
|
2018-01-18 18:48:16 +00:00
|
|
|
mw.experiments,
|
|
|
|
mw.config.get( 'wgPopupsAnonsExperimentalGroupSize' ),
|
2017-08-10 17:55:26 +00:00
|
|
|
mw.user.sessionId() );
|
2018-03-19 19:39:41 +00:00
|
|
|
const userSettings = createUserSettings( mw.storage );
|
|
|
|
const settingsDialog = createSettingsDialogRenderer();
|
|
|
|
const experiments = createExperiments( mw.experiments );
|
|
|
|
const statsvTracker = getStatsvTracker( mw.user, mw.config, experiments );
|
2018-02-21 18:28:56 +00:00
|
|
|
// Virtual pageviews are always tracked.
|
2018-03-19 19:39:41 +00:00
|
|
|
const pageviewTracker = getPageViewTracker( mw.config );
|
|
|
|
const eventLoggingTracker = getEventLoggingTracker(
|
2017-06-14 11:00:01 +00:00
|
|
|
mw.user,
|
|
|
|
mw.config,
|
2017-08-10 17:55:26 +00:00
|
|
|
userBucket,
|
2017-06-14 11:00:01 +00:00
|
|
|
window
|
|
|
|
);
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const isEnabled =
|
|
|
|
createIsEnabled( mw.user, userSettings, mw.config, userBucket );
|
2017-02-14 20:19:12 +00:00
|
|
|
|
|
|
|
// If debug mode is enabled, then enable Redux DevTools.
|
|
|
|
if ( mw.config.get( 'debug' ) === true ) {
|
|
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
|
|
compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
2016-11-14 19:37:11 +00:00
|
|
|
}
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const store = Redux.createStore(
|
2017-03-14 11:33:15 +00:00
|
|
|
Redux.combineReducers( reducers ),
|
2017-02-14 20:19:12 +00:00
|
|
|
compose( Redux.applyMiddleware(
|
|
|
|
ReduxThunk.default
|
|
|
|
) )
|
|
|
|
);
|
2018-03-19 19:39:41 +00:00
|
|
|
const boundActions = Redux.bindActionCreators( actions, store.dispatch );
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
const previewBehavior =
|
|
|
|
createPreviewBehavior( mw.config, mw.user, boundActions );
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2017-03-07 00:27:38 +00:00
|
|
|
registerChangeListeners(
|
2017-03-16 21:03:21 +00:00
|
|
|
store, boundActions, userSettings, settingsDialog,
|
2017-11-16 15:54:34 +00:00
|
|
|
previewBehavior, statsvTracker, eventLoggingTracker,
|
2018-02-21 18:28:56 +00:00
|
|
|
pageviewTracker,
|
2017-11-16 15:54:34 +00:00
|
|
|
getCurrentTimestamp
|
2017-03-07 00:27:38 +00:00
|
|
|
);
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2017-03-14 11:28:58 +00:00
|
|
|
boundActions.boot(
|
2017-02-14 20:19:12 +00:00
|
|
|
isEnabled,
|
|
|
|
mw.user,
|
|
|
|
userSettings,
|
|
|
|
generateToken,
|
2018-02-15 20:15:23 +00:00
|
|
|
mw.config,
|
|
|
|
window.location.href
|
2017-02-14 20:19:12 +00:00
|
|
|
);
|
|
|
|
|
2017-08-09 09:14:52 +00:00
|
|
|
/*
|
|
|
|
* Register external interface exposing popups internals so that other
|
|
|
|
* extensions can query it (T171287)
|
|
|
|
*/
|
|
|
|
mw.popups = createMediaWikiPopupsObject( store );
|
|
|
|
|
2017-02-14 20:19:12 +00:00
|
|
|
mw.hook( 'wikipage.content' ).add( function ( $container ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
const invalidLinksSelector = BLACKLISTED_LINKS.join( ', ' ),
|
2018-03-20 17:01:18 +00:00
|
|
|
validLinkSelector = `a[href][title]:not(${ invalidLinksSelector })`;
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2017-07-28 17:32:46 +00:00
|
|
|
rendererInit();
|
2017-02-14 20:19:12 +00:00
|
|
|
|
2017-06-08 15:23:44 +00:00
|
|
|
$container
|
|
|
|
.on( 'mouseover keyup', validLinkSelector, function ( event ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
const mwTitle = titleFromElement( this, mw.config );
|
2017-06-08 15:23:44 +00:00
|
|
|
|
|
|
|
if ( mwTitle ) {
|
2018-01-18 18:48:16 +00:00
|
|
|
boundActions.linkDwell(
|
|
|
|
mwTitle, this, event, gateway, generateToken
|
|
|
|
);
|
2017-06-08 15:23:44 +00:00
|
|
|
}
|
2017-02-14 20:19:12 +00:00
|
|
|
} )
|
2017-06-08 15:23:44 +00:00
|
|
|
.on( 'mouseout blur', validLinkSelector, function () {
|
2018-03-19 19:39:41 +00:00
|
|
|
const mwTitle = titleFromElement( this, mw.config );
|
2017-06-08 15:23:44 +00:00
|
|
|
|
|
|
|
if ( mwTitle ) {
|
|
|
|
boundActions.abandon( this );
|
|
|
|
}
|
2017-02-14 20:19:12 +00:00
|
|
|
} )
|
2017-06-08 15:23:44 +00:00
|
|
|
.on( 'click', validLinkSelector, function () {
|
2018-03-19 19:39:41 +00:00
|
|
|
const mwTitle = titleFromElement( this, mw.config );
|
2017-06-08 15:23:44 +00:00
|
|
|
|
|
|
|
if ( mwTitle ) {
|
|
|
|
boundActions.linkClick( this );
|
|
|
|
}
|
2017-02-14 20:19:12 +00:00
|
|
|
} );
|
2016-11-08 10:05:40 +00:00
|
|
|
} );
|
2017-02-14 20:19:12 +00:00
|
|
|
} );
|
2016-11-08 10:05:40 +00:00
|
|
|
|
2017-02-14 20:19:12 +00:00
|
|
|
window.Redux = Redux;
|
|
|
|
window.ReduxThunk = ReduxThunk;
|