2016-12-12 12:54:33 +00:00
|
|
|
( function ( mw, $ ) {
|
2016-12-01 13:12:29 +00:00
|
|
|
|
|
|
|
// Sugar for the mw.popups.reducers.eventLogging reducer.
|
|
|
|
var counts = mw.popups.counts;
|
|
|
|
|
2016-11-10 18:02:29 +00:00
|
|
|
mw.popups.reducers = {};
|
|
|
|
|
2016-11-17 21:41:56 +00:00
|
|
|
/**
|
|
|
|
* Creates the next state tree from the current state tree and some updates.
|
|
|
|
*
|
2016-11-30 12:44:59 +00:00
|
|
|
* N.B. OO.copy doesn't copy Element instances, whereas $.extend does.
|
|
|
|
* However, OO.copy does copy properties whose values are undefined or null,
|
2016-12-05 19:16:36 +00:00
|
|
|
* whereas $.extend doesn't. Since the state tree contains an Element instance
|
|
|
|
* - the preview.activeLink property - and we want to copy undefined/null into
|
|
|
|
* the state we need to manually iterate over updates and check with
|
|
|
|
* hasOwnProperty to copy over to the new state.
|
2016-11-30 12:44:59 +00:00
|
|
|
*
|
2016-11-17 21:41:56 +00:00
|
|
|
* In [change listeners](/doc/change_listeners.md), for example, we talk about
|
|
|
|
* the previous state and the current state (the `prevState` and `state`
|
|
|
|
* parameters, respectively). Since
|
|
|
|
* [reducers](http://redux.js.org/docs/basics/Reducers.html) take the current
|
|
|
|
* state and an action and make updates, "next state" seems appropriate.
|
|
|
|
*
|
|
|
|
* @param {Object} state
|
|
|
|
* @param {Object} updates
|
|
|
|
* @return {Object}
|
|
|
|
*/
|
|
|
|
function nextState( state, updates ) {
|
2016-12-05 19:16:36 +00:00
|
|
|
var result = {},
|
2016-11-17 21:41:56 +00:00
|
|
|
key;
|
|
|
|
|
2016-12-05 19:16:36 +00:00
|
|
|
for ( key in state ) {
|
|
|
|
if ( state.hasOwnProperty( key ) && !updates.hasOwnProperty( key ) ) {
|
|
|
|
result[key] = state[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-17 21:41:56 +00:00
|
|
|
for ( key in updates ) {
|
|
|
|
if ( updates.hasOwnProperty( key ) ) {
|
|
|
|
result[key] = updates[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-10 18:02:29 +00:00
|
|
|
/**
|
|
|
|
* Reducer for actions that modify the state of the preview model
|
|
|
|
*
|
|
|
|
* @param {Object} state before action
|
|
|
|
* @param {Object} action Redux action that modified state.
|
|
|
|
* Must have `type` property.
|
|
|
|
* @return {Object} state after action
|
|
|
|
*/
|
|
|
|
mw.popups.reducers.preview = function ( state, action ) {
|
|
|
|
if ( state === undefined ) {
|
|
|
|
state = {
|
2016-11-14 19:37:11 +00:00
|
|
|
enabled: undefined,
|
2016-11-10 18:02:29 +00:00
|
|
|
activeLink: undefined,
|
2016-11-21 11:08:06 +00:00
|
|
|
activeEvent: undefined,
|
2016-11-28 12:00:07 +00:00
|
|
|
shouldShow: false,
|
|
|
|
isUserDwelling: false
|
2016-11-10 18:02:29 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ( action.type ) {
|
|
|
|
case mw.popups.actionTypes.BOOT:
|
2016-11-17 21:41:56 +00:00
|
|
|
return nextState( state, {
|
2016-12-01 13:12:29 +00:00
|
|
|
enabled: action.user.isInCondition
|
2016-11-10 18:02:29 +00:00
|
|
|
} );
|
2016-11-16 16:16:43 +00:00
|
|
|
case mw.popups.actionTypes.LINK_DWELL:
|
2016-11-17 21:41:56 +00:00
|
|
|
return nextState( state, {
|
2016-11-16 19:45:10 +00:00
|
|
|
activeLink: action.el,
|
2016-11-21 11:08:06 +00:00
|
|
|
activeEvent: action.event,
|
2016-11-28 12:00:07 +00:00
|
|
|
|
|
|
|
// When the user dwells on a link with their keyboard, a preview is
|
|
|
|
// renderered, and then dwells on another link, the LINK_ABANDON_END
|
|
|
|
// action will be ignored.
|
|
|
|
//
|
|
|
|
// Ensure that all the preview is hidden.
|
|
|
|
shouldShow: false
|
2016-11-16 16:16:43 +00:00
|
|
|
} );
|
2016-11-28 12:00:07 +00:00
|
|
|
case mw.popups.actionTypes.LINK_ABANDON_END:
|
|
|
|
if ( action.el !== state.activeLink ) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* falls through */
|
|
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_END:
|
|
|
|
if ( !state.isUserDwelling ) {
|
|
|
|
return nextState( state, {
|
|
|
|
activeLink: undefined,
|
|
|
|
activeEvent: undefined,
|
|
|
|
fetchResponse: undefined,
|
|
|
|
shouldShow: false
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2016-12-01 09:58:49 +00:00
|
|
|
return state;
|
|
|
|
|
2016-11-28 12:00:07 +00:00
|
|
|
case mw.popups.actionTypes.PREVIEW_DWELL:
|
2016-11-17 21:41:56 +00:00
|
|
|
return nextState( state, {
|
2016-11-28 12:00:07 +00:00
|
|
|
isUserDwelling: true
|
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_START:
|
|
|
|
return nextState( state, {
|
|
|
|
isUserDwelling: false
|
2016-11-16 16:16:43 +00:00
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.FETCH_START:
|
2016-11-17 21:41:56 +00:00
|
|
|
return nextState( state, {
|
2016-11-16 16:16:43 +00:00
|
|
|
fetchResponse: undefined
|
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.FETCH_END:
|
2016-11-25 12:42:02 +00:00
|
|
|
if ( action.el === state.activeLink ) {
|
|
|
|
return nextState( state, {
|
|
|
|
fetchResponse: action.result,
|
|
|
|
shouldShow: true
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* falls through */
|
2016-11-10 18:02:29 +00:00
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-30 13:40:08 +00:00
|
|
|
/**
|
|
|
|
* Reducer for actions that may result in an event being logged via Event
|
|
|
|
* Logging.
|
|
|
|
*
|
|
|
|
* The base data represents data that's shared between all events logged with
|
|
|
|
* the Popups schema ("Popups events"). Very nearly all of it is initialized
|
|
|
|
* during the BOOT action and doesn't change between link interactions, e.g.
|
|
|
|
* the user being an anon or the number of edits they've made.
|
|
|
|
*
|
|
|
|
* The user's number of previews, however, does change between link
|
|
|
|
* interactions and the associated bucket (a computed property) is what is
|
|
|
|
* logged. This is reflected in the state tree: the `previewCount` property is
|
|
|
|
* used to store the user's number of previews and the
|
|
|
|
* `baseData.previewCountBucket` property is used to store the associated
|
|
|
|
* bucket.
|
|
|
|
*
|
|
|
|
* @param {Object} state
|
|
|
|
* @param {Object} action
|
|
|
|
* @return {Object} The state as a result of processing the action
|
|
|
|
*/
|
|
|
|
mw.popups.reducers.eventLogging = function ( state, action ) {
|
2016-12-05 11:56:51 +00:00
|
|
|
var nextCount;
|
|
|
|
|
2016-11-30 13:40:08 +00:00
|
|
|
if ( state === undefined ) {
|
|
|
|
state = {
|
2016-12-05 11:56:51 +00:00
|
|
|
previewCount: undefined,
|
2016-11-30 13:40:08 +00:00
|
|
|
baseData: {},
|
2016-12-06 14:02:13 +00:00
|
|
|
interaction: undefined,
|
2016-11-30 13:40:08 +00:00
|
|
|
event: undefined
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ( action.type ) {
|
|
|
|
case mw.popups.actionTypes.BOOT:
|
|
|
|
return nextState( state, {
|
2016-12-01 14:56:46 +00:00
|
|
|
previewCount: action.user.previewCount,
|
2016-11-30 13:40:08 +00:00
|
|
|
baseData: {
|
|
|
|
pageTitleSource: action.page.title,
|
|
|
|
namespaceIdSource: action.page.namespaceID,
|
|
|
|
pageIdSource: action.page.id,
|
2016-12-01 13:12:29 +00:00
|
|
|
isAnon: action.user.isAnon,
|
|
|
|
popupEnabled: action.user.isInCondition,
|
2016-11-30 13:40:08 +00:00
|
|
|
pageToken: action.pageToken,
|
2016-12-01 13:12:29 +00:00
|
|
|
sessionToken: action.sessionToken,
|
2016-12-01 14:56:46 +00:00
|
|
|
editCountBucket: counts.getEditCountBucket( action.user.editCount ),
|
|
|
|
previewCountBucket: counts.getPreviewCountBucket( action.user.previewCount )
|
2016-11-30 13:40:08 +00:00
|
|
|
},
|
|
|
|
event: {
|
|
|
|
action: 'pageLoaded'
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
case mw.popups.actionTypes.EVENT_LOGGED:
|
|
|
|
return nextState( state, {
|
|
|
|
event: undefined
|
|
|
|
} );
|
|
|
|
|
2016-12-05 11:56:51 +00:00
|
|
|
case mw.popups.actionTypes.PREVIEW_SHOW:
|
|
|
|
nextCount = state.previewCount + 1;
|
|
|
|
|
|
|
|
return nextState( state, {
|
|
|
|
previewCount: nextCount,
|
|
|
|
baseData: nextState( state.baseData, {
|
|
|
|
previewCountBucket: counts.getPreviewCountBucket( nextCount )
|
2016-12-08 09:02:27 +00:00
|
|
|
} ),
|
|
|
|
interaction: nextState( state.interaction, {
|
|
|
|
timeToPreviewShow: action.timestamp - state.interaction.started
|
2016-12-05 11:56:51 +00:00
|
|
|
} )
|
|
|
|
} );
|
|
|
|
|
2016-12-06 14:02:13 +00:00
|
|
|
case mw.popups.actionTypes.LINK_DWELL:
|
|
|
|
return nextState( state, {
|
|
|
|
interaction: {
|
|
|
|
token: action.interactionToken,
|
|
|
|
started: action.timestamp
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
2016-12-07 10:56:54 +00:00
|
|
|
case mw.popups.actionTypes.LINK_CLICK:
|
|
|
|
return nextState( state, {
|
|
|
|
event: {
|
|
|
|
action: 'opened',
|
|
|
|
linkInteractionToken: state.interaction.token,
|
|
|
|
totalInteractionTime: Math.round( action.timestamp - state.interaction.started )
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
2016-12-08 09:02:27 +00:00
|
|
|
case mw.popups.actionTypes.LINK_ABANDON_START:
|
|
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_START:
|
|
|
|
return nextState( state, {
|
|
|
|
interaction: nextState( state.interaction, {
|
|
|
|
finished: action.timestamp
|
|
|
|
} )
|
|
|
|
} );
|
|
|
|
|
|
|
|
case mw.popups.actionTypes.LINK_ABANDON_END:
|
|
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_END:
|
|
|
|
return nextState( state, {
|
|
|
|
event: {
|
|
|
|
action: state.interaction.timeToPreviewShow ? 'dismissed' : 'dwelledButAbandoned',
|
|
|
|
linkInteractionToken: state.interaction.token,
|
|
|
|
totalInteractionTime: Math.round( state.interaction.finished - state.interaction.started )
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
2016-11-30 13:40:08 +00:00
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-10 18:02:29 +00:00
|
|
|
/**
|
2016-12-12 12:54:33 +00:00
|
|
|
* Reducer for actions that modify the state of the view
|
|
|
|
*
|
|
|
|
* @param {Object} state before action
|
|
|
|
* @param {Object} action Redux action that modified state.
|
|
|
|
* Must have `type` property.
|
|
|
|
* @return {Object} state after action
|
2016-11-10 18:02:29 +00:00
|
|
|
*/
|
2016-12-12 12:54:33 +00:00
|
|
|
mw.popups.reducers.renderer = function ( state, action ) {
|
2016-11-10 18:02:29 +00:00
|
|
|
if ( state === undefined ) {
|
|
|
|
state = {
|
2016-12-12 12:54:33 +00:00
|
|
|
isAnimating: false,
|
|
|
|
isInteractive: false,
|
|
|
|
showSettings: false
|
2016-11-10 18:02:29 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ( action.type ) {
|
2016-12-12 12:54:33 +00:00
|
|
|
case mw.popups.actionTypes.PREVIEW_ANIMATING:
|
|
|
|
return $.extend( {}, state, {
|
|
|
|
isAnimating: true,
|
|
|
|
isInteractive: false,
|
|
|
|
showSettings: false
|
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.PREVIEW_INTERACTIVE:
|
|
|
|
return $.extend( OO.copy( state ), {
|
|
|
|
isAnimating: false,
|
|
|
|
isInteractive: true,
|
|
|
|
showSettings: false
|
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.PREVIEW_CLICK:
|
|
|
|
return $.extend( OO.copy( state ), {
|
|
|
|
isAnimating: false,
|
|
|
|
isInteractive: false,
|
|
|
|
showSettings: false
|
|
|
|
} );
|
2016-11-16 16:16:43 +00:00
|
|
|
case mw.popups.actionTypes.COG_CLICK:
|
2016-12-12 12:54:33 +00:00
|
|
|
return $.extend( OO.copy( state ), {
|
|
|
|
isAnimating: true,
|
|
|
|
isInteractive: false,
|
|
|
|
showSettings: true
|
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.SETTINGS_DIALOG_INTERACTIVE:
|
|
|
|
return $.extend( OO.copy( state ), {
|
|
|
|
isAnimating: false,
|
|
|
|
isInteractive: true,
|
|
|
|
showSettings: true
|
2016-11-16 16:16:43 +00:00
|
|
|
} );
|
|
|
|
case mw.popups.actionTypes.SETTINGS_DIALOG_CLOSED:
|
2016-12-12 12:54:33 +00:00
|
|
|
return $.extend( OO.copy( state ), {
|
|
|
|
isAnimating: false,
|
|
|
|
isInteractive: false,
|
|
|
|
showSettings: false
|
2016-11-10 18:02:29 +00:00
|
|
|
} );
|
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|
2016-12-12 12:54:33 +00:00
|
|
|
}( mediaWiki, jQuery ) );
|