mediawiki-extensions-Popups/resources/dist/index.js.map
Timo Tijhof 4996886eb9 eventLogging: Use base 32 instead of 16 for fnv-encoded hash
Follows-up 79f3b318d0.

Number#toString supports up to Base 32.
Same collision behaviour but with a shorter string.
Typical length with Base 32: 7 (max: 11)
Typical length with Base 16: 9 (max: 14)

Change-Id: I91e91341cbecdec24549ace6a6300550f5b449ee
2017-06-12 21:43:05 +01:00

1 line
264 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{"version":3,"sources":["/w/extensions/Popups/index.js","/w/extensions/Popups/webpack/bootstrap dc5653e6684c29ac34ed","/w/extensions/Popups/./~/redux-thunk/dist/redux-thunk.min.js","/w/extensions/Popups/./~/redux/dist/redux.min.js","/w/extensions/Popups/./src/actionTypes.js","/w/extensions/Popups/./src/actions.js","/w/extensions/Popups/./src/changeListener.js","/w/extensions/Popups/./src/changeListeners/eventLogging.js","/w/extensions/Popups/./src/changeListeners/footerLink.js","/w/extensions/Popups/./src/changeListeners/index.js","/w/extensions/Popups/./src/changeListeners/linkTitle.js","/w/extensions/Popups/./src/changeListeners/render.js","/w/extensions/Popups/./src/changeListeners/settings.js","/w/extensions/Popups/./src/changeListeners/statsv.js","/w/extensions/Popups/./src/changeListeners/syncUserSettings.js","/w/extensions/Popups/./src/constants.js","/w/extensions/Popups/./src/counts.js","/w/extensions/Popups/./src/formatter.js","/w/extensions/Popups/./src/gateway/mediawiki.js","/w/extensions/Popups/./src/gateway/rest.js","/w/extensions/Popups/./src/index.js","/w/extensions/Popups/./src/isEnabled.js","/w/extensions/Popups/./src/preview/model.js","/w/extensions/Popups/./src/previewBehavior.js","/w/extensions/Popups/./src/reducers/eventLogging.js","/w/extensions/Popups/./src/reducers/index.js","/w/extensions/Popups/./src/reducers/nextState.js","/w/extensions/Popups/./src/reducers/preview.js","/w/extensions/Popups/./src/reducers/settings.js","/w/extensions/Popups/./src/reducers/statsv.js","/w/extensions/Popups/./src/renderer.js","/w/extensions/Popups/./src/schema.js","/w/extensions/Popups/./src/settingsDialog.js","/w/extensions/Popups/./src/statsvInstrumentation.js","/w/extensions/Popups/./src/title.js","/w/extensions/Popups/./src/userSettings.js","/w/extensions/Popups/./src/wait.js"],"names":["modules","__webpack_require__","moduleId","installedModules","exports","module","i","l","call","m","c","value","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","./node_modules/redux-thunk/dist/redux-thunk.min.js","t","e","this","r","id","loaded","dispatch","getState","withExtraArgument","default","./node_modules/redux/dist/redux.min.js","compose","applyMiddleware","bindActionCreators","combineReducers","createStore","u","a","f","arguments","length","Array","slice","reduceRight","apply","b","h","v","Error","push","indexOf","splice","type","y","INIT","subscribe","next","TypeError","unsubscribe","replaceReducer","ActionTypes","console","error","constructor","Function","toString","map","assign","keys","forEach","Math","random","substring","split","join","getPrototypeOf","window","Symbol","observable","./src/actionTypes.js","BOOT","LINK_DWELL","ABANDON_START","ABANDON_END","LINK_CLICK","FETCH_START","FETCH_END","FETCH_COMPLETE","FETCH_FAILED","PREVIEW_DWELL","PREVIEW_SHOW","PREVIEW_CLICK","SETTINGS_SHOW","SETTINGS_HIDE","SETTINGS_CHANGE","EVENT_LOGGED","STATSV_LOGGED","./src/actions.js","timedAction","baseAction","timestamp","mw","now","$","jQuery","mediaWiki","actions","types","wait","boot","isEnabled","user","userSettings","generateToken","config","editCount","previewCount","getPreviewCount","isNavPopupsEnabled","sessionToken","sessionId","pageToken","page","title","namespaceID","isAnon","fetch","gateway","el","token","titleText","getPrefixedDb","namespace","request","getPageSummary","then","result","fail","when","FETCH_COMPLETE_TARGET_DELAY","linkDwell","event","isNewInteraction","preview","activeToken","action","enabled","abandon","linkClick","previewDwell","previewShow","showSettings","hideSettings","saveSettings","wasEnabled","eventLogged","statsvLogged","./src/changeListener.js","store","callback","state","prevState","./src/changeListeners/eventLogging.js","fnv1a32","string","charCodeAt","boundActions","schema","track","tokenToSeenMap","hashToSeenMap","_","hash","eventLogging","shouldLog","linkInteractionToken","JSON","stringify","extend","baseData","log","./src/changeListeners/footerLink.js","createFooterLink","$footer","$link","append","attr","text","message","hide","parent","$footerLink","undefined","click","preventDefault","settings","shouldShowFooterLink","show","./src/changeListeners/index.js","footerLink","linkTitle","render","statsv","syncUserSettings","./src/changeListeners/linkTitle.js","destroyTitleAttr","$el","restoreTitleAttr","hasPrevActiveLink","activeLink","./src/changeListeners/render.js","renderer","previewBehavior","shouldShow","fetchResponse","activeEvent","./src/changeListeners/settings.js","appendTo","document","body","setEnabled","showHelp","toggleHelp","./src/changeListeners/statsv.js","data","./src/changeListeners/syncUserSettings.js","reducer","prop","syncIfChanged","sync","current","setPreviewCount","setIsEnabled","./src/constants.js","THUMBNAIL_SIZE","bracketedDevicePixelRatio","EXTRACT_LENGTH","./src/counts.js","getEditCountBucket","count","bucket","getPreviewCountBucket","./src/formatter.js","htmlize","plainTextExtract","extract","removeParentheticals","removeEllipsis","makeTitleInExtractBold","regExp","escapedTitle","elements","boldIdentifier","snip","replace","trim","RegExp","escape","each","index","part","createTextNode","ch","level","charAt","./src/gateway/mediawiki.js","extractPageFromResponse","query","pages","plainTextHTMLizer","convertPageToModel","createModel","canonicalurl","pagelanguagehtmlcode","pagelanguagedir","thumbnail","CACHE_LIFETIME","api","formatversion","redirects","exintro","exchars","explaintext","piprop","pithumbsize","pilicense","rvprop","inprop","titles","smaxage","maxage","uselang","headers","X-Analytics","./src/gateway/rest.js","generateThumbnailData","original","thumbSize","filename","width","height","parts","source","lastPart","substr","floor","Title","getUrl","lang","dir","originalimage","ajax","url","encodeURIComponent","Accept","Deferred","resolve","jqXHR","status","reject","promise","./src/index.js","createGateway","createRESTBaseGateway","constants","createMediaWikiApiGateway","Api","getStatsvTracker","experiments","statsvInstrumentation","noop","registerChangeListeners","settingsDialog","statsvTracker","registerChangeListener","changeListeners","Redux","ReduxThunk","createUserSettings","createPreviewBehavior","createSchema","createSettingsDialogRenderer","createIsEnabled","reducers","BLACKLISTED_LINKS","requestIdleCallback","generateRandomSessionId","storage","__REDUX_DEVTOOLS_EXTENSION_COMPOSE__","loader","using","done","hook","add","$container","invalidLinksSelector","validLinkSelector","init","on","mwTitle","fromElement","./src/isEnabled.js","isUserSampled","samplingRate","getBucket","buckets","control","A","hasIsEnabled","getIsEnabled","./src/preview/model.js","processExtract","TYPE_GENERIC","TYPE_PAGE","languageCode","languageDirection","processedExtract","./src/previewBehavior.js","rawTitle","settingsUrl","isBetaFeature","newFromText","previewAbandon","./src/reducers/eventLogging.js","getBaseData","bootAction","pageTitleSource","namespaceIdSource","pageIdSource","popupEnabled","previewCountBucket","counts","hovercardsSuppressedByGadget","editCountBucket","createEvent","interaction","actionData","pageTitleHover","namespaceIdHover","timeToPreviewShow","previewType","perceivedWait","createClosingEvent","totalInteractionTime","round","finished","started","finalized","actionTypes","nextState","nextCount","newState","actionTypesWithTokens","link","isUserDwelling","./src/reducers/index.js","./src/reducers/nextState.js","updates","key","./src/reducers/preview.js","./src/reducers/settings.js","./src/reducers/statsv.js","fetchStartedAt","linkDwellStartedAt","./src/renderer.js","createPokeyMasks","container","html","model","createEmptyPreview","createPreview","target","templateData","createThumbnail","hasThumbnail","template","find","isTall","extractMsg","msg","readMsg","behavior","layout","createLayout","pageX","pageY","clientY","clientRects","getClientRects","offset","scrollTop","$window","SIZES","pokeySize","layoutPreview","getClasses","landscapeImage","bindBehavior","hover","stopPropagation","fadeInClass","fadeOutClass","hasClass","removeClass","addClass","remove","rawThumbnail","tall","thumbWidth","thumbHeight","x","clipPath","devicePixelRatio","w","portraitImage","createThumbnailElement","className","thumbnailWidth","thumbnailHeight","$thumbnailSVGImage","nsSvg","createElementNS","setAttributeNS","clip-path","xmlns","isPreviewTall","eventData","linkData","windowData","flippedX","flippedY","offsetTop","getClosestYPosition","top","clientTop","offsetLeft","left","landscapePopupWidth","portraitPopupWidth","classes","predefinedLandscapeImageHeight","popup","css","outerHeight","removeAttribute","setAttribute","rects","isTop","deltaY","minY","rect","abs","bottom","ceil","./src/schema.js","navigator","isFunction","sendBeacon","eventLog","Schema","./src/settingsDialog.js","createSettingsDialog","path","choices","description","image","isChecked","heading","closeLabel","saveLabel","helpText","okLabel","descriptionText","getSelectedSetting","val","visible","$dialog","pg","fn","disablePopups","$overlay","selected","outerWidth","./src/statsvInstrumentation.js","./src/title.js","getTitle","href","linkHref","matches","queryLength","titleRegex","Uri","host","location","hostname","exec","decodeURIComponent","isValid","contentNamespaces","inArray","./src/userSettings.js","set","Boolean","parseInt","./src/wait.js","delay","setTimeout"],"mappings":"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","file":"index.js","sourcesContent":["/*@nomin*/\n/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// identity function for calling harmony imports with the correct context\n/******/ \t__webpack_require__.i = function(value) { return value; };\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, {\n/******/ \t\t\t\tconfigurable: false,\n/******/ \t\t\t\tenumerable: true,\n/******/ \t\t\t\tget: getter\n/******/ \t\t\t});\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = \"./src/index.js\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./node_modules/redux-thunk/dist/redux-thunk.min.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n!function(t,e){ true?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.ReduxThunk=e():t.ReduxThunk=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p=\"\",e(0)}([function(t,e,n){t.exports=n(1)},function(t,e){\"use strict\";function n(t){return function(e){var n=e.dispatch,o=e.getState;return function(e){return function(r){return\"function\"==typeof r?r(n,o,t):e(r)}}}}e.__esModule=!0;var o=n();o.withExtraArgument=n,e.default=o}])});\n\n/***/ }),\n\n/***/ \"./node_modules/redux/dist/redux.min.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n!function(t,e){ true?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.Redux=e():t.Redux=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p=\"\",e(0)}([function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}e.__esModule=!0,e.compose=e.applyMiddleware=e.bindActionCreators=e.combineReducers=e.createStore=void 0;var o=n(2),u=r(o),i=n(7),c=r(i),a=n(6),f=r(a),s=n(5),d=r(s),l=n(1),p=r(l),y=n(3);r(y);e.createStore=u[\"default\"],e.combineReducers=c[\"default\"],e.bindActionCreators=f[\"default\"],e.applyMiddleware=d[\"default\"],e.compose=p[\"default\"]},function(t,e){\"use strict\";function n(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];if(0===e.length)return function(t){return t};if(1===e.length)return e[0];var r=e[e.length-1],o=e.slice(0,-1);return function(){return o.reduceRight(function(t,e){return e(t)},r.apply(void 0,arguments))}}e.__esModule=!0,e[\"default\"]=n},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e,n){function r(){b===h&&(b=h.slice())}function u(){return v}function c(t){if(\"function\"!=typeof t)throw Error(\"Expected listener to be a function.\");var e=!0;return r(),b.push(t),function(){if(e){e=!1,r();var n=b.indexOf(t);b.splice(n,1)}}}function s(t){if(!(0,i[\"default\"])(t))throw Error(\"Actions must be plain objects. Use custom middleware for async actions.\");if(void 0===t.type)throw Error('Actions may not have an undefined \"type\" property. Have you misspelled a constant?');if(m)throw Error(\"Reducers may not dispatch actions.\");try{m=!0,v=y(v,t)}finally{m=!1}for(var e=h=b,n=0;e.length>n;n++)e[n]();return t}function d(t){if(\"function\"!=typeof t)throw Error(\"Expected the nextReducer to be a function.\");y=t,s({type:f.INIT})}function l(){var t,e=c;return t={subscribe:function(t){function n(){t.next&&t.next(u())}if(\"object\"!=typeof t)throw new TypeError(\"Expected the observer to be an object.\");n();var r=e(n);return{unsubscribe:r}}},t[a[\"default\"]]=function(){return this},t}var p;if(\"function\"==typeof e&&void 0===n&&(n=e,e=void 0),void 0!==n){if(\"function\"!=typeof n)throw Error(\"Expected the enhancer to be a function.\");return n(o)(t,e)}if(\"function\"!=typeof t)throw Error(\"Expected the reducer to be a function.\");var y=t,v=e,h=[],b=h,m=!1;return s({type:f.INIT}),p={dispatch:s,subscribe:c,getState:u,replaceReducer:d},p[a[\"default\"]]=l,p}e.__esModule=!0,e.ActionTypes=void 0,e[\"default\"]=o;var u=n(4),i=r(u),c=n(12),a=r(c),f=e.ActionTypes={INIT:\"@@redux/INIT\"}},function(t,e){\"use strict\";function n(t){\"undefined\"!=typeof console&&\"function\"==typeof console.error&&console.error(t);try{throw Error(t)}catch(e){}}e.__esModule=!0,e[\"default\"]=n},function(t,e,n){function r(t){if(!i(t)||p.call(t)!=c||u(t))return!1;var e=o(t);if(null===e)return!0;var n=d.call(e,\"constructor\")&&e.constructor;return\"function\"==typeof n&&n instanceof n&&s.call(n)==l}var o=n(8),u=n(9),i=n(11),c=\"[object Object]\",a=Function.prototype,f=Object.prototype,s=a.toString,d=f.hasOwnProperty,l=s.call(Object),p=f.toString;t.exports=r},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];return function(t){return function(n,r,o){var i=t(n,r,o),a=i.dispatch,f=[],s={getState:i.getState,dispatch:function(t){return a(t)}};return f=e.map(function(t){return t(s)}),a=c[\"default\"].apply(void 0,f)(i.dispatch),u({},i,{dispatch:a})}}}e.__esModule=!0;var u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t};e[\"default\"]=o;var i=n(1),c=r(i)},function(t,e){\"use strict\";function n(t,e){return function(){return e(t.apply(void 0,arguments))}}function r(t,e){if(\"function\"==typeof t)return n(t,e);if(\"object\"!=typeof t||null===t)throw Error(\"bindActionCreators expected an object or a function, instead received \"+(null===t?\"null\":typeof t)+'. Did you write \"import ActionCreators from\" instead of \"import * as ActionCreators from\"?');for(var r=Object.keys(t),o={},u=0;r.length>u;u++){var i=r[u],c=t[i];\"function\"==typeof c&&(o[i]=n(c,e))}return o}e.__esModule=!0,e[\"default\"]=r},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e){var n=e&&e.type,r=n&&'\"'+n+'\"'||\"an action\";return\"Given action \"+r+', reducer \"'+t+'\" returned undefined. To ignore an action, you must explicitly return the previous state.'}function u(t){Object.keys(t).forEach(function(e){var n=t[e],r=n(void 0,{type:c.ActionTypes.INIT});if(void 0===r)throw Error('Reducer \"'+e+'\" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined.');var o=\"@@redux/PROBE_UNKNOWN_ACTION_\"+Math.random().toString(36).substring(7).split(\"\").join(\".\");if(void 0===n(void 0,{type:o}))throw Error('Reducer \"'+e+'\" returned undefined when probed with a random type. '+(\"Don't try to handle \"+c.ActionTypes.INIT+' or other actions in \"redux/*\" ')+\"namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined.\")})}function i(t){for(var e=Object.keys(t),n={},r=0;e.length>r;r++){var i=e[r];\"function\"==typeof t[i]&&(n[i]=t[i])}var c,a=Object.keys(n);try{u(n)}catch(f){c=f}return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments[1];if(c)throw c;for(var r=!1,u={},i=0;a.length>i;i++){var f=a[i],s=n[f],d=t[f],l=s(d,e);if(void 0===l){var p=o(f,e);throw Error(p)}u[f]=l,r=r||l!==d}return r?u:t}}e.__esModule=!0,e[\"default\"]=i;var c=n(2),a=n(4),f=(r(a),n(3));r(f)},function(t,e,n){var r=n(10),o=r(Object.getPrototypeOf,Object);t.exports=o},function(t,e){function n(t){var e=!1;if(null!=t&&\"function\"!=typeof t.toString)try{e=!!(t+\"\")}catch(n){}return e}t.exports=n},function(t,e){function n(t,e){return function(n){return t(e(n))}}t.exports=n},function(t,e){function n(t){return!!t&&\"object\"==typeof t}t.exports=n},function(t,e,n){t.exports=n(13)},function(t,e,n){(function(t){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}Object.defineProperty(e,\"__esModule\",{value:!0});var o=n(14),u=r(o),i=void 0;void 0!==t?i=t:\"undefined\"!=typeof window&&(i=window);var c=(0,u[\"default\"])(i);e[\"default\"]=c}).call(e,function(){return this}())},function(t,e){\"use strict\";function n(t){var e,n=t.Symbol;return\"function\"==typeof n?n.observable?e=n.observable:(e=n(\"observable\"),n.observable=e):e=\"@@observable\",e}Object.defineProperty(e,\"__esModule\",{value:!0}),e[\"default\"]=n}])});\n\n/***/ }),\n\n/***/ \"./src/actionTypes.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module actionTypes\n */\n\nmodule.exports = {\n\tBOOT: 'BOOT',\n\tLINK_DWELL: 'LINK_DWELL',\n\tABANDON_START: 'ABANDON_START',\n\tABANDON_END: 'ABANDON_END',\n\tLINK_CLICK: 'LINK_CLICK',\n\tFETCH_START: 'FETCH_START',\n\tFETCH_END: 'FETCH_END',\n\tFETCH_COMPLETE: 'FETCH_COMPLETE',\n\tFETCH_FAILED: 'FETCH_FAILED',\n\tPREVIEW_DWELL: 'PREVIEW_DWELL',\n\tPREVIEW_SHOW: 'PREVIEW_SHOW',\n\tPREVIEW_CLICK: 'PREVIEW_CLICK',\n\tSETTINGS_SHOW: 'SETTINGS_SHOW',\n\tSETTINGS_HIDE: 'SETTINGS_HIDE',\n\tSETTINGS_CHANGE: 'SETTINGS_CHANGE',\n\tEVENT_LOGGED: 'EVENT_LOGGED',\n\tSTATSV_LOGGED: 'STATSV_LOGGED'\n};\n\n\n/***/ }),\n\n/***/ \"./src/actions.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module actions\n */\n\nvar $ = jQuery,\n\tmw = window.mediaWiki,\n\tactions = {},\n\ttypes = __webpack_require__( \"./src/actionTypes.js\" ),\n\twait = __webpack_require__( \"./src/wait.js\" ),\n\n\t// See the following for context around this value.\n\t//\n\t// * https://phabricator.wikimedia.org/T161284\n\t// * https://phabricator.wikimedia.org/T70861#3129780\n\tFETCH_START_DELAY = 150, // ms.\n\n\t// The delay after which a FETCH_COMPLETE action should be dispatched.\n\t//\n\t// If the API endpoint responds faster than 500 ms (or, say, the API\n\t// response is served from the UA's cache), then we introduce a delay of\n\t// 500 - t to make the preview delay consistent to the user.\n\tFETCH_COMPLETE_TARGET_DELAY = 500, // ms.\n\n\tABANDON_END_DELAY = 300; // ms.\n\n/**\n * Mixes in timing information to an action.\n *\n * Warning: the `baseAction` parameter is modified and returned.\n *\n * @param {Object} baseAction\n * @return {Object}\n */\nfunction timedAction( baseAction ) {\n\tbaseAction.timestamp = mw.now();\n\n\treturn baseAction;\n}\n\n/**\n * Represents Page Previews booting.\n *\n * When a Redux store is created, the `@@INIT` action is immediately\n * dispatched to it. To avoid overriding the term, we refer to booting rather\n * than initializing.\n *\n * Page Previews persists critical pieces of information to local storage.\n * Since reading from and writing to local storage are synchronous, Page\n * Previews is booted when the browser is idle (using\n * [`mw.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback))\n * so as not to impact latency-critical events.\n *\n * @param {Boolean} isEnabled See `isEnabled.js`\n * @param {mw.user} user\n * @param {ext.popups.UserSettings} userSettings\n * @param {Function} generateToken\n * @param {mw.Map} config The config of the MediaWiki client-side application,\n * i.e. `mw.config`\n * @returns {Object}\n */\nactions.boot = function (\n\tisEnabled,\n\tuser,\n\tuserSettings,\n\tgenerateToken,\n\tconfig\n) {\n\tvar editCount = config.get( 'wgUserEditCount' ),\n\t\tpreviewCount = userSettings.getPreviewCount();\n\n\treturn {\n\t\ttype: types.BOOT,\n\t\tisEnabled: isEnabled,\n\t\tisNavPopupsEnabled: config.get( 'wgPopupsConflictsWithNavPopupGadget' ),\n\t\tsessionToken: user.sessionId(),\n\t\tpageToken: generateToken(),\n\t\tpage: {\n\t\t\ttitle: config.get( 'wgTitle' ),\n\t\t\tnamespaceID: config.get( 'wgNamespaceNumber' ),\n\t\t\tid: config.get( 'wgArticleId' )\n\t\t},\n\t\tuser: {\n\t\t\tisAnon: user.isAnon(),\n\t\t\teditCount: editCount,\n\t\t\tpreviewCount: previewCount\n\t\t}\n\t};\n};\n\n/**\n * Represents Page Previews fetching data via the gateway.\n *\n * @param {ext.popups.Gateway} gateway\n * @param {mw.Title} title\n * @param {Element} el\n * @param {String} token The unique token representing the link interaction that\n * triggered the fetch\n * @return {Redux.Thunk}\n */\nactions.fetch = function ( gateway, title, el, token ) {\n\tvar titleText = title.getPrefixedDb(),\n\t\tnamespaceID = title.namespace;\n\n\treturn function ( dispatch ) {\n\t\tvar request;\n\n\t\tdispatch( timedAction( {\n\t\t\ttype: types.FETCH_START,\n\t\t\tel: el,\n\t\t\ttitle: titleText,\n\t\t\tnamespaceID: namespaceID\n\t\t} ) );\n\n\t\trequest = gateway.getPageSummary( titleText )\n\t\t\t.then( function ( result ) {\n\t\t\t\tdispatch( timedAction( {\n\t\t\t\t\ttype: types.FETCH_END,\n\t\t\t\t\tel: el\n\t\t\t\t} ) );\n\n\t\t\t\treturn result;\n\t\t\t} )\n\t\t\t.fail( function () {\n\t\t\t\tdispatch( {\n\t\t\t\t\ttype: types.FETCH_FAILED,\n\t\t\t\t\tel: el\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t$.when( request, wait( FETCH_COMPLETE_TARGET_DELAY - FETCH_START_DELAY ) )\n\t\t\t.then( function ( result ) {\n\t\t\t\tdispatch( timedAction( {\n\t\t\t\t\ttype: types.FETCH_COMPLETE,\n\t\t\t\t\tel: el,\n\t\t\t\t\tresult: result,\n\t\t\t\t\ttoken: token\n\t\t\t\t} ) );\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user dwelling on a link, either by hovering over it with\n * their mouse or by focussing it using their keyboard or an assistive device.\n *\n * @param {mw.Title} title\n * @param {Element} el\n * @param {Event} event\n * @param {ext.popups.Gateway} gateway\n * @param {Function} generateToken\n * @return {Redux.Thunk}\n */\nactions.linkDwell = function ( title, el, event, gateway, generateToken ) {\n\tvar token = generateToken(),\n\t\ttitleText = title.getPrefixedDb(),\n\t\tnamespaceID = title.namespace;\n\n\treturn function ( dispatch, getState ) {\n\t\tvar action = timedAction( {\n\t\t\ttype: types.LINK_DWELL,\n\t\t\tel: el,\n\t\t\tevent: event,\n\t\t\ttoken: token,\n\t\t\ttitle: titleText,\n\t\t\tnamespaceID: namespaceID\n\t\t} );\n\n\t\t// Has the new generated token been accepted?\n\t\tfunction isNewInteraction() {\n\t\t\treturn getState().preview.activeToken === token;\n\t\t}\n\n\t\tdispatch( action );\n\n\t\tif ( !isNewInteraction() ) {\n\t\t\treturn;\n\t\t}\n\n\t\twait( FETCH_START_DELAY )\n\t\t\t.then( function () {\n\t\t\t\tvar previewState = getState().preview;\n\n\t\t\t\tif ( previewState.enabled && isNewInteraction() ) {\n\t\t\t\t\tdispatch( actions.fetch( gateway, title, el, token ) );\n\t\t\t\t}\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user abandoning a link, either by moving their mouse away\n * from it or by shifting focus to another UI element using their keyboard or\n * an assistive device, or abandoning a preview by moving their mouse away\n * from it.\n *\n * @return {Redux.Thunk}\n */\nactions.abandon = function () {\n\treturn function ( dispatch, getState ) {\n\t\tvar token = getState().preview.activeToken;\n\n\t\tif ( !token ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdispatch( timedAction( {\n\t\t\ttype: types.ABANDON_START,\n\t\t\ttoken: token\n\t\t} ) );\n\n\t\twait( ABANDON_END_DELAY )\n\t\t\t.then( function () {\n\t\t\t\tdispatch( {\n\t\t\t\t\ttype: types.ABANDON_END,\n\t\t\t\t\ttoken: token\n\t\t\t\t} );\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user clicking on a link with their mouse, keyboard, or an\n * assistive device.\n *\n * @param {Element} el\n * @return {Object}\n */\nactions.linkClick = function ( el ) {\n\treturn timedAction( {\n\t\ttype: types.LINK_CLICK,\n\t\tel: el\n\t} );\n};\n\n/**\n * Represents the user dwelling on a preview with their mouse.\n *\n * @return {Object}\n */\nactions.previewDwell = function () {\n\treturn {\n\t\ttype: types.PREVIEW_DWELL\n\t};\n};\n\n/**\n * Represents a preview being shown to the user.\n *\n * This action is dispatched by the `./changeListeners/render.js` change\n * listener.\n *\n * @param {String} token\n * @return {Object}\n */\nactions.previewShow = function ( token ) {\n\treturn timedAction( {\n\t\ttype: types.PREVIEW_SHOW,\n\t\ttoken: token\n\t} );\n};\n\n/**\n * Represents the user clicking either the \"Enable previews\" footer menu link,\n * or the \"cog\" icon that's present on each preview.\n *\n * @return {Object}\n */\nactions.showSettings = function () {\n\treturn {\n\t\ttype: types.SETTINGS_SHOW\n\t};\n};\n\n/**\n * Represents the user closing the settings dialog and saving their settings.\n *\n * @return {Object}\n */\nactions.hideSettings = function () {\n\treturn {\n\t\ttype: types.SETTINGS_HIDE\n\t};\n};\n\n/**\n * Represents the user saving their settings.\n *\n * N.B. This action returns a Redux.Thunk not because it needs to perform\n * asynchronous work, but because it needs to query the global state for the\n * current enabled state. In order to keep the enabled state in a single\n * place (the preview reducer), we query it and dispatch it as `wasEnabled`\n * so that other reducers (like settings) can act on it without having to\n * duplicate the `enabled` state locally.\n * See doc/adr/0003-keep-enabled-state-only-in-preview-reducer.md for more\n * details.\n *\n * @param {Boolean} enabled if previews are enabled or not\n * @return {Redux.Thunk}\n */\nactions.saveSettings = function ( enabled ) {\n\treturn function ( dispatch, getState ) {\n\t\tdispatch( {\n\t\t\ttype: types.SETTINGS_CHANGE,\n\t\t\twasEnabled: getState().preview.enabled,\n\t\t\tenabled: enabled\n\t\t} );\n\t};\n};\n\n/**\n * Represents the queued event being logged `changeListeners/eventLogging.js`\n * change listener.\n *\n * @param {Object} event\n * @return {Object}\n */\nactions.eventLogged = function ( event ) {\n\treturn {\n\t\ttype: types.EVENT_LOGGED,\n\t\tevent: event\n\t};\n};\n\n/**\n * Represents the queued statsv event being logged.\n * See `mw.popups.changeListeners.statsv` change listener.\n *\n * @return {Object}\n */\nactions.statsvLogged = function () {\n\treturn {\n\t\ttype: types.STATSV_LOGGED\n\t};\n};\nmodule.exports = actions;\n\n\n/***/ }),\n\n/***/ \"./src/changeListener.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module changeListener\n */\n\n/**\n * @typedef {Function} ext.popups.ChangeListener\n * @param {Object} prevState The previous state\n * @param {Object} state The current state\n */\n\n/**\n * Registers a change listener, which is bound to the\n * [store](http://redux.js.org/docs/api/Store.html).\n *\n * A change listener is a function that is only invoked when the state in the\n * [store](http://redux.js.org/docs/api/Store.html) changes. N.B. that there\n * may not be a 1:1 correspondence with actions being dispatched to the store\n * and the state in the store changing.\n *\n * See [Store#subscribe](http://redux.js.org/docs/api/Store.html#subscribe)\n * for more information about what change listeners may and may not do.\n *\n * @param {Redux.Store} store\n * @param {ext.popups.ChangeListener} callback\n */\nmodule.exports = function ( store, callback ) {\n\t// This function is based on the example in [the documentation for\n\t// Store#subscribe](http://redux.js.org/docs/api/Store.html#subscribe),\n\t// which was written by Dan Abramov.\n\n\tvar state;\n\n\tstore.subscribe( function () {\n\t\tvar prevState = state;\n\n\t\tstate = store.getState();\n\n\t\tif ( prevState !== state ) {\n\t\t\tcallback( prevState, state );\n\t\t}\n\t} );\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/eventLogging.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module changeListeners/eventLogging\n */\n\nvar $ = jQuery;\n\n/**\n * Hashes the string using the 32-bit FNV-1a algorithm.\n *\n * @see http://isthe.com/chongo/tech/comp/fnv/#FNV-1a\n * @see http://isthe.com/chongo/tech/comp/fnv/#FNV-param\n *\n * @param {String} string\n * @return {Number} A 32-bit unsigned integer\n */\nfunction fnv1a32( string ) {\n\t/* eslint-disable no-bitwise */\n\n\tvar result = 2166136261, // Offset basis.\n\t\ti = 0;\n\n\tfor ( i = 0; i < string.length; ++i ) {\n\t\tresult ^= string.charCodeAt( i );\n\t\tresult *= 16777619; // Prime.\n\t}\n\n\treturn result >>> 0;\n\t/* eslint-enable no-bitwise */\n}\n\n/**\n * Creates an instance of the event logging change listener.\n *\n * When an event is enqueued it'll be logged using the schema. Since it's the\n * responsibility of Event Logging (and the UA) to deliver logged events,\n * `EVENT_LOGGED` is immediately dispatched rather than waiting for some\n * indicator of completion.\n *\n * This change listener also stores hashes of all enqueued events. If a\n * duplicate event is queued - there's a hash collision - then the\n * `PagePreviews.EventLogging.DuplicateEvent` counter is incremented via [the\n * \"StatsD timers and counters\" analytics event protocol][0].\n *\n * See the following for additional context:\n *\n * * https://phabricator.wikimedia.org/T161769\n * * https://phabricator.wikimedia.org/T163198\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} boundActions\n * @param {mw.eventLog.Schema} schema\n * @param {ext.popups.EventTracker} track\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, schema, track ) {\n\tvar tokenToSeenMap = {},\n\t\thashToSeenMap = {};\n\n\treturn function ( _, state ) {\n\t\tvar eventLogging = state.eventLogging,\n\t\t\tevent = eventLogging.event,\n\t\t\ttoken,\n\t\t\thash,\n\t\t\tshouldLog = true;\n\n\t\tif ( !event ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttoken = event.linkInteractionToken;\n\n\t\tif ( tokenToSeenMap[ token ] === true ) {\n\t\t\ttrack( 'counter.PagePreviews.EventLogging.DuplicateToken', 1 );\n\n\t\t\tshouldLog = false;\n\t\t}\n\n\t\ttokenToSeenMap[ token ] = true;\n\n\t\t// Use 32-bit FNV-1a based on Ian Boyd's (incredibly detailed) analysis of\n\t\t// several algorithms designed to quickly hash a string\n\t\t// <https://softwareengineering.stackexchange.com/a/145633>.\n\t\t//\n\t\t// ...\n\t\t//\n\t\t// It's also remarkably easy to implement!\n\t\thash = fnv1a32( JSON.stringify( event ) ).toString( 32 );\n\n\t\t// Has the event been seen before?\n\t\tif ( hashToSeenMap[ hash ] === true ) {\n\t\t\ttrack( 'counter.PagePreviews.EventLogging.DuplicateEvent', 1 );\n\n\t\t\tshouldLog = false;\n\t\t}\n\n\t\thashToSeenMap[ hash ] = true;\n\n\t\tevent = $.extend( true, {}, eventLogging.baseData, event );\n\n\t\tif ( shouldLog ) {\n\t\t\tschema.log( event );\n\t\t}\n\n\t\t// Dispatch the eventLogged action even if it was a duplicate so that the\n\t\t// state tree can be cleared/updated.\n\t\tboundActions.eventLogged( event );\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/footerLink.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module changeListeners/footerLink\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates the link element and appends it to the footer element.\n *\n * The following elements are considered to be the footer element (highest\n * priority to lowest):\n *\n * # `#footer-places`\n * # `#f-list`\n * # The parent element of `#footer li`, which is either an `ol` or `ul`.\n *\n * @return {jQuery} The link element\n */\nfunction createFooterLink() {\n\tvar $link = $( '<li>' ).append(\n\t\t\t$( '<a>' )\n\t\t\t\t.attr( 'href', '#' )\n\t\t\t\t.text( mw.message( 'popups-settings-enable' ).text() )\n\t\t),\n\t\t$footer;\n\n\t// As yet, we don't know whether the link should be visible.\n\t$link.hide();\n\n\t// From https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js,\n\t// which was written by Yair rand <https://en.wikipedia.org/wiki/User:Yair_rand>.\n\t$footer = $( '#footer-places, #f-list' );\n\n\tif ( $footer.length === 0 ) {\n\t\t$footer = $( '#footer li' ).parent();\n\t}\n\n\t$footer.append( $link );\n\n\treturn $link;\n}\n\n/**\n * Creates an instance of the footer link change listener.\n *\n * The change listener covers the following behaviour:\n *\n * * The \"Enable previews\" link (the \"link\") is appended to the footer menu\n * (see `createFooterLink` above).\n * * When Page Previews are disabled, then the link is shown; otherwise, the\n * link is hidden.\n * * When the user clicks the link, then the `showSettings` bound action\n * creator is called.\n *\n * @param {Object} boundActions\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions ) {\n\tvar $footerLink;\n\n\treturn function ( prevState, state ) {\n\t\tif ( $footerLink === undefined ) {\n\t\t\t$footerLink = createFooterLink();\n\t\t\t$footerLink.click( function ( e ) {\n\t\t\t\te.preventDefault();\n\t\t\t\tboundActions.showSettings();\n\t\t\t} );\n\t\t}\n\n\t\tif ( state.settings.shouldShowFooterLink ) {\n\t\t\t$footerLink.show();\n\t\t} else {\n\t\t\t$footerLink.hide();\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/index.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nmodule.exports = {\n\tfooterLink: __webpack_require__( \"./src/changeListeners/footerLink.js\" ),\n\teventLogging: __webpack_require__( \"./src/changeListeners/eventLogging.js\" ),\n\tlinkTitle: __webpack_require__( \"./src/changeListeners/linkTitle.js\" ),\n\trender: __webpack_require__( \"./src/changeListeners/render.js\" ),\n\tsettings: __webpack_require__( \"./src/changeListeners/settings.js\" ),\n\tstatsv: __webpack_require__( \"./src/changeListeners/statsv.js\" ),\n\tsyncUserSettings: __webpack_require__( \"./src/changeListeners/syncUserSettings.js\" )\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/linkTitle.js\":\n/***/ (function(module, exports) {\n\nvar $ = jQuery;\n\n/**\n * Creates an instance of the link title change listener.\n *\n * While the user dwells on a link, then it becomes the active link. The\n * change listener will remove a link's `title` attribute while it's the\n * active link.\n *\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function () {\n\tvar title;\n\n\t/**\n\t * Destroys the title attribute of the element, storing its value in local\n\t * state so that it can be restored later (see `restoreTitleAttr`).\n\t *\n\t * @param {Element} el\n\t */\n\tfunction destroyTitleAttr( el ) {\n\t\tvar $el = $( el );\n\n\t\t// Has the user dwelled on a link? If we've already removed its title\n\t\t// attribute, then NOOP.\n\t\tif ( title ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttitle = $el.attr( 'title' );\n\n\t\t$el.attr( 'title', '' );\n\t}\n\n\t/**\n\t * Restores the title attribute of the element.\n\t *\n\t * @param {Element} el\n\t */\n\tfunction restoreTitleAttr( el ) {\n\t\t$( el ).attr( 'title', title );\n\n\t\ttitle = undefined;\n\t}\n\n\treturn function ( prevState, state ) {\n\t\tvar hasPrevActiveLink = prevState && prevState.preview.activeLink;\n\n\t\tif ( !state.preview.enabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( hasPrevActiveLink ) {\n\n\t\t\t// Has the user dwelled on a link immediately after abandoning another\n\t\t\t// (remembering that the ABANDON_END action is delayed by\n\t\t\t// ~10e2 ms).\n\t\t\tif ( prevState.preview.activeLink !== state.preview.activeLink ) {\n\t\t\t\trestoreTitleAttr( prevState.preview.activeLink );\n\t\t\t}\n\t\t}\n\n\t\tif ( state.preview.activeLink ) {\n\t\t\tdestroyTitleAttr( state.preview.activeLink );\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/render.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nvar renderer = __webpack_require__( \"./src/renderer.js\" );\n\n/**\n * Creates an instance of the render change listener.\n *\n * FIXME: Remove hard coupling with renderer, inject it as a parameter\n * * Wire it up in index.js\n * * Fix tests to remove require mocking\n *\n * @param {ext.popups.PreviewBehavior} previewBehavior\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( previewBehavior ) {\n\tvar preview;\n\n\treturn function ( prevState, state ) {\n\t\tif ( state.preview.shouldShow && !preview ) {\n\t\t\tpreview = renderer.render( state.preview.fetchResponse );\n\t\t\tpreview.show(\n\t\t\t\tstate.preview.activeEvent,\n\t\t\t\tpreviewBehavior,\n\t\t\t\tstate.preview.activeToken\n\t\t\t\t);\n\t\t} else if ( !state.preview.shouldShow && preview ) {\n\t\t\tpreview.hide();\n\t\t\tpreview = undefined;\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/settings.js\":\n/***/ (function(module, exports) {\n\n/**\n * Creates an instance of the settings change listener.\n *\n * @param {Object} boundActions\n * @param {Object} render function that renders a jQuery el with the settings\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, render ) {\n\tvar settings;\n\n\treturn function ( prevState, state ) {\n\t\tif ( !prevState ) {\n\t\t\t// Nothing to do on initialization\n\t\t\treturn;\n\t\t}\n\n\t\t// Update global modal visibility\n\t\tif (\n\t\t\tprevState.settings.shouldShow === false &&\n\t\t\tstate.settings.shouldShow === true\n\t\t) {\n\t\t\t// Lazily instantiate the settings UI\n\t\t\tif ( !settings ) {\n\t\t\t\tsettings = render( boundActions );\n\t\t\t\tsettings.appendTo( document.body );\n\t\t\t}\n\n\t\t\t// Update the UI settings with the current settings\n\t\t\tsettings.setEnabled( state.preview.enabled );\n\n\t\t\tsettings.show();\n\t\t} else if (\n\t\t\tprevState.settings.shouldShow === true &&\n\t\t\tstate.settings.shouldShow === false\n\t\t) {\n\t\t\tsettings.hide();\n\t\t}\n\n\t\t// Update help visibility\n\t\tif ( prevState.settings.showHelp !== state.settings.showHelp ) {\n\t\t\tsettings.toggleHelp( state.settings.showHelp );\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/statsv.js\":\n/***/ (function(module, exports) {\n\n/**\n * Creates an instance of the statsv change listener.\n *\n * The listener will log events to StatsD via the [the \"StatsD timers and\n * counters\" analytics event protocol][0].\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} boundActions\n * @param {ext.popups.EventTracker} track\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, track ) {\n\treturn function ( _, state ) {\n\t\tvar statsv = state.statsv;\n\n\t\tif ( statsv.action ) {\n\t\t\ttrack( statsv.action, statsv.data );\n\n\t\t\tboundActions.statsvLogged();\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/changeListeners/syncUserSettings.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module changeListeners/syncUserSettings\n */\n\n/**\n * Creates an instance of the user settings sync change listener.\n *\n * This change listener syncs certain parts of the state tree to user\n * settings when they change.\n *\n * Used for:\n *\n * * Enabled state: If the previews are enabled or disabled.\n * * Preview count: When the user dwells on a link for long enough that\n * a preview is shown, then their preview count will be incremented (see\n * `reducers/eventLogging.js`, and is persisted to local storage.\n *\n * @param {ext.popups.UserSettings} userSettings\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( userSettings ) {\n\n\treturn function ( prevState, state ) {\n\n\t\tsyncIfChanged(\n\t\t\tprevState, state, 'eventLogging', 'previewCount',\n\t\t\tuserSettings.setPreviewCount\n\t\t);\n\t\tsyncIfChanged(\n\t\t\tprevState, state, 'preview', 'enabled',\n\t\t\tuserSettings.setIsEnabled\n\t\t);\n\n\t};\n};\n\n/**\n * Given a state tree, reducer and property, safely return the value of the\n * property if the reducer and property exist\n * @param {Object} state tree\n * @param {String} reducer key to access on the state tree\n * @param {String} prop key to access on the reducer key of the state tree\n * @return {*}\n */\nfunction get( state, reducer, prop ) {\n\treturn state[ reducer ] && state[ reducer ][ prop ];\n}\n\n/**\n * Calls a sync function if the property prop on the property reducer on\n * the state trees has changed value.\n * @param {Object} prevState\n * @param {Object} state\n * @param {String} reducer key to access on the state tree\n * @param {String} prop key to access on the reducer key of the state tree\n * @param {Function} sync function to be called with the newest value if\n * changed\n */\nfunction syncIfChanged( prevState, state, reducer, prop, sync ) {\n\tvar current = get( state, reducer, prop );\n\tif ( prevState && ( get( prevState, reducer, prop ) !== current ) ) {\n\t\tsync( current );\n\t}\n}\n\n\n/***/ }),\n\n/***/ \"./src/constants.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module constants\n */\n\nmodule.exports = {\n\tTHUMBNAIL_SIZE: 300 * $.bracketedDevicePixelRatio(),\n\tEXTRACT_LENGTH: 525\n};\n\n\n/***/ }),\n\n/***/ \"./src/counts.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module counts\n */\n\n/**\n * Gets the count bucket for the number of edits a user has made.\n *\n * The buckets are defined as part of\n * [the Popups schema](https://meta.wikimedia.org/wiki/Schema:Popups).\n *\n * Extracted from `mw.popups.schemaPopups.getEditCountBucket`.\n *\n * @param {Number} count\n * @return {String}\n */\nexports.getEditCountBucket = function getEditCountBucket( count ) {\n\tvar bucket;\n\n\tif ( count === 0 ) {\n\t\tbucket = '0';\n\t} else if ( count >= 1 && count <= 4 ) {\n\t\tbucket = '1-4';\n\t} else if ( count >= 5 && count <= 99 ) {\n\t\tbucket = '5-99';\n\t} else if ( count >= 100 && count <= 999 ) {\n\t\tbucket = '100-999';\n\t} else if ( count >= 1000 ) {\n\t\tbucket = '1000+';\n\t}\n\n\treturn bucket + ' edits';\n};\n\n/**\n * Gets the count bucket for the number of previews a user has seen.\n *\n * If local storage isn't available - because the user has disabled it\n * or the browser doesn't support it - then then \"unknown\" is returned.\n *\n * The buckets are defined as part of\n * [the Popups schema](https://meta.wikimedia.org/wiki/Schema:Popups).\n *\n * Extracted from `mw.popups.getPreviewCountBucket`.\n *\n * @param {Number} count\n * @return {String}\n */\nexports.getPreviewCountBucket = function getPreviewCountBucket( count ) {\n\tvar bucket;\n\n\tif ( count === -1 ) {\n\t\treturn 'unknown';\n\t}\n\n\tif ( count === 0 ) {\n\t\tbucket = '0';\n\t} else if ( count >= 1 && count <= 4 ) {\n\t\tbucket = '1-4';\n\t} else if ( count >= 5 && count <= 20 ) {\n\t\tbucket = '5-20';\n\t} else if ( count >= 21 ) {\n\t\tbucket = '21+';\n\t}\n\n\treturn bucket + ' previews';\n};\n\n\n/***/ }),\n\n/***/ \"./src/formatter.js\":\n/***/ (function(module, exports) {\n\nvar $ = jQuery,\n\tmw = window.mediaWiki;\n\n/**\n * Improves the plain text extracts\n * @param {String} plainTextExtract\n * @param {String} title\n * @returns {Array}\n */\nfunction htmlize( plainTextExtract, title ) {\n\tvar extract = plainTextExtract;\n\tif ( plainTextExtract === undefined ) {\n\t\treturn [];\n\t}\n\n\textract = removeParentheticals( extract );\n\textract = removeEllipsis( extract );\n\n\t// After cleaning the extract it may have been blanked\n\tif ( extract.length === 0 ) {\n\t\treturn [];\n\t}\n\n\textract = makeTitleInExtractBold( extract, title );\n\treturn extract;\n}\n\n/**\n * Converts the extract into a list of elements, which correspond to fragments\n * of the extract. Fragments that match the title verbatim are wrapped in a\n * `<b>` element.\n *\n * Using the bolded elements of the extract of the page directly is covered by\n * [T141651](https://phabricator.wikimedia.org/T141651).\n *\n * Extracted from `mw.popups.renderer.article.getProcessedElements`.\n *\n * @param {String} extract\n * @param {String} title\n * @return {Array} A set of HTML Elements\n */\nfunction makeTitleInExtractBold( extract, title ) {\n\tvar regExp, escapedTitle,\n\t\telements = [],\n\t\tboldIdentifier = '<bi-' + Math.random() + '>',\n\t\tsnip = '<snip-' + Math.random() + '>';\n\n\ttitle = title.replace( /\\s+/g, ' ' ).trim(); // Remove extra white spaces\n\tescapedTitle = mw.RegExp.escape( title ); // Escape RegExp elements\n\tregExp = new RegExp( '(^|\\\\s)(' + escapedTitle + ')(|$)', 'i' );\n\n\t// Remove text in parentheses along with the parentheses\n\textract = extract.replace( /\\s+/, ' ' ); // Remove extra white spaces\n\n\t// Make title bold in the extract text\n\t// As the extract is html escaped there can be no such string in it\n\t// Also, the title is escaped of RegExp elements thus can't have \"*\"\n\textract = extract.replace( regExp, '$1' + snip + boldIdentifier + '$2' + snip + '$3' );\n\textract = extract.split( snip );\n\n\t$.each( extract, function ( index, part ) {\n\t\tif ( part.indexOf( boldIdentifier ) === 0 ) {\n\t\t\telements.push( $( '<b>' ).text( part.substring( boldIdentifier.length ) ) );\n\t\t} else {\n\t\t\telements.push( document.createTextNode( part ) );\n\t\t}\n\t} );\n\n\treturn elements;\n}\n\n/**\n * Removes the trailing ellipsis from the extract, if it's there.\n *\n * This function was extracted from\n * `mw.popups.renderer.article#removeEllipsis`.\n *\n * @param {String} extract\n * @return {String}\n */\nfunction removeEllipsis( extract ) {\n\treturn extract.replace( /\\.\\.\\.$/, '' );\n}\n\n/**\n * Removes parentheticals from the extract.\n *\n * If the parenthesis are unbalanced or out of order, then the extract is\n * returned without further processing.\n *\n * This function was extracted from\n * `mw.popups.renderer.article#removeParensFromText`.\n *\n * @param {String} extract\n * @return {String}\n */\nfunction removeParentheticals( extract ) {\n\tvar\n\t\tch,\n\t\tresult = '',\n\t\tlevel = 0,\n\t\ti = 0;\n\n\tfor ( i; i < extract.length; i++ ) {\n\t\tch = extract.charAt( i );\n\n\t\tif ( ch === ')' && level === 0 ) {\n\t\t\treturn extract;\n\t\t}\n\t\tif ( ch === '(' ) {\n\t\t\tlevel++;\n\t\t\tcontinue;\n\t\t} else if ( ch === ')' ) {\n\t\t\tlevel--;\n\t\t\tcontinue;\n\t\t}\n\t\tif ( level === 0 ) {\n\t\t\t// Remove leading spaces before brackets\n\t\t\tif ( ch === ' ' && extract.charAt( i + 1 ) === '(' ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tresult += ch;\n\t\t}\n\t}\n\n\treturn ( level === 0 ) ? result : extract;\n}\n\nmodule.exports = {\n\thtmlize: htmlize\n};\n\n\n/***/ }),\n\n/***/ \"./src/gateway/mediawiki.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module gateway/mediawiki\n */\n\n/**\n * @interface MediaWikiGateway\n * @extends Gateway\n *\n * @global\n */\n\n// Public and private cache lifetime (5 minutes)\n//\n// FIXME: Move this to src/constants.js.\nvar CACHE_LIFETIME = 300,\n\tcreateModel = __webpack_require__( \"./src/preview/model.js\" ).createModel,\n\tplainTextHTMLizer = __webpack_require__( \"./src/formatter.js\" ).htmlize,\n\t$ = jQuery;\n\n/**\n * Creates an instance of the MediaWiki API gateway.\n *\n * @param {mw.Api} api\n * @param {Object} config Configuration that affects the major behavior of the\n * gateway.\n * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of\n * the thumbnail.\n * @param {Number} config.EXTRACT_LENGTH The maximum length, in characters,\n * of the extract.\n * @returns {MediaWikiGateway}\n */\nmodule.exports = function createMediaWikiApiGateway( api, config ) {\n\n\t/**\n\t * Fetches page data from the API.\n\t *\n\t * @function\n\t * @name MediaWikiGateway#fetch\n\t * @param {String} title\n\t * @return {jQuery.Promise}\n\t */\n\tfunction fetch( title ) {\n\t\treturn api.get( {\n\t\t\taction: 'query',\n\t\t\tprop: 'info|extracts|pageimages|revisions|info',\n\t\t\tformatversion: 2,\n\t\t\tredirects: true,\n\t\t\texintro: true,\n\t\t\texchars: config.EXTRACT_LENGTH,\n\n\t\t\t// There is an added geometric limit on .mwe-popups-extract\n\t\t\t// so that text does not overflow from the card.\n\t\t\texplaintext: true,\n\n\t\t\tpiprop: 'thumbnail',\n\t\t\tpithumbsize: config.THUMBNAIL_SIZE,\n\t\t\tpilicense: 'any',\n\t\t\trvprop: 'timestamp',\n\t\t\tinprop: 'url',\n\t\t\ttitles: title,\n\t\t\tsmaxage: CACHE_LIFETIME,\n\t\t\tmaxage: CACHE_LIFETIME,\n\t\t\tuselang: 'content'\n\t\t}, {\n\t\t\theaders: {\n\t\t\t\t'X-Analytics': 'preview=1'\n\t\t\t}\n\t\t} );\n\t}\n\n\tfunction getPageSummary( title ) {\n\t\treturn fetch( title )\n\t\t\t.then( extractPageFromResponse )\n\t\t\t.then( htmlize )\n\t\t\t.then( convertPageToModel );\n\t}\n\n\treturn {\n\t\tfetch: fetch,\n\t\textractPageFromResponse: extractPageFromResponse,\n\t\tconvertPageToModel: convertPageToModel,\n\t\tgetPageSummary: getPageSummary,\n\t\thtmlize: htmlize\n\t};\n};\n\n/**\n * Extracts page data from the API response.\n *\n * @function\n * @name MediaWikiGateway#extractPageFromResponse\n * @param {Object} data The response\n * @throws {Error} If the response is empty or doesn't contain data about the\n * page\n * @returns {Object}\n */\nfunction extractPageFromResponse( data ) {\n\tif (\n\t\tdata.query &&\n\t\tdata.query.pages &&\n\t\tdata.query.pages.length\n\t) {\n\t\treturn data.query.pages[ 0 ];\n\t}\n\n\tthrow new Error( 'API response `query.pages` is empty.' );\n}\n\n/**\n * HTMLize plain text response\n *\n * @function\n * @name MediaWikiGateway#htmlize\n * @param {Object} data The response\n * @returns {Object}\n */\nfunction htmlize( data ) {\n\tvar result = $.extend( {}, data );\n\tresult.extract = plainTextHTMLizer( data.extract, data.title );\n\treturn result;\n}\n\n/**\n * Converts the API response to a preview model.\n *\n * @function\n * @name MediaWikiGateway#convertPageToModel\n * @param {Object} page\n * @returns {PreviewModel}\n */\nfunction convertPageToModel( page ) {\n\treturn createModel(\n\t\tpage.title,\n\t\tpage.canonicalurl,\n\t\tpage.pagelanguagehtmlcode,\n\t\tpage.pagelanguagedir,\n\t\tpage.extract,\n\t\tpage.thumbnail\n\t);\n}\n\n\n/***/ }),\n\n/***/ \"./src/gateway/rest.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module gateway/rest\n */\n\nvar RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/',\n\tRESTBASE_PROFILE = 'https://www.mediawiki.org/wiki/Specs/Summary/1.2.0',\n\tcreateModel = __webpack_require__( \"./src/preview/model.js\" ).createModel,\n\tplainTextHTMLizer = __webpack_require__( \"./src/formatter.js\" ).htmlize,\n\tmw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * @interface RESTBaseGateway\n * @extends Gateway\n *\n * @global\n */\n\n/**\n * Creates an instance of the RESTBase gateway.\n *\n * This gateway differs from the {@link MediaWikiGateway MediaWiki gateway} in\n * that it fetches page data from [the RESTBase page summary endpoint][0].\n *\n * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title\n *\n * @param {Function} ajax A function with the same signature as `jQuery.ajax`\n * @param {Object} config Configuration that affects the major behavior of the\n * gateway.\n * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of\n * the thumbnail.\n * @returns {RESTBaseGateway}\n */\nmodule.exports = function createRESTBaseGateway( ajax, config ) {\n\n\t/**\n\t * Fetches page data from [the RESTBase page summary endpoint][0].\n\t *\n\t * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title\n\t *\n\t * @function\n\t * @name MediaWikiGateway#fetch\n\t * @param {String} title\n\t * @return {jQuery.Promise}\n\t */\n\tfunction fetch( title ) {\n\t\treturn ajax( {\n\t\t\turl: RESTBASE_ENDPOINT + encodeURIComponent( title ),\n\t\t\theaders: {\n\t\t\t\tAccept: 'application/json; charset=utf-8; ' +\n\t\t\t\t\t'profile=\"' + RESTBASE_PROFILE + '\"'\n\t\t\t}\n\t\t} );\n\t}\n\n\tfunction getPageSummary( title ) {\n\t\tvar result = $.Deferred();\n\n\t\tfetch( title )\n\t\t\t.then(\n\t\t\t\tfunction ( page ) {\n\t\t\t\t\tresult.resolve(\n\t\t\t\t\t\tconvertPageToModel( page, config.THUMBNAIL_SIZE ) );\n\t\t\t\t},\n\t\t\t\tfunction ( jqXHR ) {\n\t\t\t\t\tif ( jqXHR.status === 404 ) {\n\t\t\t\t\t\tresult.resolve(\n\t\t\t\t\t\t\tconvertPageToModel( {\n\t\t\t\t\t\t\t\ttitle: title,\n\t\t\t\t\t\t\t\tlang: '',\n\t\t\t\t\t\t\t\tdir: '',\n\t\t\t\t\t\t\t\textract: ''\n\t\t\t\t\t\t\t}, 0 )\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult.reject();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\n\t\treturn result.promise();\n\t}\n\n\treturn {\n\t\tfetch: fetch,\n\t\tconvertPageToModel: convertPageToModel,\n\t\tgetPageSummary: getPageSummary\n\t};\n};\n\n/**\n * Resizes the thumbnail to the requested width, preserving its aspect ratio.\n *\n * The requested width is limited to that of the original image unless the image\n * is an SVG, which can be scaled infinitely.\n *\n * This function is only intended to mangle the pretty thumbnail URLs used on\n * Wikimedia Commons. Once [an official thumb API](https://phabricator.wikimedia.org/T66214)\n * is fully specified and implemented, this function can be made more general.\n *\n * @param {Object} thumbnail The thumbnail image\n * @param {Object} original The original image\n * @param {Number} thumbSize The requested size\n * @returns {Object}\n */\nfunction generateThumbnailData( thumbnail, original, thumbSize ) {\n\tvar parts = thumbnail.source.split( '/' ),\n\t\tlastPart = parts[ parts.length - 1 ],\n\t\tfilename,\n\t\twidth,\n\t\theight;\n\n\t// The last part, the thumbnail's full filename, is in the following form:\n\t// ${width}px-${filename}.${extension}. Splitting the thumbnail's filename\n\t// makes this function resilient to the thumbnail not having the same\n\t// extension as the original image, which is definitely the case for SVG's\n\t// where the thumbnail's extension is .svg.png.\n\tfilename = lastPart.substr( lastPart.indexOf( 'px-' ) + 3 );\n\n\t\t// Scale the thumbnail's largest dimension.\n\tif ( thumbnail.width > thumbnail.height ) {\n\t\twidth = thumbSize;\n\t\theight = Math.floor( ( thumbSize / thumbnail.width ) * thumbnail.height );\n\t} else {\n\t\twidth = Math.floor( ( thumbSize / thumbnail.height ) * thumbnail.width );\n\t\theight = thumbSize;\n\t}\n\n\t// If the image isn't an SVG, then it shouldn't be scaled past its original\n\t// dimensions.\n\tif ( width >= original.width && filename.indexOf( '.svg' ) === -1 ) {\n\t\treturn original;\n\t}\n\n\tparts[ parts.length - 1 ] = width + 'px-' + filename;\n\n\treturn {\n\t\tsource: parts.join( '/' ),\n\t\twidth: width,\n\t\theight: height\n\t};\n}\n\n/**\n * Converts the API response to a preview model.\n *\n * @function\n * @name RESTBaseGateway#convertPageToModel\n * @param {Object} page\n * @param {Number} thumbSize\n * @returns {PreviewModel}\n */\nfunction convertPageToModel( page, thumbSize ) {\n\treturn createModel(\n\t\tpage.title,\n\t\tnew mw.Title( page.title ).getUrl(),\n\t\tpage.lang,\n\t\tpage.dir,\n\t\tplainTextHTMLizer( page.extract, page.title ),\n\t\tpage.thumbnail ? generateThumbnailData( page.thumbnail, page.originalimage, thumbSize ) : undefined\n\t);\n}\n\n\n/***/ }),\n\n/***/ \"./src/index.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module popups\n */\n\nvar mw = mediaWiki,\n\t$ = jQuery,\n\tRedux = __webpack_require__( \"./node_modules/redux/dist/redux.min.js\" ),\n\tReduxThunk = __webpack_require__( \"./node_modules/redux-thunk/dist/redux-thunk.min.js\" ),\n\tconstants = __webpack_require__( \"./src/constants.js\" ),\n\n\tcreateRESTBaseGateway = __webpack_require__( \"./src/gateway/rest.js\" ),\n\tcreateMediaWikiApiGateway = __webpack_require__( \"./src/gateway/mediawiki.js\" ),\n\tcreateUserSettings = __webpack_require__( \"./src/userSettings.js\" ),\n\tcreatePreviewBehavior = __webpack_require__( \"./src/previewBehavior.js\" ),\n\tcreateSchema = __webpack_require__( \"./src/schema.js\" ),\n\tcreateSettingsDialogRenderer = __webpack_require__( \"./src/settingsDialog.js\" ),\n\tregisterChangeListener = __webpack_require__( \"./src/changeListener.js\" ),\n\tcreateIsEnabled = __webpack_require__( \"./src/isEnabled.js\" ),\n\ttitle = __webpack_require__( \"./src/title.js\" ),\n\trenderer = __webpack_require__( \"./src/renderer.js\" ),\n\tstatsvInstrumentation = __webpack_require__( \"./src/statsvInstrumentation.js\" ),\n\n\tchangeListeners = __webpack_require__( \"./src/changeListeners/index.js\" ),\n\tactions = __webpack_require__( \"./src/actions.js\" ),\n\treducers = __webpack_require__( \"./src/reducers/index.js\" ),\n\n\tBLACKLISTED_LINKS = [\n\t\t'.extiw',\n\t\t'.image',\n\t\t'.new',\n\t\t'.internal',\n\t\t'.external',\n\t\t'.oo-ui-buttonedElement-button',\n\t\t'.cancelLink a'\n\t];\n\n/**\n * @typedef {Function} ext.popups.EventTracker\n *\n * An analytics event tracker like `mw.track`.\n */\n\n/**\n * Creates a gateway with sensible values for the dependencies.\n *\n * @param {mw.Map} config\n * @return {ext.popups.Gateway}\n */\nfunction createGateway( config ) {\n\tif ( config.get( 'wgPopupsAPIUseRESTBase' ) ) {\n\t\treturn createRESTBaseGateway( $.ajax, constants );\n\t}\n\treturn createMediaWikiApiGateway( new mw.Api(), constants );\n}\n\n/**\n * Gets the appropriate analytics event tracker for logging metrics to StatsD\n * via the [the \"StatsD timers and counters\" analytics event protocol][0].\n *\n * If logging metrics to StatsD is enabled for the user, then the appriopriate\n * function is `mw.track`; otherwise it's `$.noop`.\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} user\n * @param {Object} config\n * @param {Object} experiments\n * @return {ext.popups.EventTracker}\n */\nfunction getStatsvTracker( user, config, experiments ) {\n\treturn statsvInstrumentation.isEnabled( user, config, experiments ) ? mw.track : $.noop;\n}\n\n/**\n * Subscribes the registered change listeners to the\n * [store](http://redux.js.org/docs/api/Store.html#store).\n *\n * @param {Redux.Store} store\n * @param {Object} actions\n * @param {ext.popups.UserSettings} userSettings\n * @param {Function} settingsDialog\n * @param {ext.popups.PreviewBehavior} previewBehavior\n * @param {ext.popups.EventTracker} statsvTracker\n */\nfunction registerChangeListeners( store, actions, userSettings, settingsDialog, previewBehavior, statsvTracker ) {\n\tregisterChangeListener( store, changeListeners.footerLink( actions ) );\n\tregisterChangeListener( store, changeListeners.linkTitle() );\n\tregisterChangeListener( store, changeListeners.render( previewBehavior ) );\n\tregisterChangeListener( store, changeListeners.statsv( actions, statsvTracker ) );\n\tregisterChangeListener( store, changeListeners.syncUserSettings( userSettings ) );\n\tregisterChangeListener( store, changeListeners.settings( actions, settingsDialog ) );\n}\n\n/*\n * Initialize the application by:\n * 1. Creating the state store\n * 2. Binding the actions to such store\n * 3. Trigger the boot action to bootstrap the system\n * 4. When the page content is ready:\n * - Process the eligible links for page previews\n * - Initialize the renderer\n * - Bind hover and click events to the eligible links to trigger actions\n */\nmw.requestIdleCallback( function () {\n\tvar compose = Redux.compose,\n\t\tstore,\n\t\tboundActions,\n\n\t\t// So-called \"services\".\n\t\tgenerateToken = mw.user.generateRandomSessionId,\n\t\tgateway = createGateway( mw.config ),\n\t\tuserSettings,\n\t\tsettingsDialog,\n\t\tstatsvTracker,\n\t\tisEnabled,\n\t\tschema,\n\t\tpreviewBehavior;\n\n\tuserSettings = createUserSettings( mw.storage );\n\tsettingsDialog = createSettingsDialogRenderer();\n\tstatsvTracker = getStatsvTracker( mw.user, mw.config, mw.experiments );\n\n\tisEnabled = createIsEnabled( mw.user, userSettings, mw.config, mw.experiments );\n\n\t// If debug mode is enabled, then enable Redux DevTools.\n\tif ( mw.config.get( 'debug' ) === true ) {\n\t\t// eslint-disable-next-line no-underscore-dangle\n\t\tcompose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\t}\n\n\tstore = Redux.createStore(\n\t\tRedux.combineReducers( reducers ),\n\t\tcompose( Redux.applyMiddleware(\n\t\t\tReduxThunk.default\n\t\t) )\n\t);\n\tboundActions = Redux.bindActionCreators( actions, store.dispatch );\n\n\tpreviewBehavior = createPreviewBehavior( mw.config, mw.user, boundActions );\n\n\tregisterChangeListeners(\n\t\tstore, boundActions, userSettings, settingsDialog,\n\t\tpreviewBehavior, statsvTracker\n\t);\n\n\t// Load EventLogging schema if possible...\n\tmw.loader.using( 'ext.eventLogging.Schema' ).done( function () {\n\t\tschema = createSchema( mw.config, window );\n\t\tregisterChangeListener( store, changeListeners.eventLogging( boundActions, schema, statsvTracker ) );\n\t} );\n\n\tboundActions.boot(\n\t\tisEnabled,\n\t\tmw.user,\n\t\tuserSettings,\n\t\tgenerateToken,\n\t\tmw.config\n\t);\n\n\tmw.hook( 'wikipage.content' ).add( function ( $container ) {\n\t\tvar invalidLinksSelector = BLACKLISTED_LINKS.join( ', ' ),\n\t\t\tvalidLinkSelector = 'a[href][title]:not(' + invalidLinksSelector + ')';\n\n\t\trenderer.init();\n\n\t\t$container\n\t\t\t.on( 'mouseover keyup', validLinkSelector, function ( event ) {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.linkDwell( mwTitle, this, event, gateway, generateToken );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.on( 'mouseout blur', validLinkSelector, function () {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.abandon( this );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.on( 'click', validLinkSelector, function () {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.linkClick( this );\n\t\t\t\t}\n\t\t\t} );\n\n\t} );\n} );\n\nwindow.Redux = Redux;\nwindow.ReduxThunk = ReduxThunk;\n\n\n/***/ }),\n\n/***/ \"./src/isEnabled.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module isEnabled\n */\n\n/**\n * Given the global state of the application, creates a function that gets\n * whether or not the user should have Page Previews enabled.\n *\n * Page Previews is disabled when the Navigation Popups gadget is enabled.\n *\n * If Page Previews is configured as a beta feature (see\n * `$wgPopupsBetaFeature`), the user must be logged in and have enabled the\n * beta feature in order to see previews. Logged out users won't be able\n * to see the feature.\n *\n * If Page Previews is configured as a user preference, then the user must\n * either be logged in and have enabled the preference or be logged out and have\n * not disabled previews via the settings modal. Logged out users who have not\n * disabled or enabled the previews via the settings modal are sampled at the\n * sampling rate defined by `wgPopupsAnonsEnabledSamplingRate`.\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {Object} userSettings An object returned by `userSettings.js`\n * @param {mw.Map} config\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n *\n * @return {Boolean}\n */\nmodule.exports = function ( user, userSettings, config, experiments ) {\n\tif ( config.get( 'wgPopupsConflictsWithNavPopupGadget' ) ) {\n\t\treturn false;\n\t}\n\n\tif ( !user.isAnon() ) {\n\t\treturn config.get( 'wgPopupsShouldSendModuleToUser' );\n\t}\n\n\tif ( config.get( 'wgPopupsBetaFeature' ) ) {\n\t\treturn false;\n\t}\n\n\tif ( !userSettings.hasIsEnabled() ) {\n\t\treturn isUserSampled( user, config, experiments );\n\t}\n\n\treturn userSettings.getIsEnabled();\n};\n\n/**\n * Is the user sampled based on a sampling rate?\n *\n * The sampling rate is taken from `wgPopupsAnonsEnabledSamplingRate` and\n * defaults to 0.9.\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {mw.Map} config\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n *\n * @return {Boolean}\n */\nfunction isUserSampled( user, config, experiments ) {\n\tvar samplingRate = config.get( 'wgPopupsAnonsEnabledSamplingRate', 0.9 ),\n\t\tbucket = experiments.getBucket( {\n\t\t\tname: 'ext.Popups.visibility',\n\t\t\tenabled: true,\n\t\t\tbuckets: {\n\t\t\t\tcontrol: 1 - samplingRate,\n\t\t\t\tA: samplingRate\n\t\t\t}\n\t\t}, user.sessionId() );\n\n\treturn bucket === 'A';\n}\n\n\n/***/ }),\n\n/***/ \"./src/preview/model.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module preview/model\n */\n\nvar TYPE_GENERIC = 'generic',\n\tTYPE_PAGE = 'page';\n\n/**\n * @constant {String}\n */\nexports.TYPE_GENERIC = TYPE_GENERIC;\n\n/**\n * @constant {String}\n */\nexports.TYPE_PAGE = TYPE_PAGE;\n\n/**\n * @typedef {Object} PreviewModel\n * @property {String} title\n * @property {String} url The canonical URL of the page being previewed\n * @property {String} languageCode\n * @property {String} languageDirection Either \"ltr\" or \"rtl\"\n * @property {?Array} extract `undefined` if the extract isn't\n * viable, e.g. if it's empty after having ellipsis and parentheticals\n * removed\n * @property {String} type Either \"EXTRACT\" or \"GENERIC\"\n * @property {?Object} thumbnail\n *\n * @global\n */\n\n/**\n * Creates a preview model.\n *\n * @param {String} title\n * @param {String} url The canonical URL of the page being previewed\n * @param {String} languageCode\n * @param {String} languageDirection Either \"ltr\" or \"rtl\"\n * @param {?Array} extract\n * @param {?Object} thumbnail\n * @return {PreviewModel}\n */\nexports.createModel = function createModel(\n\ttitle,\n\turl,\n\tlanguageCode,\n\tlanguageDirection,\n\textract,\n\tthumbnail\n) {\n\tvar processedExtract = processExtract( extract );\n\n\treturn {\n\t\ttitle: title,\n\t\turl: url,\n\t\tlanguageCode: languageCode,\n\t\tlanguageDirection: languageDirection,\n\t\textract: processedExtract,\n\t\ttype: processedExtract === undefined ? TYPE_GENERIC : TYPE_PAGE,\n\t\tthumbnail: thumbnail\n\t};\n};\n\n/**\n * Processes the extract returned by the TextExtracts MediaWiki API query\n * module.\n *\n * If the extract is `undefined`, `null`, or empty, then `undefined` is\n * returned.\n *\n * @param {?Array} extract\n * @return {?String}\n */\nfunction processExtract( extract ) {\n\tif ( extract === undefined || extract.length === 0 ) {\n\t\treturn undefined;\n\t}\n\treturn extract;\n}\n\n\n/***/ }),\n\n/***/ \"./src/previewBehavior.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module previewBehaviour\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * A collection of event handlers specific to how the user interacts with all\n * previews. The event handlers are are agnostic to how/when they are bound\n * //but not to what they are bound//, i.e. the showSettings event handler is\n * written to be bound to either an `<a>` or `<button>` element.\n *\n * @typedef {Object} ext.popups.PreviewBehavior\n * @property {String} settingsUrl\n * @property {Function} showSettings\n * @property {Function} previewDwell\n * @property {Function} previewAbandon\n * @property {Function} click handler for the entire preview\n */\n\n/**\n * Creates an instance of `ext.popups.PreviewBehavior`.\n *\n * If the user is logged out, then clicking the cog should show the settings\n * modal.\n *\n * If the user is logged in, then clicking the cog should send them to the\n * Special:Preferences page with the \"Beta features\" tab open if Page Previews\n * is enabled as a beta feature, or the \"Appearance\" tab otherwise.\n *\n * @param {mw.Map} config\n * @param {mw.User} user\n * @param {Object} actions The action creators bound to the Redux store\n * @return {ext.popups.PreviewBehavior}\n */\nmodule.exports = function ( config, user, actions ) {\n\tvar isBetaFeature = config.get( 'wgPopupsBetaFeature' ),\n\t\trawTitle,\n\t\tsettingsUrl,\n\t\tshowSettings = $.noop;\n\n\tif ( user.isAnon() ) {\n\t\tshowSettings = function ( event ) {\n\t\t\tevent.preventDefault();\n\n\t\t\tactions.showSettings();\n\t\t};\n\t} else {\n\t\trawTitle = 'Special:Preferences#mw-prefsection-';\n\t\trawTitle += isBetaFeature ? 'betafeatures' : 'rendering';\n\n\t\tsettingsUrl = mw.Title.newFromText( rawTitle )\n\t\t\t.getUrl();\n\t}\n\n\treturn {\n\t\tsettingsUrl: settingsUrl,\n\t\tshowSettings: showSettings,\n\t\tpreviewDwell: actions.previewDwell,\n\t\tpreviewAbandon: actions.abandon,\n\t\tpreviewShow: actions.previewShow,\n\t\tclick: actions.linkClick\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/eventLogging.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module reducers/eventLogging\n */\n\nvar actionTypes = __webpack_require__( \"./src/actionTypes.js\" ),\n\tnextState = __webpack_require__( \"./src/reducers/nextState.js\" ),\n\tcounts = __webpack_require__( \"./src/counts.js\" );\n\n/**\n * Initialize the data that's shared between all events.\n *\n * @param {Object} bootAction\n * @return {Object}\n */\nfunction getBaseData( bootAction ) {\n\tvar result = {\n\t\tpageTitleSource: bootAction.page.title,\n\t\tnamespaceIdSource: bootAction.page.namespaceID,\n\t\tpageIdSource: bootAction.page.id,\n\t\tisAnon: bootAction.user.isAnon,\n\t\tpopupEnabled: bootAction.isEnabled,\n\t\tpageToken: bootAction.pageToken,\n\t\tsessionToken: bootAction.sessionToken,\n\t\tpreviewCountBucket: counts.getPreviewCountBucket( bootAction.user.previewCount ),\n\t\thovercardsSuppressedByGadget: bootAction.isNavPopupsEnabled\n\t};\n\n\tif ( !bootAction.user.isAnon ) {\n\t\tresult.editCountBucket = counts.getEditCountBucket( bootAction.user.editCount );\n\t}\n\n\treturn result;\n}\n\n/**\n * Takes data specific to the action and adds the following properties:\n *\n * * `linkInteractionToken`;\n * * `pageTitleHover` and `namespaceIdHover`; and\n * * `previewType` and `perceivedWait`, if a preview has been shown.\n *\n * @param {Object} interaction\n * @param {Object} actionData Data specific to the action, e.g. see\n * {@link module:reducers/eventLogging~createClosingEvent `createClosingEvent`}\n * @return {Object}\n */\nfunction createEvent( interaction, actionData ) {\n\tactionData.linkInteractionToken = interaction.token;\n\tactionData.pageTitleHover = interaction.title;\n\tactionData.namespaceIdHover = interaction.namespaceID;\n\n\t// Has the preview been shown?\n\tif ( interaction.timeToPreviewShow !== undefined ) {\n\t\tactionData.previewType = interaction.previewType;\n\t\tactionData.perceivedWait = interaction.timeToPreviewShow;\n\t}\n\n\treturn actionData;\n}\n\n/**\n * Creates an event that, when mixed into the base data (see\n * {@link module:reducers/eventLogging~getBaseData `getBaseData`}), represents\n * the user abandoning a link or preview.\n *\n * Since the event should be logged when the user has either abandoned a link or\n * dwelled on a different link, we refer to these events as \"closing\" events as\n * the link interaction has finished and a new one will be created later.\n *\n * If the link interaction is finalized, then no closing event is created.\n *\n * @param {Object} interaction\n * @return {Object|undefined}\n */\nfunction createClosingEvent( interaction ) {\n\tvar actionData = {\n\t\ttotalInteractionTime: Math.round( interaction.finished - interaction.started )\n\t};\n\n\tif ( interaction.finalized ) {\n\t\treturn undefined;\n\t}\n\n\t// Has the preview been shown? If so, then, in the context of the\n\t// instrumentation, then the preview has been dismissed by the user\n\t// rather than the user has abandoned the link.\n\tactionData.action = interaction.timeToPreviewShow ? 'dismissed' : 'dwelledButAbandoned';\n\n\treturn createEvent( interaction, actionData );\n}\n\n/**\n * Reducer for actions that may result in an event being logged with [the\n * Popups schema][0] via EventLogging.\n *\n * The complexity of this reducer reflects the complexity of [the schema][0],\n * which is compounded by the introduction of two delays introduced by the\n * system to provide reasonable performance and a consistent UX.\n *\n * The reducer must:\n *\n * * Accumulate the state required to log events. This state is\n * referred to as \"the interaction state\" or \"the interaction\";\n * * Enforce the invariant that one event is logged per interaction;\n * * Defend against delayed actions being dispatched; and, as a direct\n * consequence\n * * Handle transitioning from one interaction to another at the same time.\n *\n * Furthermore, we distinguish between \"finalizing\" and \"closing\" the current\n * interaction state. Since only one event should be logged per link\n * interaction, we say that the interaction state is *finalized* when an event\n * has been logged and is *closed* when a new interaction should be created.\n * In practice, the interaction state is only finalized when the user clicks a\n * link or a preview.\n *\n * [0]: https://meta.wikimedia.org/wiki/Schema:Popups\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} The state resulting from reducing the action with the\n * current state\n */\nmodule.exports = function ( state, action ) {\n\tvar nextCount, newState,\n\t\tactionTypesWithTokens = [\n\t\t\tactionTypes.FETCH_COMPLETE,\n\t\t\tactionTypes.ABANDON_END,\n\t\t\tactionTypes.PREVIEW_SHOW\n\t\t];\n\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tpreviewCount: undefined,\n\t\t\tbaseData: {},\n\t\t\tinteraction: undefined,\n\t\t\tevent: undefined\n\t\t};\n\t}\n\n\t// Was the action delayed? Then it requires a token to be reduced. Enforce\n\t// this here to avoid repetion and reduce nesting below.\n\tif (\n\t\tactionTypesWithTokens.indexOf( action.type ) !== -1 &&\n\t\t( !state.interaction || action.token !== state.interaction.token )\n\t) {\n\t\treturn state;\n\t}\n\n\t// If there is no interaction ongoing, ignore all actions except for:\n\t// * Application initialization\n\t// * New link dwells (which start a new interaction)\n\t// * Clearing queued events\n\t//\n\t// For example, after ctrl+clicking a link or preview, any other actions\n\t// until the new interaction should be ignored.\n\tif (\n\t\t!state.interaction &&\n\t\taction.type !== actionTypes.BOOT &&\n\t\taction.type !== actionTypes.LINK_DWELL &&\n\t\taction.type !== actionTypes.EVENT_LOGGED\n\t) {\n\t\treturn state;\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tpreviewCount: action.user.previewCount,\n\t\t\t\tbaseData: getBaseData( action ),\n\t\t\t\tevent: {\n\t\t\t\t\taction: 'pageLoaded'\n\t\t\t\t}\n\t\t\t} );\n\n\t\tcase actionTypes.EVENT_LOGGED:\n\t\t\tnewState = nextState( state, {\n\t\t\t\tevent: undefined\n\t\t\t} );\n\n\t\t\t// If an event was logged with an interaction token, and it is still\n\t\t\t// the current interaction, finish the interaction since logging is\n\t\t\t// the exit point of the state machine and an interaction should never\n\t\t\t// be logged twice.\n\t\t\tif (\n\t\t\t\taction.event.linkInteractionToken &&\n\t\t\t\tstate.interaction &&\n\t\t\t\t( action.event.linkInteractionToken === state.interaction.token )\n\t\t\t) {\n\t\t\t\tnewState.interaction = undefined;\n\t\t\t}\n\n\t\t\treturn newState;\n\n\t\tcase actionTypes.FETCH_COMPLETE:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tpreviewType: action.result.type\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_SHOW:\n\t\t\tnextCount = state.previewCount + 1;\n\n\t\t\treturn nextState( state, {\n\t\t\t\tpreviewCount: nextCount,\n\t\t\t\tbaseData: nextState( state.baseData, {\n\t\t\t\t\tpreviewCountBucket: counts.getPreviewCountBucket( nextCount )\n\t\t\t\t} ),\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\ttimeToPreviewShow: Math.round( action.timestamp - state.interaction.started )\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_DWELL:\n\n\t\t\t// Not a new interaction?\n\t\t\tif ( state.interaction && action.el === state.interaction.link ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\t\tisUserDwelling: true\n\t\t\t\t\t} )\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn nextState( state, {\n\n\t\t\t\t// TODO: Extract this object into a module that can be shared between\n\t\t\t\t// this and the preview reducer.\n\t\t\t\tinteraction: {\n\t\t\t\t\tlink: action.el,\n\t\t\t\t\ttitle: action.title,\n\t\t\t\t\tnamespaceID: action.namespaceID,\n\t\t\t\t\ttoken: action.token,\n\t\t\t\t\tstarted: action.timestamp,\n\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t},\n\n\t\t\t\t// Was the user interacting with another link? If so, then log the\n\t\t\t\t// abandoned event.\n\t\t\t\tevent: state.interaction ? createClosingEvent( state.interaction ) : undefined\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_CLICK:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tfinalized: true\n\t\t\t\t} ),\n\t\t\t\tevent: createEvent( state.interaction, {\n\t\t\t\t\taction: 'opened',\n\t\t\t\t\ttotalInteractionTime: Math.round( action.timestamp - state.interaction.started )\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tfinished: action.timestamp,\n\n\t\t\t\t\tisUserDwelling: false\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_END:\n\t\t\tif ( !state.interaction.isUserDwelling ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tinteraction: undefined,\n\t\t\t\t\tevent: createClosingEvent( state.interaction )\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn state;\n\n\t\tcase actionTypes.SETTINGS_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\tevent: createEvent( state.interaction, {\n\t\t\t\t\taction: 'tapped settings cog'\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/index.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nmodule.exports = {\n\teventLogging: __webpack_require__( \"./src/reducers/eventLogging.js\" ),\n\tpreview: __webpack_require__( \"./src/reducers/preview.js\" ),\n\tsettings: __webpack_require__( \"./src/reducers/settings.js\" ),\n\tstatsv: __webpack_require__( \"./src/reducers/statsv.js\" )\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/nextState.js\":\n/***/ (function(module, exports) {\n\n/**\n * Creates the next state tree from the current state tree and some updates.\n *\n * N.B. OO.copy doesn't copy Element instances, whereas $.extend does.\n * However, OO.copy does copy properties whose values are undefined or null,\n * whereas $.extend doesn't. Since the state tree contains an Element instance\n * - the preview.activeLink property - and we want to copy undefined/null into\n * the state we need to manually iterate over updates and check with\n * hasOwnProperty to copy over to the new state.\n *\n * In [change listeners](/doc/change_listeners.md), for example, we talk about\n * the previous state and the current state (the `prevState` and `state`\n * parameters, respectively). Since\n * [reducers](http://redux.js.org/docs/basics/Reducers.html) take the current\n * state and an action and make updates, \"next state\" seems appropriate.\n *\n * @param {Object} state\n * @param {Object} updates\n * @return {Object}\n */\nmodule.exports = function ( state, updates ) {\n\tvar result = {},\n\t\tkey;\n\n\tfor ( key in state ) {\n\t\tif ( state.hasOwnProperty( key ) && !updates.hasOwnProperty( key ) ) {\n\t\t\tresult[ key ] = state[ key ];\n\t\t}\n\t}\n\n\tfor ( key in updates ) {\n\t\tif ( updates.hasOwnProperty( key ) ) {\n\t\t\tresult[ key ] = updates[ key ];\n\t\t}\n\t}\n\n\treturn result;\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/preview.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nvar actionTypes = __webpack_require__( \"./src/actionTypes.js\" ),\n\tnextState = __webpack_require__( \"./src/reducers/nextState.js\" );\n\n/**\n * Reducer for actions that modify the state of the preview model\n *\n * @param {Object} state before action\n * @param {Object} action Redux action that modified state.\n * Must have `type` property.\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tenabled: undefined,\n\t\t\tactiveLink: undefined,\n\t\t\tactiveEvent: undefined,\n\t\t\tactiveToken: '',\n\t\t\tshouldShow: false,\n\t\t\tisUserDwelling: false\n\t\t};\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tenabled: action.isEnabled\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_CHANGE:\n\t\t\treturn nextState( state, {\n\t\t\t\tenabled: action.enabled\n\t\t\t} );\n\t\tcase actionTypes.LINK_DWELL:\n\t\t\t// New interaction\n\t\t\tif ( action.el !== state.activeLink ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tactiveLink: action.el,\n\t\t\t\t\tactiveEvent: action.event,\n\t\t\t\t\tactiveToken: action.token,\n\n\t\t\t\t\t// When the user dwells on a link with their keyboard, a preview is\n\t\t\t\t\t// renderered, and then dwells on another link, the ABANDON_END\n\t\t\t\t\t// action will be ignored.\n\t\t\t\t\t//\n\t\t\t\t\t// Ensure that all the preview is hidden.\n\t\t\t\t\tshouldShow: false,\n\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\t// Dwelling back into the same link\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} );\n\t\t\t}\n\n\t\tcase actionTypes.ABANDON_END:\n\t\t\tif ( action.token === state.activeToken && !state.isUserDwelling ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tactiveLink: undefined,\n\t\t\t\t\tactiveToken: undefined,\n\t\t\t\t\tactiveEvent: undefined,\n\t\t\t\t\tfetchResponse: undefined,\n\t\t\t\t\tshouldShow: false\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn state;\n\n\t\tcase actionTypes.PREVIEW_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tisUserDwelling: true\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tisUserDwelling: false\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tfetchResponse: undefined\n\t\t\t} );\n\t\tcase actionTypes.FETCH_COMPLETE:\n\t\t\tif ( action.token === state.activeToken ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tfetchResponse: action.result,\n\t\t\t\t\tshouldShow: state.isUserDwelling\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t/* falls through */\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/settings.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nvar actionTypes = __webpack_require__( \"./src/actionTypes.js\" ),\n\tnextState = __webpack_require__( \"./src/reducers/nextState.js\" );\n\n/**\n * Reducer for actions that modify the state of the settings\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tshouldShow: false,\n\t\t\tshowHelp: false,\n\t\t\tshouldShowFooterLink: false\n\t\t};\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.SETTINGS_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShow: true,\n\t\t\t\tshowHelp: false\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_HIDE:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShow: false,\n\t\t\t\tshowHelp: false\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_CHANGE:\n\t\t\treturn action.wasEnabled === action.enabled ?\n\t\t\t\t// If the setting is the same, just hide the dialogs\n\t\t\t\tnextState( state, {\n\t\t\t\t\tshouldShow: false\n\t\t\t\t} ) :\n\t\t\t\t// If the settings have changed...\n\t\t\t\tnextState( state, {\n\t\t\t\t\t// If we enabled, we just hide directly, no help\n\t\t\t\t\t// If we disabled, keep it showing and let the ui show the help.\n\t\t\t\t\tshouldShow: !action.enabled,\n\t\t\t\t\tshowHelp: !action.enabled,\n\n\t\t\t\t\t// Since the footer link is only ever shown to anonymous users (see\n\t\t\t\t\t// the BOOT case below), state.userIsAnon is always truthy here.\n\t\t\t\t\tshouldShowFooterLink: !action.enabled\n\t\t\t\t} );\n\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShowFooterLink: action.user.isAnon && !action.isEnabled\n\t\t\t} );\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n/***/ }),\n\n/***/ \"./src/reducers/statsv.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\nvar actionTypes = __webpack_require__( \"./src/actionTypes.js\" ),\n\tnextState = __webpack_require__( \"./src/reducers/nextState.js\" );\n\n/**\n * Reducer for actions that may result in an event being logged via statsv.\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tstate = state || {};\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.FETCH_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tfetchStartedAt: action.timestamp\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_END:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'timing.PagePreviewsApiResponse',\n\t\t\t\tdata: action.timestamp - state.fetchStartedAt\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_FAILED:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'counter.PagePreviewsApiFailure',\n\t\t\t\tdata: 1\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tlinkDwellStartedAt: action.timestamp\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'timing.PagePreviewsPreviewShow',\n\t\t\t\tdata: action.timestamp - state.linkDwellStartedAt\n\t\t\t} );\n\n\t\tcase actionTypes.STATSV_LOGGED:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: null,\n\t\t\t\tdata: null\n\t\t\t} );\n\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n/***/ }),\n\n/***/ \"./src/renderer.js\":\n/***/ (function(module, exports, __webpack_require__) {\n\n/**\n * @module renderer\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery,\n\twait = __webpack_require__( \"./src/wait.js\" ),\n\tSIZES = {\n\t\tportraitImage: {\n\t\t\th: 250, // Exact height\n\t\t\tw: 203 // Max width\n\t\t},\n\t\tlandscapeImage: {\n\t\t\th: 200, // Max height\n\t\t\tw: 300 // Exact Width\n\t\t},\n\t\tlandscapePopupWidth: 450,\n\t\tportraitPopupWidth: 300,\n\t\tpokeySize: 8 // Height of the pokey.\n\t},\n\t$window = $( window );\n\n/**\n * Extracted from `mw.popups.createSVGMasks`.\n * @private\n * @param {Object} container DOM object to which pokey masks are appended\n */\nfunction createPokeyMasks( container ) {\n\t$( '<div>' )\n\t\t.attr( 'id', 'mwe-popups-svg' )\n\t\t.html(\n\t\t\t'<svg width=\"0\" height=\"0\">' +\n\t\t\t\t'<defs>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-mask\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 10 8, 18 0, 26 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-mask-flip\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 274 8, 282 0, 290 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-landscape-mask\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 174 8, 182 0, 190 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-landscape-mask-flip\">' +\n\t\t\t\t\t\t'<polygon points=\"0 0, 1000 0, 1000 242, 190 242, 182 250, 174 242, 0 242\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t'</defs>' +\n\t\t\t'</svg>'\n\t\t)\n\t\t.appendTo( container );\n}\n\n/**\n * Initializes the renderer.\n */\nfunction init() {\n\tcreatePokeyMasks( document.body );\n}\n\n/**\n * The model of how a view is rendered, which is constructed from a response\n * from the gateway.\n *\n * TODO: Rename `isTall` to `isPortrait`.\n *\n * @typedef {Object} ext.popups.Preview\n * @property {jQuery} el\n * @property {Boolean} hasThumbnail\n * @property {Object} thumbnail\n * @property {Boolean} isTall Sugar around\n * `preview.hasThumbnail && thumbnail.isTall`\n */\n\n/**\n * Renders a preview given data from the {@link gateway ext.popups.Gateway}.\n * The preview is rendered and added to the DOM but will remain hidden until\n * the `show` method is called.\n *\n * Previews are rendered at:\n *\n * # The position of the mouse when the user dwells on the link with their\n * mouse.\n * # The centermost point of the link when the user dwells on the link with\n * their keboard or other assistive device.\n *\n * Since the content of the preview doesn't change but its position might, we\n * distinguish between \"rendering\" - generating HTML from a MediaWiki API\n * response - and \"showing/hiding\" - positioning the layout and changing its\n * orientation, if necessary.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction render( model ) {\n\tvar preview = model.extract === undefined ? createEmptyPreview( model ) : createPreview( model );\n\n\treturn {\n\n\t\t/**\n\t\t * Shows the preview given an event representing the user's interaction\n\t\t * with the active link, e.g. an instance of\n\t\t * [MouseEvent](https://developer.mozilla.org/en/docs/Web/API/MouseEvent).\n\t\t *\n\t\t * See `show` for more detail.\n\t\t *\n\t\t * @param {Event} event\n\t\t * @param {Object} boundActions The\n\t\t * [bound action creators](http://redux.js.org/docs/api/bindActionCreators.html)\n\t\t * that were (likely) created in [boot.js](./boot.js).\n\t\t * @param {String} token The unique token representing the link interaction\n\t\t * that resulted in showing the preview\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tshow: function ( event, boundActions, token ) {\n\t\t\treturn show(\n\t\t\t\tpreview, event, $( event.target ), boundActions, token,\n\t\t\t\tdocument.body\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Hides the preview.\n\t\t *\n\t\t * See `hide` for more detail.\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\thide: function () {\n\t\t\treturn hide( preview );\n\t\t}\n\t};\n}\n\n/**\n * Creates an instance of the DTO backing a preview.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction createPreview( model ) {\n\tvar templateData,\n\t\tthumbnail = createThumbnail( model.thumbnail ),\n\t\thasThumbnail = thumbnail !== null,\n\t\textract = model.extract,\n\t\t$el;\n\n\ttemplateData = $.extend( {}, model, {\n\t\thasThumbnail: hasThumbnail\n\t} );\n\n\t$el = mw.template.get( 'ext.popups', 'preview.mustache' )\n\t\t.render( templateData );\n\n\tif ( hasThumbnail ) {\n\t\t$el.find( '.mwe-popups-discreet' ).append( thumbnail.el );\n\t}\n\tif ( extract ) {\n\t\t$el.find( '.mwe-popups-extract' ).append( extract );\n\t}\n\n\treturn {\n\t\tel: $el,\n\t\thasThumbnail: hasThumbnail,\n\t\tthumbnail: thumbnail,\n\t\tisTall: hasThumbnail && thumbnail.isTall\n\t};\n}\n\n/**\n * Creates an instance of the DTO backing a preview. In this case the DTO\n * represents a generic preview, which covers the following scenarios:\n *\n * * The page doesn't exist, i.e. the user hovered over a redlink or a\n * redirect to a page that doesn't exist.\n * * The page doesn't have a viable extract.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction createEmptyPreview( model ) {\n\tvar templateData,\n\t\t$el;\n\n\ttemplateData = $.extend( {}, model, {\n\t\textractMsg: mw.msg( 'popups-preview-no-preview' ),\n\t\treadMsg: mw.msg( 'popups-preview-footer-read' )\n\t} );\n\n\t$el = mw.template.get( 'ext.popups', 'preview-empty.mustache' )\n\t\t.render( templateData );\n\n\treturn {\n\t\tel: $el,\n\t\thasThumbnail: false,\n\t\tisTall: false\n\t};\n}\n\n/**\n * Shows the preview.\n *\n * Extracted from `mw.popups.render.openPopup`.\n *\n * TODO: From the perspective of the client, there's no need to distinguish\n * between renderering and showing a preview. Merge #render and Preview#show.\n *\n * @param {ext.popups.Preview} preview\n * @param {Event} event\n * @param {jQuery} $link event target\n * @param {ext.popups.PreviewBehavior} behavior\n * @param {String} token\n * @param {Object} container DOM object to which pokey masks are appended\n * @return {jQuery.Promise} A promise that resolves when the promise has faded\n * in\n */\nfunction show( preview, event, $link, behavior, token, container ) {\n\tvar layout = createLayout(\n\t\tpreview.isTall,\n\t\t{\n\t\t\tpageX: event.pageX,\n\t\t\tpageY: event.pageY,\n\t\t\tclientY: event.clientY\n\t\t},\n\t\t{\n\t\t\tclientRects: $link.get( 0 ).getClientRects(),\n\t\t\toffset: $link.offset(),\n\t\t\twidth: $link.width(),\n\t\t\theight: $link.height()\n\t\t},\n\t\t{\n\t\t\tscrollTop: $window.scrollTop(),\n\t\t\twidth: $window.width(),\n\t\t\theight: $window.height()\n\t\t},\n\t\tSIZES.pokeySize\n\t);\n\n\tpreview.el.appendTo( container );\n\n\tlayoutPreview(\n\t\tpreview, layout, getClasses( preview, layout ),\n\t\tSIZES.landscapeImage.h, SIZES.pokeySize\n\t);\n\n\tpreview.el.show();\n\n\treturn wait( 200 )\n\t\t.then( function () {\n\t\t\tbindBehavior( preview, behavior );\n\t\t} )\n\t\t.then( function () {\n\t\t\tbehavior.previewShow( token );\n\t\t} );\n}\n\n/**\n * Binds the behavior to the interactive elements of the preview.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewBehavior} behavior\n */\nfunction bindBehavior( preview, behavior ) {\n\tpreview.el.hover( behavior.previewDwell, behavior.previewAbandon );\n\n\tpreview.el.click( behavior.click );\n\n\tpreview.el.find( '.mwe-popups-settings-icon' )\n\t\t.attr( 'href', behavior.settingsUrl )\n\t\t.click( function ( event ) {\n\t\t\tevent.stopPropagation();\n\n\t\t\tbehavior.showSettings( event );\n\t\t} );\n}\n\n/**\n * Extracted from `mw.popups.render.closePopup`.\n *\n * @param {ext.popups.Preview} preview\n * @return {jQuery.Promise} A promise that resolves when the preview has faded\n * out\n */\nfunction hide( preview ) {\n\tvar fadeInClass,\n\t\tfadeOutClass;\n\n\t// FIXME: This method clearly needs access to the layout of the preview.\n\tfadeInClass = ( preview.el.hasClass( 'mwe-popups-fade-in-up' ) ) ?\n\t\t'mwe-popups-fade-in-up' :\n\t\t'mwe-popups-fade-in-down';\n\n\tfadeOutClass = ( fadeInClass === 'mwe-popups-fade-in-up' ) ?\n\t\t'mwe-popups-fade-out-down' :\n\t\t'mwe-popups-fade-out-up';\n\n\tpreview.el\n\t\t.removeClass( fadeInClass )\n\t\t.addClass( fadeOutClass );\n\n\treturn wait( 150 ).then( function () {\n\t\tpreview.el.remove();\n\t} );\n}\n\n/**\n * @typedef {Object} ext.popups.Thumbnail\n * @property {Element} el\n * @property {Boolean} isTall Whether or not the thumbnail is portrait\n */\n\n/**\n * Creates a thumbnail from the representation of a thumbnail returned by the\n * PageImages MediaWiki API query module.\n *\n * If there's no thumbnail, the thumbnail is too small, or the thumbnail's URL\n * contains characters that could be used to perform an\n * [XSS attack via CSS](https://www.owasp.org/index.php/Testing_for_CSS_Injection_(OTG-CLIENT-005)),\n * then `null` is returned.\n *\n * Extracted from `mw.popups.renderer.article.createThumbnail`.\n *\n * @param {Object} rawThumbnail\n * @return {ext.popups.Thumbnail|null}\n */\nfunction createThumbnail( rawThumbnail ) {\n\tvar tall, thumbWidth, thumbHeight,\n\t\tx, y, width, height, clipPath,\n\t\tdevicePixelRatio = $.bracketedDevicePixelRatio();\n\n\tif ( !rawThumbnail ) {\n\t\treturn null;\n\t}\n\n\ttall = rawThumbnail.width < rawThumbnail.height;\n\tthumbWidth = rawThumbnail.width / devicePixelRatio;\n\tthumbHeight = rawThumbnail.height / devicePixelRatio;\n\n\tif (\n\t\t// Image too small for landscape display\n\t\t( !tall && thumbWidth < SIZES.landscapeImage.w ) ||\n\t\t// Image too small for portrait display\n\t\t( tall && thumbHeight < SIZES.portraitImage.h ) ||\n\t\t// These characters in URL that could inject CSS and thus JS\n\t\t(\n\t\t\trawThumbnail.source.indexOf( '\\\\' ) > -1 ||\n\t\t\trawThumbnail.source.indexOf( '\\'' ) > -1 ||\n\t\t\trawThumbnail.source.indexOf( '\\\"' ) > -1\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\tif ( tall ) {\n\t\tx = ( thumbWidth > SIZES.portraitImage.w ) ?\n\t\t\t( ( thumbWidth - SIZES.portraitImage.w ) / -2 ) :\n\t\t\t( SIZES.portraitImage.w - thumbWidth );\n\t\ty = ( thumbHeight > SIZES.portraitImage.h ) ?\n\t\t\t( ( thumbHeight - SIZES.portraitImage.h ) / -2 ) : 0;\n\t\twidth = SIZES.portraitImage.w;\n\t\theight = SIZES.portraitImage.h;\n\t} else {\n\t\tx = 0;\n\t\ty = ( thumbHeight > SIZES.landscapeImage.h ) ?\n\t\t\t( ( thumbHeight - SIZES.landscapeImage.h ) / -2 ) : 0;\n\t\twidth = SIZES.landscapeImage.w + 3;\n\t\theight = ( thumbHeight > SIZES.landscapeImage.h ) ?\n\t\t\tSIZES.landscapeImage.h : thumbHeight;\n\t\tclipPath = 'mwe-popups-mask';\n\t}\n\n\treturn {\n\t\tel: createThumbnailElement(\n\t\t\ttall ? 'mwe-popups-is-tall' : 'mwe-popups-is-not-tall',\n\t\t\trawThumbnail.source,\n\t\t\tx,\n\t\t\ty,\n\t\t\tthumbWidth,\n\t\t\tthumbHeight,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tclipPath\n\t\t),\n\t\tisTall: tall,\n\t\twidth: thumbWidth,\n\t\theight: thumbHeight\n\t};\n}\n\n/**\n * Creates the SVG image element that represents the thumbnail.\n *\n * This function is distinct from `createThumbnail` as it abstracts away some\n * browser issues that are uncovered when manipulating elements across\n * namespaces.\n *\n * @param {String} className\n * @param {String} url\n * @param {Number} x\n * @param {Number} y\n * @param {Number} thumbnailWidth\n * @param {Number} thumbnailHeight\n * @param {Number} width\n * @param {Number} height\n * @param {String} clipPath\n * @return {jQuery}\n */\nfunction createThumbnailElement( className, url, x, y, thumbnailWidth, thumbnailHeight, width, height, clipPath ) {\n\tvar $thumbnailSVGImage, $thumbnail,\n\t\tnsSvg = 'http://www.w3.org/2000/svg',\n\t\tnsXlink = 'http://www.w3.org/1999/xlink';\n\n\t$thumbnailSVGImage = $( document.createElementNS( nsSvg, 'image' ) );\n\t$thumbnailSVGImage[ 0 ].setAttributeNS( nsXlink, 'href', url );\n\t$thumbnailSVGImage\n\t\t.addClass( className )\n\t\t.attr( {\n\t\t\tx: x,\n\t\t\ty: y,\n\t\t\twidth: thumbnailWidth,\n\t\t\theight: thumbnailHeight,\n\t\t\t'clip-path': 'url(#' + clipPath + ')'\n\t\t} );\n\n\t$thumbnail = $( document.createElementNS( nsSvg, 'svg' ) )\n\t\t.attr( {\n\t\t\txmlns: nsSvg,\n\t\t\twidth: width,\n\t\t\theight: height\n\t\t} )\n\t\t.append( $thumbnailSVGImage );\n\n\treturn $thumbnail;\n}\n\n/**\n * Represents the layout of a preview, which consists of a position (`offset`)\n * and whether or not the preview should be flipped horizontally or\n * vertically (`flippedX` and `flippedY` respectively).\n *\n * @typedef {Object} ext.popups.PreviewLayout\n * @property {Object} offset\n * @property {number} offset.top\n * @property {number} offset.left\n * @property {Boolean} flippedX\n * @property {Boolean} flippedY\n */\n\n/**\n * @param {isPreviewTall} isPreviewTall\n * @param {Object} eventData Data related to the event that triggered showing a popup\n * @param {number} eventData.pageX\n * @param {number} eventData.pageY\n * @param {number} eventData.clientY\n * @param {Object} linkData Data related to the link thats used for showing a popup\n * @param {ClientRectList} linkData.clientRects list of rectangles defined by four edges\n * @param {Object} linkData.offset\n * @param {number} linkData.width\n * @param {number} linkData.height\n * @param {Object} windowData Data related to the window\n * @param {number} windowData.scrollTop\n * @param {number} windowData.width\n * @param {number} windowData.height\n * @param {number} pokeySize Space reserved for the pokey\n * @return {ext.popups.PreviewLayout}\n */\nfunction createLayout( isPreviewTall, eventData, linkData, windowData, pokeySize ) {\n\tvar flippedX = false,\n\t\tflippedY = false,\n\t\toffsetTop = ( eventData.pageY ) ? // If it was a mouse event\n\t\t\t// Position according to mouse\n\t\t\t// Since client rectangles are relative to the viewport,\n\t\t\t// take scroll position into account.\n\t\t\tgetClosestYPosition(\n\t\t\t\teventData.pageY - windowData.scrollTop,\n\t\t\t\tlinkData.clientRects,\n\t\t\t\tfalse\n\t\t\t) + windowData.scrollTop + pokeySize :\n\t\t\t// Position according to link position or size\n\t\t\tlinkData.offset.top + linkData.height + pokeySize,\n\t\tclientTop = ( eventData.clientY ) ?\n\t\t\teventData.clientY :\n\t\t\toffsetTop,\n\t\toffsetLeft = ( eventData.pageX ) ?\n\t\t\teventData.pageX :\n\t\t\tlinkData.offset.left;\n\n\t// X Flip\n\tif ( offsetLeft > ( windowData.width / 2 ) ) {\n\t\toffsetLeft += ( !eventData.pageX ) ? linkData.width : 0;\n\t\toffsetLeft -= !isPreviewTall ?\n\t\t\tSIZES.portraitPopupWidth :\n\t\t\tSIZES.landscapePopupWidth;\n\t\tflippedX = true;\n\t}\n\n\tif ( eventData.pageX ) {\n\t\toffsetLeft += ( flippedX ) ? 20 : -20;\n\t}\n\n\t// Y Flip\n\tif ( clientTop > ( windowData.height / 2 ) ) {\n\t\tflippedY = true;\n\n\t\t// Mirror the positioning of the preview when there's no \"Y flip\": rest\n\t\t// the pokey on the edge of the link's bounding rectangle. In this case\n\t\t// the edge is the top-most.\n\t\toffsetTop = linkData.offset.top;\n\n\t\t// Change the Y position to the top of the link\n\t\tif ( eventData.pageY ) {\n\t\t\t// Since client rectangles are relative to the viewport,\n\t\t\t// take scroll position into account.\n\t\t\toffsetTop = getClosestYPosition(\n\t\t\t\teventData.pageY - windowData.scrollTop,\n\t\t\t\tlinkData.clientRects,\n\t\t\t\ttrue\n\t\t\t) + windowData.scrollTop;\n\t\t}\n\n\t\toffsetTop -= pokeySize;\n\t}\n\n\treturn {\n\t\toffset: {\n\t\t\ttop: offsetTop,\n\t\t\tleft: offsetLeft\n\t\t},\n\t\tflippedX: flippedX,\n\t\tflippedY: flippedY\n\t};\n}\n\n/**\n * Generates a list of declarative CSS classes that represent the layout of\n * the preview.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewLayout} layout\n * @return {String[]}\n */\nfunction getClasses( preview, layout ) {\n\tvar classes = [];\n\n\tif ( layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-fade-in-down' );\n\t} else {\n\t\tclasses.push( 'mwe-popups-fade-in-up' );\n\t}\n\n\tif ( layout.flippedY && layout.flippedX ) {\n\t\tclasses.push( 'flipped_x_y' );\n\t}\n\n\tif ( layout.flippedY && !layout.flippedX ) {\n\t\tclasses.push( 'flipped_y' );\n\t}\n\n\tif ( layout.flippedX && !layout.flippedY ) {\n\t\tclasses.push( 'flipped_x' );\n\t}\n\n\tif ( ( !preview.hasThumbnail || preview.isTall ) && !layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-no-image-tri' );\n\t}\n\n\tif ( ( preview.hasThumbnail && !preview.isTall ) && !layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-image-tri' );\n\t}\n\n\tif ( preview.isTall ) {\n\t\tclasses.push( 'mwe-popups-is-tall' );\n\t} else {\n\t\tclasses.push( 'mwe-popups-is-not-tall' );\n\t}\n\n\treturn classes;\n}\n\n/**\n * Lays out the preview given the layout.\n *\n * If the preview should be oriented differently, then the pokey is updated,\n * e.g. if the preview should be flipped vertically, then the pokey is\n * removed.\n *\n * If the thumbnail is landscape and isn't the full height of the thumbnail\n * container, then pull the extract up to keep whitespace consistent across\n * previews.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewLayout} layout\n * @param {string[]} classes class names used for layout out the preview\n * @param {number} predefinedLandscapeImageHeight landscape image height\n * @param {number} pokeySize\n */\nfunction layoutPreview( preview, layout, classes, predefinedLandscapeImageHeight, pokeySize ) {\n\tvar popup = preview.el,\n\t\tisTall = preview.isTall,\n\t\thasThumbnail = preview.hasThumbnail,\n\t\tthumbnail = preview.thumbnail,\n\t\tflippedY = layout.flippedY,\n\t\tflippedX = layout.flippedX,\n\t\toffsetTop = layout.offset.top;\n\n\tif (\n\t\t!flippedY && !isTall && hasThumbnail &&\n\t\t\tthumbnail.height < predefinedLandscapeImageHeight\n\t) {\n\t\tpopup.find( '.mwe-popups-extract' ).css(\n\t\t\t'margin-top',\n\t\t\tthumbnail.height - pokeySize\n\t\t);\n\t}\n\n\tpopup.addClass( classes.join( ' ' ) );\n\n\tif ( flippedY ) {\n\t\toffsetTop -= popup.outerHeight();\n\t}\n\n\tpopup.css( {\n\t\ttop: offsetTop,\n\t\tleft: layout.offset.left + 'px'\n\t} );\n\n\tif ( flippedY && hasThumbnail ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.removeAttribute( 'clip-path' );\n\t}\n\n\tif ( flippedY && flippedX && hasThumbnail && isTall ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask-flip)' );\n\t}\n\n\tif ( flippedX && !flippedY && hasThumbnail && !isTall ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );\n\t}\n\n\tif ( flippedX && !flippedY && hasThumbnail && isTall ) {\n\t\tpopup.removeClass( 'mwe-popups-no-image-tri' )\n\t\t\t.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );\n\t}\n}\n\n/**\n * Given the rectangular box(es) find the 'y' boundary of the closest\n * rectangle to the point 'y'. The point 'y' is the location of the mouse\n * on the 'y' axis and the rectangular box(es) are the borders of the\n * element over which the mouse is located. There will be more than one\n * rectangle in case the element spans multiple lines.\n *\n * In the majority of cases the mouse pointer will be inside a rectangle.\n * However, some browsers (i.e. Chrome) trigger a hover action even when\n * the mouse pointer is just outside a bounding rectangle. That's why\n * we need to look at all rectangles and not just the rectangle that\n * encloses the point.\n *\n * @private\n * @param {Number} y the point for which the closest location is being\n * looked for\n * @param {ClientRectList} rects list of rectangles defined by four edges\n * @param {Boolean} [isTop] should the resulting rectangle's top 'y'\n * boundary be returned. By default the bottom 'y' value is returned.\n * @return {Number}\n */\nfunction getClosestYPosition( y, rects, isTop ) {\n\tvar result,\n\t\tdeltaY,\n\t\tminY = null;\n\n\t$.each( rects, function ( i, rect ) {\n\t\tdeltaY = Math.abs( y - rect.top + y - rect.bottom );\n\n\t\tif ( minY === null || minY > deltaY ) {\n\t\t\tminY = deltaY;\n\t\t\t// Make sure the resulting point is at or outside the rectangle\n\t\t\t// boundaries.\n\t\t\tresult = ( isTop ) ? Math.floor( rect.top ) : Math.ceil( rect.bottom );\n\t\t}\n\t} );\n\n\treturn result;\n}\n\nmodule.exports = {\n\trender: render,\n\tinit: init,\n\t// The following are exposed for teseting purposes only\n\tcreatePokeyMasks: createPokeyMasks,\n\tcreatePreview: createPreview,\n\tcreateEmptyPreview: createEmptyPreview,\n\tbindBehavior: bindBehavior,\n\tshow: show,\n\thide: hide,\n\tcreateThumbnail: createThumbnail,\n\tcreateThumbnailElement: createThumbnailElement,\n\tcreateLayout: createLayout,\n\tgetClasses: getClasses,\n\tlayoutPreview: layoutPreview,\n\tgetClosestYPosition: getClosestYPosition\n};\n\n\n/***/ }),\n\n/***/ \"./src/schema.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module schema\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates an instance of the [EventLogging Schema class][0] with a sampling\n * rate of `wgPopupsSchemaSamplingRate` if the UA supports [the Beacon API][1]\n * or `0` if it doesn't.\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-EventLogging/blob/master/modules/ext.eventLogging.Schema.js\n * [1]: https://w3c.github.io/beacon/\n *\n * @param {mw.Map} config\n * @param {Window} window\n * @return {mw.eventLog.Schema}\n */\nmodule.exports = function ( config, window ) {\n\tvar samplingRate = config.get( 'wgPopupsSchemaSamplingRate', 0 );\n\n\tif (\n\t\t!window.navigator ||\n\t\t!$.isFunction( window.navigator.sendBeacon )\n\t) {\n\t\tsamplingRate = 0;\n\t}\n\n\treturn new mw.eventLog.Schema( 'Popups', samplingRate );\n};\n\n\n/***/ }),\n\n/***/ \"./src/settingsDialog.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module settingsDialog\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates a render function that will create the settings dialog and return\n * a set of methods to operate on it\n * @returns {Function} render function\n */\nmodule.exports = function () {\n\n\t/**\n\t * Cached settings dialog\n\t *\n\t * @type {jQuery}\n\t */\n\tvar $dialog,\n\t\t/**\n\t\t * Cached settings overlay\n\t\t *\n\t\t * @type {jQuery}\n\t\t */\n\t\t$overlay;\n\n\t/**\n\t * Renders the relevant form and labels in the settings dialog\n\t * @param {Object} boundActions\n\t * @returns {Object} object with methods to affect the rendered UI\n\t */\n\treturn function ( boundActions ) {\n\n\t\tif ( !$dialog ) {\n\t\t\t$dialog = createSettingsDialog();\n\t\t\t$overlay = $( '<div>' ).addClass( 'mwe-popups-overlay' );\n\n\t\t\t// Setup event bindings\n\n\t\t\t$dialog.find( '.save' ).click( function () {\n\t\t\t\t// Find the selected value (simple|advanced|off)\n\t\t\t\tvar selected = getSelectedSetting( $dialog ),\n\t\t\t\t\t// Only simple means enabled, advanced is disabled in favor of\n\t\t\t\t\t// NavPops and off means disabled.\n\t\t\t\t\tenabled = selected === 'simple';\n\n\t\t\t\tboundActions.saveSettings( enabled );\n\t\t\t} );\n\t\t\t$dialog.find( '.close, .okay' ).click( boundActions.hideSettings );\n\t\t}\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Append the dialog and overlay to a DOM element\n\t\t\t * @param {HTMLElement} el\n\t\t\t */\n\t\t\tappendTo: function ( el ) {\n\t\t\t\t$overlay.appendTo( el );\n\t\t\t\t$dialog.appendTo( el );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Show the settings element and position it correctly\n\t\t\t */\n\t\t\tshow: function () {\n\t\t\t\tvar h = $( window ).height(),\n\t\t\t\t\tw = $( window ).width();\n\n\t\t\t\t$overlay.show();\n\n\t\t\t\t// FIXME: Should recalc on browser resize\n\t\t\t\t$dialog\n\t\t\t\t\t.show()\n\t\t\t\t\t.css( 'left', ( w - $dialog.outerWidth( true ) ) / 2 )\n\t\t\t\t\t.css( 'top', ( h - $dialog.outerHeight( true ) ) / 2 );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Hide the settings dialog.\n\t\t\t */\n\t\t\thide: function () {\n\t\t\t\t$overlay.hide();\n\t\t\t\t$dialog.hide();\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Toggle the help dialog on or off\n\t\t\t * @param {Boolean} visible if you want to show or hide the help dialog\n\t\t\t */\n\t\t\ttoggleHelp: function ( visible ) {\n\t\t\t\ttoggleHelp( $dialog, visible );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Update the form depending on the enabled flag\n\t\t\t *\n\t\t\t * If false and no navpops, then checks 'off'\n\t\t\t * If true, then checks 'on'\n\t\t\t * If false, and there are navpops, then checks 'advanced'\n\t\t\t *\n\t\t\t * @param {Boolean} enabled if page previews are enabled\n\t\t\t */\n\t\t\tsetEnabled: function ( enabled ) {\n\t\t\t\tvar name = 'off';\n\t\t\t\tif ( enabled ) {\n\t\t\t\t\tname = 'simple';\n\t\t\t\t} else if ( isNavPopupsEnabled() ) {\n\t\t\t\t\tname = 'advanced';\n\t\t\t\t}\n\n\t\t\t\t// Check the appropiate radio button\n\t\t\t\t$dialog.find( '#mwe-popups-settings-' + name )\n\t\t\t\t\t.prop( 'checked', true );\n\t\t\t}\n\t\t};\n\t};\n};\n\n/**\n * Create the settings dialog\n *\n * @return {jQuery} settings dialog\n */\nfunction createSettingsDialog() {\n\tvar $el,\n\t\tpath = mw.config.get( 'wgExtensionAssetsPath' ) + '/Popups/resources/ext.popups/images/',\n\t\tchoices = [\n\t\t\t{\n\t\t\t\tid: 'simple',\n\t\t\t\tname: mw.msg( 'popups-settings-option-simple' ),\n\t\t\t\tdescription: mw.msg( 'popups-settings-option-simple-description' ),\n\t\t\t\timage: path + 'hovercard.svg',\n\t\t\t\tisChecked: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'advanced',\n\t\t\t\tname: mw.msg( 'popups-settings-option-advanced' ),\n\t\t\t\tdescription: mw.msg( 'popups-settings-option-advanced-description' ),\n\t\t\t\timage: path + 'navpop.svg'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'off',\n\t\t\t\tname: mw.msg( 'popups-settings-option-off' )\n\t\t\t}\n\t\t];\n\n\tif ( !isNavPopupsEnabled() ) {\n\t\t// remove the advanced option\n\t\tchoices.splice( 1, 1 );\n\t}\n\n\t// render the template\n\t$el = mw.template.get( 'ext.popups', 'settings.mustache' ).render( {\n\t\theading: mw.msg( 'popups-settings-title' ),\n\t\tcloseLabel: mw.msg( 'popups-settings-cancel' ),\n\t\tsaveLabel: mw.msg( 'popups-settings-save' ),\n\t\thelpText: mw.msg( 'popups-settings-help' ),\n\t\tokLabel: mw.msg( 'popups-settings-help-ok' ),\n\t\tdescriptionText: mw.msg( 'popups-settings-description' ),\n\t\tchoices: choices\n\t} );\n\n\treturn $el;\n}\n\n/**\n * Get the selected value on the radio button\n *\n * @param {jQuery.Object} $el the element to extract the setting from\n * @return {String} Which should be (simple|advanced|off)\n */\nfunction getSelectedSetting( $el ) {\n\treturn $el.find(\n\t\t'input[name=mwe-popups-setting]:checked, #mwe-popups-settings'\n\t).val();\n}\n\n/**\n * Toggles the visibility between a form and the help\n * @param {jQuery.Object} $el element that contains form and help\n * @param {Boolean} visible if the help should be visible, or the form\n */\nfunction toggleHelp( $el, visible ) {\n\tvar $dialog = $( '#mwe-popups-settings' ),\n\t\tformSelectors = 'main, .save, .close',\n\t\thelpSelectors = '.mwe-popups-settings-help, .okay';\n\n\tif ( visible ) {\n\t\t$dialog.find( formSelectors ).hide();\n\t\t$dialog.find( helpSelectors ).show();\n\t} else {\n\t\t$dialog.find( formSelectors ).show();\n\t\t$dialog.find( helpSelectors ).hide();\n\t}\n}\n\n/**\n * Checks if the NavigationPopups gadget is enabled by looking at the global\n * variables\n * @returns {Boolean} if navpops was found to be enabled\n */\nfunction isNavPopupsEnabled() {\n\t/* global pg: false*/\n\treturn typeof pg !== 'undefined' && pg.fn.disablePopups !== undefined;\n}\n\n\n/***/ }),\n\n/***/ \"./src/statsvInstrumentation.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module statsvInstrumentation\n */\n\n/**\n * Gets whether Graphite logging (via [the statsv HTTP endpoint][0]) is enabled\n * for duration of the browser session. The sampling rate is controlled by\n * `wgPopupsStatsvSamplingRate`.\n *\n * [0]: https://wikitech.wikimedia.org/wiki/Graphite#statsv\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {mw.Map} config The `mw.config` singleton instance\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n * @returns {Boolean}\n */\nexports.isEnabled = function isEnabled( user, config, experiments ) {\n\tvar samplingRate = config.get( 'wgPopupsStatsvSamplingRate', 0 ),\n\t\tbucket = experiments.getBucket( {\n\t\t\tname: 'ext.Popups.statsv',\n\t\t\tenabled: true,\n\t\t\tbuckets: {\n\t\t\t\tcontrol: 1 - samplingRate,\n\t\t\t\tA: samplingRate\n\t\t\t}\n\t\t}, user.sessionId() );\n\n\treturn bucket === 'A';\n};\n\n\n/***/ }),\n\n/***/ \"./src/title.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module title\n */\n\nvar mw = window.mediaWiki;\n\n/**\n * Gets the title of a local page from an href given some configuration.\n *\n * @param {String} href\n * @param {mw.Map} config\n * @return {String|undefined}\n */\nfunction getTitle( href, config ) {\n\tvar linkHref,\n\t\tmatches,\n\t\tqueryLength,\n\t\ttitleRegex = new RegExp( mw.RegExp.escape( config.get( 'wgArticlePath' ) )\n\t\t\t.replace( '\\\\$1', '(.+)' ) );\n\n\t// Skip every URI that mw.Uri cannot parse\n\ttry {\n\t\tlinkHref = new mw.Uri( href );\n\t} catch ( e ) {\n\t\treturn undefined;\n\t}\n\n\t// External links\n\tif ( linkHref.host !== location.hostname ) {\n\t\treturn undefined;\n\t}\n\n\tqueryLength = Object.keys( linkHref.query ).length;\n\n\t// No query params (pretty URL)\n\tif ( !queryLength ) {\n\t\tmatches = titleRegex.exec( linkHref.path );\n\t\treturn matches ? decodeURIComponent( matches[ 1 ] ) : undefined;\n\t} else if ( queryLength === 1 && linkHref.query.hasOwnProperty( 'title' ) ) {\n\t\t// URL is not pretty, but only has a `title` parameter\n\t\treturn linkHref.query.title;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Given a page title it will return the mediawiki.Title if it is an eligible\n * link for showing page previews, null otherwise\n *\n * @param {String} title page title to check if it should show preview\n * @param {Number[]} contentNamespaces contentNamespaces as specified in\n * wgContentNamespaces\n * @returns {mw.Title|null}\n */\nfunction isValid( title, contentNamespaces ) {\n\tvar mwTitle;\n\n\tif ( !title ) {\n\t\treturn null;\n\t}\n\n\t// Is title in a content namespace?\n\tmwTitle = mw.Title.newFromText( title );\n\tif ( mwTitle && ( $.inArray( mwTitle.namespace, contentNamespaces ) >= 0 ) ) {\n\t\treturn mwTitle;\n\t}\n\n\treturn null;\n}\n\n/**\n * Return a mw.Title from a HTMLElement if valid for hovercards. Convenience\n * method\n *\n * @param {Element} el\n * @param {mw.Map} config\n * @return {mw.Title|null}\n */\nfunction fromElement( el, config ) {\n\treturn isValid( getTitle( el.href, config ), config.get( 'wgContentNamespaces' ) );\n}\n\nmodule.exports = {\n\tgetTitle: getTitle,\n\tisValid: isValid,\n\tfromElement: fromElement\n};\n\n\n/***/ }),\n\n/***/ \"./src/userSettings.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module userSettings\n */\n\n/**\n * @interface UserSettings\n *\n * @global\n */\n\nvar IS_ENABLED_KEY = 'mwe-popups-enabled',\n\tPREVIEW_COUNT_KEY = 'ext.popups.core.previewCount';\n\n/**\n * Creates an object whose methods encapsulate all interactions with the UA's\n * storage.\n *\n * @param {Object} storage The `mw.storage` singleton instance\n *\n * @return {UserSettings}\n */\nmodule.exports = function ( storage ) {\n\treturn {\n\n\t\t/**\n\t\t * Gets whether the user has previously enabled Page Previews.\n\t\t *\n\t\t * N.B. that if the user hasn't previously enabled or disabled Page\n\t\t * Previews, i.e. userSettings.setIsEnabled(true), then they are treated as\n\t\t * if they have enabled them.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#getIsEnabled\n\t\t * @return {Boolean}\n\t\t */\n\t\tgetIsEnabled: function () {\n\t\t\treturn storage.get( IS_ENABLED_KEY ) !== '0';\n\t\t},\n\n\t\t/**\n\t\t * Sets whether the user has enabled Page Previews.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#setIsEnabled\n\t\t * @param {Boolean} isEnabled\n\t\t */\n\t\tsetIsEnabled: function ( isEnabled ) {\n\t\t\tstorage.set( IS_ENABLED_KEY, isEnabled ? '1' : '0' );\n\t\t},\n\n\t\t/**\n\t\t * Gets whether the user has previously enabled **or disabled** Page\n\t\t * Previews.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#hasIsEnabled\n\t\t * @return {Boolean}\n\t\t */\n\t\thasIsEnabled: function () {\n\t\t\tvar value = storage.get( IS_ENABLED_KEY );\n\n\t\t\treturn Boolean( value ) !== false;\n\t\t},\n\n\t\t/**\n\t\t * Gets the number of previews that the user has seen.\n\t\t *\n\t\t * If the storage isn't available, then -1 is returned.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#getPreviewCount\n\t\t * @return {Number}\n\t\t */\n\t\tgetPreviewCount: function () {\n\t\t\tvar result = storage.get( PREVIEW_COUNT_KEY );\n\n\t\t\tif ( result === false ) {\n\t\t\t\treturn -1;\n\t\t\t} else if ( result === null ) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn parseInt( result, 10 );\n\t\t},\n\n\t\t/**\n\t\t * Sets the number of previews that the user has seen.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#setPreviewCount\n\t\t * @param {Number} count\n\t\t */\n\t\tsetPreviewCount: function ( count ) {\n\t\t\tstorage.set( PREVIEW_COUNT_KEY, count.toString() );\n\t\t}\n\t};\n};\n\n\n/***/ }),\n\n/***/ \"./src/wait.js\":\n/***/ (function(module, exports) {\n\n/**\n * @module wait\n */\n\nvar $ = jQuery;\n\n/**\n * Sugar around `window.setTimeout`.\n *\n * @example\n * var wait = require( './wait' );\n *\n * wait( 150 )\n * .then( function () {\n * // Continue processing...\n * } );\n *\n * @param {Number} delay The number of milliseconds to wait\n * @return {jQuery.Promise}\n */\nmodule.exports = function ( delay ) {\n\tvar result = $.Deferred();\n\n\tsetTimeout( function () {\n\t\tresult.resolve();\n\t}, delay );\n\n\treturn result.promise();\n};\n\n\n/***/ })\n\n/******/ });\n\n\n// WEBPACK FOOTER //\n// index.js"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./src/index.js\");\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap dc5653e6684c29ac34ed","!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.ReduxThunk=e():t.ReduxThunk=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p=\"\",e(0)}([function(t,e,n){t.exports=n(1)},function(t,e){\"use strict\";function n(t){return function(e){var n=e.dispatch,o=e.getState;return function(e){return function(r){return\"function\"==typeof r?r(n,o,t):e(r)}}}}e.__esModule=!0;var o=n();o.withExtraArgument=n,e.default=o}])});\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/redux-thunk/dist/redux-thunk.min.js\n// module id = ./node_modules/redux-thunk/dist/redux-thunk.min.js\n// module chunks = 0","!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.Redux=e():t.Redux=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p=\"\",e(0)}([function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}e.__esModule=!0,e.compose=e.applyMiddleware=e.bindActionCreators=e.combineReducers=e.createStore=void 0;var o=n(2),u=r(o),i=n(7),c=r(i),a=n(6),f=r(a),s=n(5),d=r(s),l=n(1),p=r(l),y=n(3);r(y);e.createStore=u[\"default\"],e.combineReducers=c[\"default\"],e.bindActionCreators=f[\"default\"],e.applyMiddleware=d[\"default\"],e.compose=p[\"default\"]},function(t,e){\"use strict\";function n(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];if(0===e.length)return function(t){return t};if(1===e.length)return e[0];var r=e[e.length-1],o=e.slice(0,-1);return function(){return o.reduceRight(function(t,e){return e(t)},r.apply(void 0,arguments))}}e.__esModule=!0,e[\"default\"]=n},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e,n){function r(){b===h&&(b=h.slice())}function u(){return v}function c(t){if(\"function\"!=typeof t)throw Error(\"Expected listener to be a function.\");var e=!0;return r(),b.push(t),function(){if(e){e=!1,r();var n=b.indexOf(t);b.splice(n,1)}}}function s(t){if(!(0,i[\"default\"])(t))throw Error(\"Actions must be plain objects. Use custom middleware for async actions.\");if(void 0===t.type)throw Error('Actions may not have an undefined \"type\" property. Have you misspelled a constant?');if(m)throw Error(\"Reducers may not dispatch actions.\");try{m=!0,v=y(v,t)}finally{m=!1}for(var e=h=b,n=0;e.length>n;n++)e[n]();return t}function d(t){if(\"function\"!=typeof t)throw Error(\"Expected the nextReducer to be a function.\");y=t,s({type:f.INIT})}function l(){var t,e=c;return t={subscribe:function(t){function n(){t.next&&t.next(u())}if(\"object\"!=typeof t)throw new TypeError(\"Expected the observer to be an object.\");n();var r=e(n);return{unsubscribe:r}}},t[a[\"default\"]]=function(){return this},t}var p;if(\"function\"==typeof e&&void 0===n&&(n=e,e=void 0),void 0!==n){if(\"function\"!=typeof n)throw Error(\"Expected the enhancer to be a function.\");return n(o)(t,e)}if(\"function\"!=typeof t)throw Error(\"Expected the reducer to be a function.\");var y=t,v=e,h=[],b=h,m=!1;return s({type:f.INIT}),p={dispatch:s,subscribe:c,getState:u,replaceReducer:d},p[a[\"default\"]]=l,p}e.__esModule=!0,e.ActionTypes=void 0,e[\"default\"]=o;var u=n(4),i=r(u),c=n(12),a=r(c),f=e.ActionTypes={INIT:\"@@redux/INIT\"}},function(t,e){\"use strict\";function n(t){\"undefined\"!=typeof console&&\"function\"==typeof console.error&&console.error(t);try{throw Error(t)}catch(e){}}e.__esModule=!0,e[\"default\"]=n},function(t,e,n){function r(t){if(!i(t)||p.call(t)!=c||u(t))return!1;var e=o(t);if(null===e)return!0;var n=d.call(e,\"constructor\")&&e.constructor;return\"function\"==typeof n&&n instanceof n&&s.call(n)==l}var o=n(8),u=n(9),i=n(11),c=\"[object Object]\",a=Function.prototype,f=Object.prototype,s=a.toString,d=f.hasOwnProperty,l=s.call(Object),p=f.toString;t.exports=r},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];return function(t){return function(n,r,o){var i=t(n,r,o),a=i.dispatch,f=[],s={getState:i.getState,dispatch:function(t){return a(t)}};return f=e.map(function(t){return t(s)}),a=c[\"default\"].apply(void 0,f)(i.dispatch),u({},i,{dispatch:a})}}}e.__esModule=!0;var u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t};e[\"default\"]=o;var i=n(1),c=r(i)},function(t,e){\"use strict\";function n(t,e){return function(){return e(t.apply(void 0,arguments))}}function r(t,e){if(\"function\"==typeof t)return n(t,e);if(\"object\"!=typeof t||null===t)throw Error(\"bindActionCreators expected an object or a function, instead received \"+(null===t?\"null\":typeof t)+'. Did you write \"import ActionCreators from\" instead of \"import * as ActionCreators from\"?');for(var r=Object.keys(t),o={},u=0;r.length>u;u++){var i=r[u],c=t[i];\"function\"==typeof c&&(o[i]=n(c,e))}return o}e.__esModule=!0,e[\"default\"]=r},function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e){var n=e&&e.type,r=n&&'\"'+n+'\"'||\"an action\";return\"Given action \"+r+', reducer \"'+t+'\" returned undefined. To ignore an action, you must explicitly return the previous state.'}function u(t){Object.keys(t).forEach(function(e){var n=t[e],r=n(void 0,{type:c.ActionTypes.INIT});if(void 0===r)throw Error('Reducer \"'+e+'\" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined.');var o=\"@@redux/PROBE_UNKNOWN_ACTION_\"+Math.random().toString(36).substring(7).split(\"\").join(\".\");if(void 0===n(void 0,{type:o}))throw Error('Reducer \"'+e+'\" returned undefined when probed with a random type. '+(\"Don't try to handle \"+c.ActionTypes.INIT+' or other actions in \"redux/*\" ')+\"namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined.\")})}function i(t){for(var e=Object.keys(t),n={},r=0;e.length>r;r++){var i=e[r];\"function\"==typeof t[i]&&(n[i]=t[i])}var c,a=Object.keys(n);try{u(n)}catch(f){c=f}return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments[1];if(c)throw c;for(var r=!1,u={},i=0;a.length>i;i++){var f=a[i],s=n[f],d=t[f],l=s(d,e);if(void 0===l){var p=o(f,e);throw Error(p)}u[f]=l,r=r||l!==d}return r?u:t}}e.__esModule=!0,e[\"default\"]=i;var c=n(2),a=n(4),f=(r(a),n(3));r(f)},function(t,e,n){var r=n(10),o=r(Object.getPrototypeOf,Object);t.exports=o},function(t,e){function n(t){var e=!1;if(null!=t&&\"function\"!=typeof t.toString)try{e=!!(t+\"\")}catch(n){}return e}t.exports=n},function(t,e){function n(t,e){return function(n){return t(e(n))}}t.exports=n},function(t,e){function n(t){return!!t&&\"object\"==typeof t}t.exports=n},function(t,e,n){t.exports=n(13)},function(t,e,n){(function(t){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}Object.defineProperty(e,\"__esModule\",{value:!0});var o=n(14),u=r(o),i=void 0;void 0!==t?i=t:\"undefined\"!=typeof window&&(i=window);var c=(0,u[\"default\"])(i);e[\"default\"]=c}).call(e,function(){return this}())},function(t,e){\"use strict\";function n(t){var e,n=t.Symbol;return\"function\"==typeof n?n.observable?e=n.observable:(e=n(\"observable\"),n.observable=e):e=\"@@observable\",e}Object.defineProperty(e,\"__esModule\",{value:!0}),e[\"default\"]=n}])});\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/redux/dist/redux.min.js\n// module id = ./node_modules/redux/dist/redux.min.js\n// module chunks = 0","/**\n * @module actionTypes\n */\n\nmodule.exports = {\n\tBOOT: 'BOOT',\n\tLINK_DWELL: 'LINK_DWELL',\n\tABANDON_START: 'ABANDON_START',\n\tABANDON_END: 'ABANDON_END',\n\tLINK_CLICK: 'LINK_CLICK',\n\tFETCH_START: 'FETCH_START',\n\tFETCH_END: 'FETCH_END',\n\tFETCH_COMPLETE: 'FETCH_COMPLETE',\n\tFETCH_FAILED: 'FETCH_FAILED',\n\tPREVIEW_DWELL: 'PREVIEW_DWELL',\n\tPREVIEW_SHOW: 'PREVIEW_SHOW',\n\tPREVIEW_CLICK: 'PREVIEW_CLICK',\n\tSETTINGS_SHOW: 'SETTINGS_SHOW',\n\tSETTINGS_HIDE: 'SETTINGS_HIDE',\n\tSETTINGS_CHANGE: 'SETTINGS_CHANGE',\n\tEVENT_LOGGED: 'EVENT_LOGGED',\n\tSTATSV_LOGGED: 'STATSV_LOGGED'\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/actionTypes.js\n// module id = ./src/actionTypes.js\n// module chunks = 0","/**\n * @module actions\n */\n\nvar $ = jQuery,\n\tmw = window.mediaWiki,\n\tactions = {},\n\ttypes = require( './actionTypes' ),\n\twait = require( './wait' ),\n\n\t// See the following for context around this value.\n\t//\n\t// * https://phabricator.wikimedia.org/T161284\n\t// * https://phabricator.wikimedia.org/T70861#3129780\n\tFETCH_START_DELAY = 150, // ms.\n\n\t// The delay after which a FETCH_COMPLETE action should be dispatched.\n\t//\n\t// If the API endpoint responds faster than 500 ms (or, say, the API\n\t// response is served from the UA's cache), then we introduce a delay of\n\t// 500 - t to make the preview delay consistent to the user.\n\tFETCH_COMPLETE_TARGET_DELAY = 500, // ms.\n\n\tABANDON_END_DELAY = 300; // ms.\n\n/**\n * Mixes in timing information to an action.\n *\n * Warning: the `baseAction` parameter is modified and returned.\n *\n * @param {Object} baseAction\n * @return {Object}\n */\nfunction timedAction( baseAction ) {\n\tbaseAction.timestamp = mw.now();\n\n\treturn baseAction;\n}\n\n/**\n * Represents Page Previews booting.\n *\n * When a Redux store is created, the `@@INIT` action is immediately\n * dispatched to it. To avoid overriding the term, we refer to booting rather\n * than initializing.\n *\n * Page Previews persists critical pieces of information to local storage.\n * Since reading from and writing to local storage are synchronous, Page\n * Previews is booted when the browser is idle (using\n * [`mw.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback))\n * so as not to impact latency-critical events.\n *\n * @param {Boolean} isEnabled See `isEnabled.js`\n * @param {mw.user} user\n * @param {ext.popups.UserSettings} userSettings\n * @param {Function} generateToken\n * @param {mw.Map} config The config of the MediaWiki client-side application,\n * i.e. `mw.config`\n * @returns {Object}\n */\nactions.boot = function (\n\tisEnabled,\n\tuser,\n\tuserSettings,\n\tgenerateToken,\n\tconfig\n) {\n\tvar editCount = config.get( 'wgUserEditCount' ),\n\t\tpreviewCount = userSettings.getPreviewCount();\n\n\treturn {\n\t\ttype: types.BOOT,\n\t\tisEnabled: isEnabled,\n\t\tisNavPopupsEnabled: config.get( 'wgPopupsConflictsWithNavPopupGadget' ),\n\t\tsessionToken: user.sessionId(),\n\t\tpageToken: generateToken(),\n\t\tpage: {\n\t\t\ttitle: config.get( 'wgTitle' ),\n\t\t\tnamespaceID: config.get( 'wgNamespaceNumber' ),\n\t\t\tid: config.get( 'wgArticleId' )\n\t\t},\n\t\tuser: {\n\t\t\tisAnon: user.isAnon(),\n\t\t\teditCount: editCount,\n\t\t\tpreviewCount: previewCount\n\t\t}\n\t};\n};\n\n/**\n * Represents Page Previews fetching data via the gateway.\n *\n * @param {ext.popups.Gateway} gateway\n * @param {mw.Title} title\n * @param {Element} el\n * @param {String} token The unique token representing the link interaction that\n * triggered the fetch\n * @return {Redux.Thunk}\n */\nactions.fetch = function ( gateway, title, el, token ) {\n\tvar titleText = title.getPrefixedDb(),\n\t\tnamespaceID = title.namespace;\n\n\treturn function ( dispatch ) {\n\t\tvar request;\n\n\t\tdispatch( timedAction( {\n\t\t\ttype: types.FETCH_START,\n\t\t\tel: el,\n\t\t\ttitle: titleText,\n\t\t\tnamespaceID: namespaceID\n\t\t} ) );\n\n\t\trequest = gateway.getPageSummary( titleText )\n\t\t\t.then( function ( result ) {\n\t\t\t\tdispatch( timedAction( {\n\t\t\t\t\ttype: types.FETCH_END,\n\t\t\t\t\tel: el\n\t\t\t\t} ) );\n\n\t\t\t\treturn result;\n\t\t\t} )\n\t\t\t.fail( function () {\n\t\t\t\tdispatch( {\n\t\t\t\t\ttype: types.FETCH_FAILED,\n\t\t\t\t\tel: el\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t$.when( request, wait( FETCH_COMPLETE_TARGET_DELAY - FETCH_START_DELAY ) )\n\t\t\t.then( function ( result ) {\n\t\t\t\tdispatch( timedAction( {\n\t\t\t\t\ttype: types.FETCH_COMPLETE,\n\t\t\t\t\tel: el,\n\t\t\t\t\tresult: result,\n\t\t\t\t\ttoken: token\n\t\t\t\t} ) );\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user dwelling on a link, either by hovering over it with\n * their mouse or by focussing it using their keyboard or an assistive device.\n *\n * @param {mw.Title} title\n * @param {Element} el\n * @param {Event} event\n * @param {ext.popups.Gateway} gateway\n * @param {Function} generateToken\n * @return {Redux.Thunk}\n */\nactions.linkDwell = function ( title, el, event, gateway, generateToken ) {\n\tvar token = generateToken(),\n\t\ttitleText = title.getPrefixedDb(),\n\t\tnamespaceID = title.namespace;\n\n\treturn function ( dispatch, getState ) {\n\t\tvar action = timedAction( {\n\t\t\ttype: types.LINK_DWELL,\n\t\t\tel: el,\n\t\t\tevent: event,\n\t\t\ttoken: token,\n\t\t\ttitle: titleText,\n\t\t\tnamespaceID: namespaceID\n\t\t} );\n\n\t\t// Has the new generated token been accepted?\n\t\tfunction isNewInteraction() {\n\t\t\treturn getState().preview.activeToken === token;\n\t\t}\n\n\t\tdispatch( action );\n\n\t\tif ( !isNewInteraction() ) {\n\t\t\treturn;\n\t\t}\n\n\t\twait( FETCH_START_DELAY )\n\t\t\t.then( function () {\n\t\t\t\tvar previewState = getState().preview;\n\n\t\t\t\tif ( previewState.enabled && isNewInteraction() ) {\n\t\t\t\t\tdispatch( actions.fetch( gateway, title, el, token ) );\n\t\t\t\t}\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user abandoning a link, either by moving their mouse away\n * from it or by shifting focus to another UI element using their keyboard or\n * an assistive device, or abandoning a preview by moving their mouse away\n * from it.\n *\n * @return {Redux.Thunk}\n */\nactions.abandon = function () {\n\treturn function ( dispatch, getState ) {\n\t\tvar token = getState().preview.activeToken;\n\n\t\tif ( !token ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdispatch( timedAction( {\n\t\t\ttype: types.ABANDON_START,\n\t\t\ttoken: token\n\t\t} ) );\n\n\t\twait( ABANDON_END_DELAY )\n\t\t\t.then( function () {\n\t\t\t\tdispatch( {\n\t\t\t\t\ttype: types.ABANDON_END,\n\t\t\t\t\ttoken: token\n\t\t\t\t} );\n\t\t\t} );\n\t};\n};\n\n/**\n * Represents the user clicking on a link with their mouse, keyboard, or an\n * assistive device.\n *\n * @param {Element} el\n * @return {Object}\n */\nactions.linkClick = function ( el ) {\n\treturn timedAction( {\n\t\ttype: types.LINK_CLICK,\n\t\tel: el\n\t} );\n};\n\n/**\n * Represents the user dwelling on a preview with their mouse.\n *\n * @return {Object}\n */\nactions.previewDwell = function () {\n\treturn {\n\t\ttype: types.PREVIEW_DWELL\n\t};\n};\n\n/**\n * Represents a preview being shown to the user.\n *\n * This action is dispatched by the `./changeListeners/render.js` change\n * listener.\n *\n * @param {String} token\n * @return {Object}\n */\nactions.previewShow = function ( token ) {\n\treturn timedAction( {\n\t\ttype: types.PREVIEW_SHOW,\n\t\ttoken: token\n\t} );\n};\n\n/**\n * Represents the user clicking either the \"Enable previews\" footer menu link,\n * or the \"cog\" icon that's present on each preview.\n *\n * @return {Object}\n */\nactions.showSettings = function () {\n\treturn {\n\t\ttype: types.SETTINGS_SHOW\n\t};\n};\n\n/**\n * Represents the user closing the settings dialog and saving their settings.\n *\n * @return {Object}\n */\nactions.hideSettings = function () {\n\treturn {\n\t\ttype: types.SETTINGS_HIDE\n\t};\n};\n\n/**\n * Represents the user saving their settings.\n *\n * N.B. This action returns a Redux.Thunk not because it needs to perform\n * asynchronous work, but because it needs to query the global state for the\n * current enabled state. In order to keep the enabled state in a single\n * place (the preview reducer), we query it and dispatch it as `wasEnabled`\n * so that other reducers (like settings) can act on it without having to\n * duplicate the `enabled` state locally.\n * See doc/adr/0003-keep-enabled-state-only-in-preview-reducer.md for more\n * details.\n *\n * @param {Boolean} enabled if previews are enabled or not\n * @return {Redux.Thunk}\n */\nactions.saveSettings = function ( enabled ) {\n\treturn function ( dispatch, getState ) {\n\t\tdispatch( {\n\t\t\ttype: types.SETTINGS_CHANGE,\n\t\t\twasEnabled: getState().preview.enabled,\n\t\t\tenabled: enabled\n\t\t} );\n\t};\n};\n\n/**\n * Represents the queued event being logged `changeListeners/eventLogging.js`\n * change listener.\n *\n * @param {Object} event\n * @return {Object}\n */\nactions.eventLogged = function ( event ) {\n\treturn {\n\t\ttype: types.EVENT_LOGGED,\n\t\tevent: event\n\t};\n};\n\n/**\n * Represents the queued statsv event being logged.\n * See `mw.popups.changeListeners.statsv` change listener.\n *\n * @return {Object}\n */\nactions.statsvLogged = function () {\n\treturn {\n\t\ttype: types.STATSV_LOGGED\n\t};\n};\nmodule.exports = actions;\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/actions.js\n// module id = ./src/actions.js\n// module chunks = 0","/**\n * @module changeListener\n */\n\n/**\n * @typedef {Function} ext.popups.ChangeListener\n * @param {Object} prevState The previous state\n * @param {Object} state The current state\n */\n\n/**\n * Registers a change listener, which is bound to the\n * [store](http://redux.js.org/docs/api/Store.html).\n *\n * A change listener is a function that is only invoked when the state in the\n * [store](http://redux.js.org/docs/api/Store.html) changes. N.B. that there\n * may not be a 1:1 correspondence with actions being dispatched to the store\n * and the state in the store changing.\n *\n * See [Store#subscribe](http://redux.js.org/docs/api/Store.html#subscribe)\n * for more information about what change listeners may and may not do.\n *\n * @param {Redux.Store} store\n * @param {ext.popups.ChangeListener} callback\n */\nmodule.exports = function ( store, callback ) {\n\t// This function is based on the example in [the documentation for\n\t// Store#subscribe](http://redux.js.org/docs/api/Store.html#subscribe),\n\t// which was written by Dan Abramov.\n\n\tvar state;\n\n\tstore.subscribe( function () {\n\t\tvar prevState = state;\n\n\t\tstate = store.getState();\n\n\t\tif ( prevState !== state ) {\n\t\t\tcallback( prevState, state );\n\t\t}\n\t} );\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListener.js\n// module id = ./src/changeListener.js\n// module chunks = 0","/**\n * @module changeListeners/eventLogging\n */\n\nvar $ = jQuery;\n\n/**\n * Hashes the string using the 32-bit FNV-1a algorithm.\n *\n * @see http://isthe.com/chongo/tech/comp/fnv/#FNV-1a\n * @see http://isthe.com/chongo/tech/comp/fnv/#FNV-param\n *\n * @param {String} string\n * @return {Number} A 32-bit unsigned integer\n */\nfunction fnv1a32( string ) {\n\t/* eslint-disable no-bitwise */\n\n\tvar result = 2166136261, // Offset basis.\n\t\ti = 0;\n\n\tfor ( i = 0; i < string.length; ++i ) {\n\t\tresult ^= string.charCodeAt( i );\n\t\tresult *= 16777619; // Prime.\n\t}\n\n\treturn result >>> 0;\n\t/* eslint-enable no-bitwise */\n}\n\n/**\n * Creates an instance of the event logging change listener.\n *\n * When an event is enqueued it'll be logged using the schema. Since it's the\n * responsibility of Event Logging (and the UA) to deliver logged events,\n * `EVENT_LOGGED` is immediately dispatched rather than waiting for some\n * indicator of completion.\n *\n * This change listener also stores hashes of all enqueued events. If a\n * duplicate event is queued - there's a hash collision - then the\n * `PagePreviews.EventLogging.DuplicateEvent` counter is incremented via [the\n * \"StatsD timers and counters\" analytics event protocol][0].\n *\n * See the following for additional context:\n *\n * * https://phabricator.wikimedia.org/T161769\n * * https://phabricator.wikimedia.org/T163198\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} boundActions\n * @param {mw.eventLog.Schema} schema\n * @param {ext.popups.EventTracker} track\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, schema, track ) {\n\tvar tokenToSeenMap = {},\n\t\thashToSeenMap = {};\n\n\treturn function ( _, state ) {\n\t\tvar eventLogging = state.eventLogging,\n\t\t\tevent = eventLogging.event,\n\t\t\ttoken,\n\t\t\thash,\n\t\t\tshouldLog = true;\n\n\t\tif ( !event ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttoken = event.linkInteractionToken;\n\n\t\tif ( tokenToSeenMap[ token ] === true ) {\n\t\t\ttrack( 'counter.PagePreviews.EventLogging.DuplicateToken', 1 );\n\n\t\t\tshouldLog = false;\n\t\t}\n\n\t\ttokenToSeenMap[ token ] = true;\n\n\t\t// Use 32-bit FNV-1a based on Ian Boyd's (incredibly detailed) analysis of\n\t\t// several algorithms designed to quickly hash a string\n\t\t// <https://softwareengineering.stackexchange.com/a/145633>.\n\t\t//\n\t\t// ...\n\t\t//\n\t\t// It's also remarkably easy to implement!\n\t\thash = fnv1a32( JSON.stringify( event ) ).toString( 32 );\n\n\t\t// Has the event been seen before?\n\t\tif ( hashToSeenMap[ hash ] === true ) {\n\t\t\ttrack( 'counter.PagePreviews.EventLogging.DuplicateEvent', 1 );\n\n\t\t\tshouldLog = false;\n\t\t}\n\n\t\thashToSeenMap[ hash ] = true;\n\n\t\tevent = $.extend( true, {}, eventLogging.baseData, event );\n\n\t\tif ( shouldLog ) {\n\t\t\tschema.log( event );\n\t\t}\n\n\t\t// Dispatch the eventLogged action even if it was a duplicate so that the\n\t\t// state tree can be cleared/updated.\n\t\tboundActions.eventLogged( event );\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/eventLogging.js\n// module id = ./src/changeListeners/eventLogging.js\n// module chunks = 0","/**\n * @module changeListeners/footerLink\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates the link element and appends it to the footer element.\n *\n * The following elements are considered to be the footer element (highest\n * priority to lowest):\n *\n * # `#footer-places`\n * # `#f-list`\n * # The parent element of `#footer li`, which is either an `ol` or `ul`.\n *\n * @return {jQuery} The link element\n */\nfunction createFooterLink() {\n\tvar $link = $( '<li>' ).append(\n\t\t\t$( '<a>' )\n\t\t\t\t.attr( 'href', '#' )\n\t\t\t\t.text( mw.message( 'popups-settings-enable' ).text() )\n\t\t),\n\t\t$footer;\n\n\t// As yet, we don't know whether the link should be visible.\n\t$link.hide();\n\n\t// From https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js,\n\t// which was written by Yair rand <https://en.wikipedia.org/wiki/User:Yair_rand>.\n\t$footer = $( '#footer-places, #f-list' );\n\n\tif ( $footer.length === 0 ) {\n\t\t$footer = $( '#footer li' ).parent();\n\t}\n\n\t$footer.append( $link );\n\n\treturn $link;\n}\n\n/**\n * Creates an instance of the footer link change listener.\n *\n * The change listener covers the following behaviour:\n *\n * * The \"Enable previews\" link (the \"link\") is appended to the footer menu\n * (see `createFooterLink` above).\n * * When Page Previews are disabled, then the link is shown; otherwise, the\n * link is hidden.\n * * When the user clicks the link, then the `showSettings` bound action\n * creator is called.\n *\n * @param {Object} boundActions\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions ) {\n\tvar $footerLink;\n\n\treturn function ( prevState, state ) {\n\t\tif ( $footerLink === undefined ) {\n\t\t\t$footerLink = createFooterLink();\n\t\t\t$footerLink.click( function ( e ) {\n\t\t\t\te.preventDefault();\n\t\t\t\tboundActions.showSettings();\n\t\t\t} );\n\t\t}\n\n\t\tif ( state.settings.shouldShowFooterLink ) {\n\t\t\t$footerLink.show();\n\t\t} else {\n\t\t\t$footerLink.hide();\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/footerLink.js\n// module id = ./src/changeListeners/footerLink.js\n// module chunks = 0","module.exports = {\n\tfooterLink: require( './footerLink' ),\n\teventLogging: require( './eventLogging' ),\n\tlinkTitle: require( './linkTitle' ),\n\trender: require( './render' ),\n\tsettings: require( './settings' ),\n\tstatsv: require( './statsv' ),\n\tsyncUserSettings: require( './syncUserSettings' )\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/index.js\n// module id = ./src/changeListeners/index.js\n// module chunks = 0","var $ = jQuery;\n\n/**\n * Creates an instance of the link title change listener.\n *\n * While the user dwells on a link, then it becomes the active link. The\n * change listener will remove a link's `title` attribute while it's the\n * active link.\n *\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function () {\n\tvar title;\n\n\t/**\n\t * Destroys the title attribute of the element, storing its value in local\n\t * state so that it can be restored later (see `restoreTitleAttr`).\n\t *\n\t * @param {Element} el\n\t */\n\tfunction destroyTitleAttr( el ) {\n\t\tvar $el = $( el );\n\n\t\t// Has the user dwelled on a link? If we've already removed its title\n\t\t// attribute, then NOOP.\n\t\tif ( title ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttitle = $el.attr( 'title' );\n\n\t\t$el.attr( 'title', '' );\n\t}\n\n\t/**\n\t * Restores the title attribute of the element.\n\t *\n\t * @param {Element} el\n\t */\n\tfunction restoreTitleAttr( el ) {\n\t\t$( el ).attr( 'title', title );\n\n\t\ttitle = undefined;\n\t}\n\n\treturn function ( prevState, state ) {\n\t\tvar hasPrevActiveLink = prevState && prevState.preview.activeLink;\n\n\t\tif ( !state.preview.enabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( hasPrevActiveLink ) {\n\n\t\t\t// Has the user dwelled on a link immediately after abandoning another\n\t\t\t// (remembering that the ABANDON_END action is delayed by\n\t\t\t// ~10e2 ms).\n\t\t\tif ( prevState.preview.activeLink !== state.preview.activeLink ) {\n\t\t\t\trestoreTitleAttr( prevState.preview.activeLink );\n\t\t\t}\n\t\t}\n\n\t\tif ( state.preview.activeLink ) {\n\t\t\tdestroyTitleAttr( state.preview.activeLink );\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/linkTitle.js\n// module id = ./src/changeListeners/linkTitle.js\n// module chunks = 0","var renderer = require( '../renderer' );\n\n/**\n * Creates an instance of the render change listener.\n *\n * FIXME: Remove hard coupling with renderer, inject it as a parameter\n * * Wire it up in index.js\n * * Fix tests to remove require mocking\n *\n * @param {ext.popups.PreviewBehavior} previewBehavior\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( previewBehavior ) {\n\tvar preview;\n\n\treturn function ( prevState, state ) {\n\t\tif ( state.preview.shouldShow && !preview ) {\n\t\t\tpreview = renderer.render( state.preview.fetchResponse );\n\t\t\tpreview.show(\n\t\t\t\tstate.preview.activeEvent,\n\t\t\t\tpreviewBehavior,\n\t\t\t\tstate.preview.activeToken\n\t\t\t\t);\n\t\t} else if ( !state.preview.shouldShow && preview ) {\n\t\t\tpreview.hide();\n\t\t\tpreview = undefined;\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/render.js\n// module id = ./src/changeListeners/render.js\n// module chunks = 0","/**\n * Creates an instance of the settings change listener.\n *\n * @param {Object} boundActions\n * @param {Object} render function that renders a jQuery el with the settings\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, render ) {\n\tvar settings;\n\n\treturn function ( prevState, state ) {\n\t\tif ( !prevState ) {\n\t\t\t// Nothing to do on initialization\n\t\t\treturn;\n\t\t}\n\n\t\t// Update global modal visibility\n\t\tif (\n\t\t\tprevState.settings.shouldShow === false &&\n\t\t\tstate.settings.shouldShow === true\n\t\t) {\n\t\t\t// Lazily instantiate the settings UI\n\t\t\tif ( !settings ) {\n\t\t\t\tsettings = render( boundActions );\n\t\t\t\tsettings.appendTo( document.body );\n\t\t\t}\n\n\t\t\t// Update the UI settings with the current settings\n\t\t\tsettings.setEnabled( state.preview.enabled );\n\n\t\t\tsettings.show();\n\t\t} else if (\n\t\t\tprevState.settings.shouldShow === true &&\n\t\t\tstate.settings.shouldShow === false\n\t\t) {\n\t\t\tsettings.hide();\n\t\t}\n\n\t\t// Update help visibility\n\t\tif ( prevState.settings.showHelp !== state.settings.showHelp ) {\n\t\t\tsettings.toggleHelp( state.settings.showHelp );\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/settings.js\n// module id = ./src/changeListeners/settings.js\n// module chunks = 0","/**\n * Creates an instance of the statsv change listener.\n *\n * The listener will log events to StatsD via the [the \"StatsD timers and\n * counters\" analytics event protocol][0].\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} boundActions\n * @param {ext.popups.EventTracker} track\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( boundActions, track ) {\n\treturn function ( _, state ) {\n\t\tvar statsv = state.statsv;\n\n\t\tif ( statsv.action ) {\n\t\t\ttrack( statsv.action, statsv.data );\n\n\t\t\tboundActions.statsvLogged();\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/statsv.js\n// module id = ./src/changeListeners/statsv.js\n// module chunks = 0","/**\n * @module changeListeners/syncUserSettings\n */\n\n/**\n * Creates an instance of the user settings sync change listener.\n *\n * This change listener syncs certain parts of the state tree to user\n * settings when they change.\n *\n * Used for:\n *\n * * Enabled state: If the previews are enabled or disabled.\n * * Preview count: When the user dwells on a link for long enough that\n * a preview is shown, then their preview count will be incremented (see\n * `reducers/eventLogging.js`, and is persisted to local storage.\n *\n * @param {ext.popups.UserSettings} userSettings\n * @return {ext.popups.ChangeListener}\n */\nmodule.exports = function ( userSettings ) {\n\n\treturn function ( prevState, state ) {\n\n\t\tsyncIfChanged(\n\t\t\tprevState, state, 'eventLogging', 'previewCount',\n\t\t\tuserSettings.setPreviewCount\n\t\t);\n\t\tsyncIfChanged(\n\t\t\tprevState, state, 'preview', 'enabled',\n\t\t\tuserSettings.setIsEnabled\n\t\t);\n\n\t};\n};\n\n/**\n * Given a state tree, reducer and property, safely return the value of the\n * property if the reducer and property exist\n * @param {Object} state tree\n * @param {String} reducer key to access on the state tree\n * @param {String} prop key to access on the reducer key of the state tree\n * @return {*}\n */\nfunction get( state, reducer, prop ) {\n\treturn state[ reducer ] && state[ reducer ][ prop ];\n}\n\n/**\n * Calls a sync function if the property prop on the property reducer on\n * the state trees has changed value.\n * @param {Object} prevState\n * @param {Object} state\n * @param {String} reducer key to access on the state tree\n * @param {String} prop key to access on the reducer key of the state tree\n * @param {Function} sync function to be called with the newest value if\n * changed\n */\nfunction syncIfChanged( prevState, state, reducer, prop, sync ) {\n\tvar current = get( state, reducer, prop );\n\tif ( prevState && ( get( prevState, reducer, prop ) !== current ) ) {\n\t\tsync( current );\n\t}\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/changeListeners/syncUserSettings.js\n// module id = ./src/changeListeners/syncUserSettings.js\n// module chunks = 0","/**\n * @module constants\n */\n\nmodule.exports = {\n\tTHUMBNAIL_SIZE: 300 * $.bracketedDevicePixelRatio(),\n\tEXTRACT_LENGTH: 525\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/constants.js\n// module id = ./src/constants.js\n// module chunks = 0","/**\n * @module counts\n */\n\n/**\n * Gets the count bucket for the number of edits a user has made.\n *\n * The buckets are defined as part of\n * [the Popups schema](https://meta.wikimedia.org/wiki/Schema:Popups).\n *\n * Extracted from `mw.popups.schemaPopups.getEditCountBucket`.\n *\n * @param {Number} count\n * @return {String}\n */\nexports.getEditCountBucket = function getEditCountBucket( count ) {\n\tvar bucket;\n\n\tif ( count === 0 ) {\n\t\tbucket = '0';\n\t} else if ( count >= 1 && count <= 4 ) {\n\t\tbucket = '1-4';\n\t} else if ( count >= 5 && count <= 99 ) {\n\t\tbucket = '5-99';\n\t} else if ( count >= 100 && count <= 999 ) {\n\t\tbucket = '100-999';\n\t} else if ( count >= 1000 ) {\n\t\tbucket = '1000+';\n\t}\n\n\treturn bucket + ' edits';\n};\n\n/**\n * Gets the count bucket for the number of previews a user has seen.\n *\n * If local storage isn't available - because the user has disabled it\n * or the browser doesn't support it - then then \"unknown\" is returned.\n *\n * The buckets are defined as part of\n * [the Popups schema](https://meta.wikimedia.org/wiki/Schema:Popups).\n *\n * Extracted from `mw.popups.getPreviewCountBucket`.\n *\n * @param {Number} count\n * @return {String}\n */\nexports.getPreviewCountBucket = function getPreviewCountBucket( count ) {\n\tvar bucket;\n\n\tif ( count === -1 ) {\n\t\treturn 'unknown';\n\t}\n\n\tif ( count === 0 ) {\n\t\tbucket = '0';\n\t} else if ( count >= 1 && count <= 4 ) {\n\t\tbucket = '1-4';\n\t} else if ( count >= 5 && count <= 20 ) {\n\t\tbucket = '5-20';\n\t} else if ( count >= 21 ) {\n\t\tbucket = '21+';\n\t}\n\n\treturn bucket + ' previews';\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/counts.js\n// module id = ./src/counts.js\n// module chunks = 0","var $ = jQuery,\n\tmw = window.mediaWiki;\n\n/**\n * Improves the plain text extracts\n * @param {String} plainTextExtract\n * @param {String} title\n * @returns {Array}\n */\nfunction htmlize( plainTextExtract, title ) {\n\tvar extract = plainTextExtract;\n\tif ( plainTextExtract === undefined ) {\n\t\treturn [];\n\t}\n\n\textract = removeParentheticals( extract );\n\textract = removeEllipsis( extract );\n\n\t// After cleaning the extract it may have been blanked\n\tif ( extract.length === 0 ) {\n\t\treturn [];\n\t}\n\n\textract = makeTitleInExtractBold( extract, title );\n\treturn extract;\n}\n\n/**\n * Converts the extract into a list of elements, which correspond to fragments\n * of the extract. Fragments that match the title verbatim are wrapped in a\n * `<b>` element.\n *\n * Using the bolded elements of the extract of the page directly is covered by\n * [T141651](https://phabricator.wikimedia.org/T141651).\n *\n * Extracted from `mw.popups.renderer.article.getProcessedElements`.\n *\n * @param {String} extract\n * @param {String} title\n * @return {Array} A set of HTML Elements\n */\nfunction makeTitleInExtractBold( extract, title ) {\n\tvar regExp, escapedTitle,\n\t\telements = [],\n\t\tboldIdentifier = '<bi-' + Math.random() + '>',\n\t\tsnip = '<snip-' + Math.random() + '>';\n\n\ttitle = title.replace( /\\s+/g, ' ' ).trim(); // Remove extra white spaces\n\tescapedTitle = mw.RegExp.escape( title ); // Escape RegExp elements\n\tregExp = new RegExp( '(^|\\\\s)(' + escapedTitle + ')(|$)', 'i' );\n\n\t// Remove text in parentheses along with the parentheses\n\textract = extract.replace( /\\s+/, ' ' ); // Remove extra white spaces\n\n\t// Make title bold in the extract text\n\t// As the extract is html escaped there can be no such string in it\n\t// Also, the title is escaped of RegExp elements thus can't have \"*\"\n\textract = extract.replace( regExp, '$1' + snip + boldIdentifier + '$2' + snip + '$3' );\n\textract = extract.split( snip );\n\n\t$.each( extract, function ( index, part ) {\n\t\tif ( part.indexOf( boldIdentifier ) === 0 ) {\n\t\t\telements.push( $( '<b>' ).text( part.substring( boldIdentifier.length ) ) );\n\t\t} else {\n\t\t\telements.push( document.createTextNode( part ) );\n\t\t}\n\t} );\n\n\treturn elements;\n}\n\n/**\n * Removes the trailing ellipsis from the extract, if it's there.\n *\n * This function was extracted from\n * `mw.popups.renderer.article#removeEllipsis`.\n *\n * @param {String} extract\n * @return {String}\n */\nfunction removeEllipsis( extract ) {\n\treturn extract.replace( /\\.\\.\\.$/, '' );\n}\n\n/**\n * Removes parentheticals from the extract.\n *\n * If the parenthesis are unbalanced or out of order, then the extract is\n * returned without further processing.\n *\n * This function was extracted from\n * `mw.popups.renderer.article#removeParensFromText`.\n *\n * @param {String} extract\n * @return {String}\n */\nfunction removeParentheticals( extract ) {\n\tvar\n\t\tch,\n\t\tresult = '',\n\t\tlevel = 0,\n\t\ti = 0;\n\n\tfor ( i; i < extract.length; i++ ) {\n\t\tch = extract.charAt( i );\n\n\t\tif ( ch === ')' && level === 0 ) {\n\t\t\treturn extract;\n\t\t}\n\t\tif ( ch === '(' ) {\n\t\t\tlevel++;\n\t\t\tcontinue;\n\t\t} else if ( ch === ')' ) {\n\t\t\tlevel--;\n\t\t\tcontinue;\n\t\t}\n\t\tif ( level === 0 ) {\n\t\t\t// Remove leading spaces before brackets\n\t\t\tif ( ch === ' ' && extract.charAt( i + 1 ) === '(' ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tresult += ch;\n\t\t}\n\t}\n\n\treturn ( level === 0 ) ? result : extract;\n}\n\nmodule.exports = {\n\thtmlize: htmlize\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/formatter.js\n// module id = ./src/formatter.js\n// module chunks = 0","/**\n * @module gateway/mediawiki\n */\n\n/**\n * @interface MediaWikiGateway\n * @extends Gateway\n *\n * @global\n */\n\n// Public and private cache lifetime (5 minutes)\n//\n// FIXME: Move this to src/constants.js.\nvar CACHE_LIFETIME = 300,\n\tcreateModel = require( '../preview/model' ).createModel,\n\tplainTextHTMLizer = require( '../formatter' ).htmlize,\n\t$ = jQuery;\n\n/**\n * Creates an instance of the MediaWiki API gateway.\n *\n * @param {mw.Api} api\n * @param {Object} config Configuration that affects the major behavior of the\n * gateway.\n * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of\n * the thumbnail.\n * @param {Number} config.EXTRACT_LENGTH The maximum length, in characters,\n * of the extract.\n * @returns {MediaWikiGateway}\n */\nmodule.exports = function createMediaWikiApiGateway( api, config ) {\n\n\t/**\n\t * Fetches page data from the API.\n\t *\n\t * @function\n\t * @name MediaWikiGateway#fetch\n\t * @param {String} title\n\t * @return {jQuery.Promise}\n\t */\n\tfunction fetch( title ) {\n\t\treturn api.get( {\n\t\t\taction: 'query',\n\t\t\tprop: 'info|extracts|pageimages|revisions|info',\n\t\t\tformatversion: 2,\n\t\t\tredirects: true,\n\t\t\texintro: true,\n\t\t\texchars: config.EXTRACT_LENGTH,\n\n\t\t\t// There is an added geometric limit on .mwe-popups-extract\n\t\t\t// so that text does not overflow from the card.\n\t\t\texplaintext: true,\n\n\t\t\tpiprop: 'thumbnail',\n\t\t\tpithumbsize: config.THUMBNAIL_SIZE,\n\t\t\tpilicense: 'any',\n\t\t\trvprop: 'timestamp',\n\t\t\tinprop: 'url',\n\t\t\ttitles: title,\n\t\t\tsmaxage: CACHE_LIFETIME,\n\t\t\tmaxage: CACHE_LIFETIME,\n\t\t\tuselang: 'content'\n\t\t}, {\n\t\t\theaders: {\n\t\t\t\t'X-Analytics': 'preview=1'\n\t\t\t}\n\t\t} );\n\t}\n\n\tfunction getPageSummary( title ) {\n\t\treturn fetch( title )\n\t\t\t.then( extractPageFromResponse )\n\t\t\t.then( htmlize )\n\t\t\t.then( convertPageToModel );\n\t}\n\n\treturn {\n\t\tfetch: fetch,\n\t\textractPageFromResponse: extractPageFromResponse,\n\t\tconvertPageToModel: convertPageToModel,\n\t\tgetPageSummary: getPageSummary,\n\t\thtmlize: htmlize\n\t};\n};\n\n/**\n * Extracts page data from the API response.\n *\n * @function\n * @name MediaWikiGateway#extractPageFromResponse\n * @param {Object} data The response\n * @throws {Error} If the response is empty or doesn't contain data about the\n * page\n * @returns {Object}\n */\nfunction extractPageFromResponse( data ) {\n\tif (\n\t\tdata.query &&\n\t\tdata.query.pages &&\n\t\tdata.query.pages.length\n\t) {\n\t\treturn data.query.pages[ 0 ];\n\t}\n\n\tthrow new Error( 'API response `query.pages` is empty.' );\n}\n\n/**\n * HTMLize plain text response\n *\n * @function\n * @name MediaWikiGateway#htmlize\n * @param {Object} data The response\n * @returns {Object}\n */\nfunction htmlize( data ) {\n\tvar result = $.extend( {}, data );\n\tresult.extract = plainTextHTMLizer( data.extract, data.title );\n\treturn result;\n}\n\n/**\n * Converts the API response to a preview model.\n *\n * @function\n * @name MediaWikiGateway#convertPageToModel\n * @param {Object} page\n * @returns {PreviewModel}\n */\nfunction convertPageToModel( page ) {\n\treturn createModel(\n\t\tpage.title,\n\t\tpage.canonicalurl,\n\t\tpage.pagelanguagehtmlcode,\n\t\tpage.pagelanguagedir,\n\t\tpage.extract,\n\t\tpage.thumbnail\n\t);\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/gateway/mediawiki.js\n// module id = ./src/gateway/mediawiki.js\n// module chunks = 0","/**\n * @module gateway/rest\n */\n\nvar RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/',\n\tRESTBASE_PROFILE = 'https://www.mediawiki.org/wiki/Specs/Summary/1.2.0',\n\tcreateModel = require( '../preview/model' ).createModel,\n\tplainTextHTMLizer = require( '../formatter' ).htmlize,\n\tmw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * @interface RESTBaseGateway\n * @extends Gateway\n *\n * @global\n */\n\n/**\n * Creates an instance of the RESTBase gateway.\n *\n * This gateway differs from the {@link MediaWikiGateway MediaWiki gateway} in\n * that it fetches page data from [the RESTBase page summary endpoint][0].\n *\n * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title\n *\n * @param {Function} ajax A function with the same signature as `jQuery.ajax`\n * @param {Object} config Configuration that affects the major behavior of the\n * gateway.\n * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of\n * the thumbnail.\n * @returns {RESTBaseGateway}\n */\nmodule.exports = function createRESTBaseGateway( ajax, config ) {\n\n\t/**\n\t * Fetches page data from [the RESTBase page summary endpoint][0].\n\t *\n\t * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title\n\t *\n\t * @function\n\t * @name MediaWikiGateway#fetch\n\t * @param {String} title\n\t * @return {jQuery.Promise}\n\t */\n\tfunction fetch( title ) {\n\t\treturn ajax( {\n\t\t\turl: RESTBASE_ENDPOINT + encodeURIComponent( title ),\n\t\t\theaders: {\n\t\t\t\tAccept: 'application/json; charset=utf-8; ' +\n\t\t\t\t\t'profile=\"' + RESTBASE_PROFILE + '\"'\n\t\t\t}\n\t\t} );\n\t}\n\n\tfunction getPageSummary( title ) {\n\t\tvar result = $.Deferred();\n\n\t\tfetch( title )\n\t\t\t.then(\n\t\t\t\tfunction ( page ) {\n\t\t\t\t\tresult.resolve(\n\t\t\t\t\t\tconvertPageToModel( page, config.THUMBNAIL_SIZE ) );\n\t\t\t\t},\n\t\t\t\tfunction ( jqXHR ) {\n\t\t\t\t\tif ( jqXHR.status === 404 ) {\n\t\t\t\t\t\tresult.resolve(\n\t\t\t\t\t\t\tconvertPageToModel( {\n\t\t\t\t\t\t\t\ttitle: title,\n\t\t\t\t\t\t\t\tlang: '',\n\t\t\t\t\t\t\t\tdir: '',\n\t\t\t\t\t\t\t\textract: ''\n\t\t\t\t\t\t\t}, 0 )\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult.reject();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\n\t\treturn result.promise();\n\t}\n\n\treturn {\n\t\tfetch: fetch,\n\t\tconvertPageToModel: convertPageToModel,\n\t\tgetPageSummary: getPageSummary\n\t};\n};\n\n/**\n * Resizes the thumbnail to the requested width, preserving its aspect ratio.\n *\n * The requested width is limited to that of the original image unless the image\n * is an SVG, which can be scaled infinitely.\n *\n * This function is only intended to mangle the pretty thumbnail URLs used on\n * Wikimedia Commons. Once [an official thumb API](https://phabricator.wikimedia.org/T66214)\n * is fully specified and implemented, this function can be made more general.\n *\n * @param {Object} thumbnail The thumbnail image\n * @param {Object} original The original image\n * @param {Number} thumbSize The requested size\n * @returns {Object}\n */\nfunction generateThumbnailData( thumbnail, original, thumbSize ) {\n\tvar parts = thumbnail.source.split( '/' ),\n\t\tlastPart = parts[ parts.length - 1 ],\n\t\tfilename,\n\t\twidth,\n\t\theight;\n\n\t// The last part, the thumbnail's full filename, is in the following form:\n\t// ${width}px-${filename}.${extension}. Splitting the thumbnail's filename\n\t// makes this function resilient to the thumbnail not having the same\n\t// extension as the original image, which is definitely the case for SVG's\n\t// where the thumbnail's extension is .svg.png.\n\tfilename = lastPart.substr( lastPart.indexOf( 'px-' ) + 3 );\n\n\t\t// Scale the thumbnail's largest dimension.\n\tif ( thumbnail.width > thumbnail.height ) {\n\t\twidth = thumbSize;\n\t\theight = Math.floor( ( thumbSize / thumbnail.width ) * thumbnail.height );\n\t} else {\n\t\twidth = Math.floor( ( thumbSize / thumbnail.height ) * thumbnail.width );\n\t\theight = thumbSize;\n\t}\n\n\t// If the image isn't an SVG, then it shouldn't be scaled past its original\n\t// dimensions.\n\tif ( width >= original.width && filename.indexOf( '.svg' ) === -1 ) {\n\t\treturn original;\n\t}\n\n\tparts[ parts.length - 1 ] = width + 'px-' + filename;\n\n\treturn {\n\t\tsource: parts.join( '/' ),\n\t\twidth: width,\n\t\theight: height\n\t};\n}\n\n/**\n * Converts the API response to a preview model.\n *\n * @function\n * @name RESTBaseGateway#convertPageToModel\n * @param {Object} page\n * @param {Number} thumbSize\n * @returns {PreviewModel}\n */\nfunction convertPageToModel( page, thumbSize ) {\n\treturn createModel(\n\t\tpage.title,\n\t\tnew mw.Title( page.title ).getUrl(),\n\t\tpage.lang,\n\t\tpage.dir,\n\t\tplainTextHTMLizer( page.extract, page.title ),\n\t\tpage.thumbnail ? generateThumbnailData( page.thumbnail, page.originalimage, thumbSize ) : undefined\n\t);\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/gateway/rest.js\n// module id = ./src/gateway/rest.js\n// module chunks = 0","/**\n * @module popups\n */\n\nvar mw = mediaWiki,\n\t$ = jQuery,\n\tRedux = require( 'redux' ),\n\tReduxThunk = require( 'redux-thunk' ),\n\tconstants = require( './constants' ),\n\n\tcreateRESTBaseGateway = require( './gateway/rest' ),\n\tcreateMediaWikiApiGateway = require( './gateway/mediawiki' ),\n\tcreateUserSettings = require( './userSettings' ),\n\tcreatePreviewBehavior = require( './previewBehavior' ),\n\tcreateSchema = require( './schema' ),\n\tcreateSettingsDialogRenderer = require( './settingsDialog' ),\n\tregisterChangeListener = require( './changeListener' ),\n\tcreateIsEnabled = require( './isEnabled' ),\n\ttitle = require( './title' ),\n\trenderer = require( './renderer' ),\n\tstatsvInstrumentation = require( './statsvInstrumentation' ),\n\n\tchangeListeners = require( './changeListeners' ),\n\tactions = require( './actions' ),\n\treducers = require( './reducers' ),\n\n\tBLACKLISTED_LINKS = [\n\t\t'.extiw',\n\t\t'.image',\n\t\t'.new',\n\t\t'.internal',\n\t\t'.external',\n\t\t'.oo-ui-buttonedElement-button',\n\t\t'.cancelLink a'\n\t];\n\n/**\n * @typedef {Function} ext.popups.EventTracker\n *\n * An analytics event tracker like `mw.track`.\n */\n\n/**\n * Creates a gateway with sensible values for the dependencies.\n *\n * @param {mw.Map} config\n * @return {ext.popups.Gateway}\n */\nfunction createGateway( config ) {\n\tif ( config.get( 'wgPopupsAPIUseRESTBase' ) ) {\n\t\treturn createRESTBaseGateway( $.ajax, constants );\n\t}\n\treturn createMediaWikiApiGateway( new mw.Api(), constants );\n}\n\n/**\n * Gets the appropriate analytics event tracker for logging metrics to StatsD\n * via the [the \"StatsD timers and counters\" analytics event protocol][0].\n *\n * If logging metrics to StatsD is enabled for the user, then the appriopriate\n * function is `mw.track`; otherwise it's `$.noop`.\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/master/modules/ext.wikimediaEvents.statsd.js\n *\n * @param {Object} user\n * @param {Object} config\n * @param {Object} experiments\n * @return {ext.popups.EventTracker}\n */\nfunction getStatsvTracker( user, config, experiments ) {\n\treturn statsvInstrumentation.isEnabled( user, config, experiments ) ? mw.track : $.noop;\n}\n\n/**\n * Subscribes the registered change listeners to the\n * [store](http://redux.js.org/docs/api/Store.html#store).\n *\n * @param {Redux.Store} store\n * @param {Object} actions\n * @param {ext.popups.UserSettings} userSettings\n * @param {Function} settingsDialog\n * @param {ext.popups.PreviewBehavior} previewBehavior\n * @param {ext.popups.EventTracker} statsvTracker\n */\nfunction registerChangeListeners( store, actions, userSettings, settingsDialog, previewBehavior, statsvTracker ) {\n\tregisterChangeListener( store, changeListeners.footerLink( actions ) );\n\tregisterChangeListener( store, changeListeners.linkTitle() );\n\tregisterChangeListener( store, changeListeners.render( previewBehavior ) );\n\tregisterChangeListener( store, changeListeners.statsv( actions, statsvTracker ) );\n\tregisterChangeListener( store, changeListeners.syncUserSettings( userSettings ) );\n\tregisterChangeListener( store, changeListeners.settings( actions, settingsDialog ) );\n}\n\n/*\n * Initialize the application by:\n * 1. Creating the state store\n * 2. Binding the actions to such store\n * 3. Trigger the boot action to bootstrap the system\n * 4. When the page content is ready:\n * - Process the eligible links for page previews\n * - Initialize the renderer\n * - Bind hover and click events to the eligible links to trigger actions\n */\nmw.requestIdleCallback( function () {\n\tvar compose = Redux.compose,\n\t\tstore,\n\t\tboundActions,\n\n\t\t// So-called \"services\".\n\t\tgenerateToken = mw.user.generateRandomSessionId,\n\t\tgateway = createGateway( mw.config ),\n\t\tuserSettings,\n\t\tsettingsDialog,\n\t\tstatsvTracker,\n\t\tisEnabled,\n\t\tschema,\n\t\tpreviewBehavior;\n\n\tuserSettings = createUserSettings( mw.storage );\n\tsettingsDialog = createSettingsDialogRenderer();\n\tstatsvTracker = getStatsvTracker( mw.user, mw.config, mw.experiments );\n\n\tisEnabled = createIsEnabled( mw.user, userSettings, mw.config, mw.experiments );\n\n\t// If debug mode is enabled, then enable Redux DevTools.\n\tif ( mw.config.get( 'debug' ) === true ) {\n\t\t// eslint-disable-next-line no-underscore-dangle\n\t\tcompose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\t}\n\n\tstore = Redux.createStore(\n\t\tRedux.combineReducers( reducers ),\n\t\tcompose( Redux.applyMiddleware(\n\t\t\tReduxThunk.default\n\t\t) )\n\t);\n\tboundActions = Redux.bindActionCreators( actions, store.dispatch );\n\n\tpreviewBehavior = createPreviewBehavior( mw.config, mw.user, boundActions );\n\n\tregisterChangeListeners(\n\t\tstore, boundActions, userSettings, settingsDialog,\n\t\tpreviewBehavior, statsvTracker\n\t);\n\n\t// Load EventLogging schema if possible...\n\tmw.loader.using( 'ext.eventLogging.Schema' ).done( function () {\n\t\tschema = createSchema( mw.config, window );\n\t\tregisterChangeListener( store, changeListeners.eventLogging( boundActions, schema, statsvTracker ) );\n\t} );\n\n\tboundActions.boot(\n\t\tisEnabled,\n\t\tmw.user,\n\t\tuserSettings,\n\t\tgenerateToken,\n\t\tmw.config\n\t);\n\n\tmw.hook( 'wikipage.content' ).add( function ( $container ) {\n\t\tvar invalidLinksSelector = BLACKLISTED_LINKS.join( ', ' ),\n\t\t\tvalidLinkSelector = 'a[href][title]:not(' + invalidLinksSelector + ')';\n\n\t\trenderer.init();\n\n\t\t$container\n\t\t\t.on( 'mouseover keyup', validLinkSelector, function ( event ) {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.linkDwell( mwTitle, this, event, gateway, generateToken );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.on( 'mouseout blur', validLinkSelector, function () {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.abandon( this );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.on( 'click', validLinkSelector, function () {\n\t\t\t\tvar mwTitle = title.fromElement( this, mw.config );\n\n\t\t\t\tif ( mwTitle ) {\n\t\t\t\t\tboundActions.linkClick( this );\n\t\t\t\t}\n\t\t\t} );\n\n\t} );\n} );\n\nwindow.Redux = Redux;\nwindow.ReduxThunk = ReduxThunk;\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/index.js\n// module id = ./src/index.js\n// module chunks = 0","/**\n * @module isEnabled\n */\n\n/**\n * Given the global state of the application, creates a function that gets\n * whether or not the user should have Page Previews enabled.\n *\n * Page Previews is disabled when the Navigation Popups gadget is enabled.\n *\n * If Page Previews is configured as a beta feature (see\n * `$wgPopupsBetaFeature`), the user must be logged in and have enabled the\n * beta feature in order to see previews. Logged out users won't be able\n * to see the feature.\n *\n * If Page Previews is configured as a user preference, then the user must\n * either be logged in and have enabled the preference or be logged out and have\n * not disabled previews via the settings modal. Logged out users who have not\n * disabled or enabled the previews via the settings modal are sampled at the\n * sampling rate defined by `wgPopupsAnonsEnabledSamplingRate`.\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {Object} userSettings An object returned by `userSettings.js`\n * @param {mw.Map} config\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n *\n * @return {Boolean}\n */\nmodule.exports = function ( user, userSettings, config, experiments ) {\n\tif ( config.get( 'wgPopupsConflictsWithNavPopupGadget' ) ) {\n\t\treturn false;\n\t}\n\n\tif ( !user.isAnon() ) {\n\t\treturn config.get( 'wgPopupsShouldSendModuleToUser' );\n\t}\n\n\tif ( config.get( 'wgPopupsBetaFeature' ) ) {\n\t\treturn false;\n\t}\n\n\tif ( !userSettings.hasIsEnabled() ) {\n\t\treturn isUserSampled( user, config, experiments );\n\t}\n\n\treturn userSettings.getIsEnabled();\n};\n\n/**\n * Is the user sampled based on a sampling rate?\n *\n * The sampling rate is taken from `wgPopupsAnonsEnabledSamplingRate` and\n * defaults to 0.9.\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {mw.Map} config\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n *\n * @return {Boolean}\n */\nfunction isUserSampled( user, config, experiments ) {\n\tvar samplingRate = config.get( 'wgPopupsAnonsEnabledSamplingRate', 0.9 ),\n\t\tbucket = experiments.getBucket( {\n\t\t\tname: 'ext.Popups.visibility',\n\t\t\tenabled: true,\n\t\t\tbuckets: {\n\t\t\t\tcontrol: 1 - samplingRate,\n\t\t\t\tA: samplingRate\n\t\t\t}\n\t\t}, user.sessionId() );\n\n\treturn bucket === 'A';\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/isEnabled.js\n// module id = ./src/isEnabled.js\n// module chunks = 0","/**\n * @module preview/model\n */\n\nvar TYPE_GENERIC = 'generic',\n\tTYPE_PAGE = 'page';\n\n/**\n * @constant {String}\n */\nexports.TYPE_GENERIC = TYPE_GENERIC;\n\n/**\n * @constant {String}\n */\nexports.TYPE_PAGE = TYPE_PAGE;\n\n/**\n * @typedef {Object} PreviewModel\n * @property {String} title\n * @property {String} url The canonical URL of the page being previewed\n * @property {String} languageCode\n * @property {String} languageDirection Either \"ltr\" or \"rtl\"\n * @property {?Array} extract `undefined` if the extract isn't\n * viable, e.g. if it's empty after having ellipsis and parentheticals\n * removed\n * @property {String} type Either \"EXTRACT\" or \"GENERIC\"\n * @property {?Object} thumbnail\n *\n * @global\n */\n\n/**\n * Creates a preview model.\n *\n * @param {String} title\n * @param {String} url The canonical URL of the page being previewed\n * @param {String} languageCode\n * @param {String} languageDirection Either \"ltr\" or \"rtl\"\n * @param {?Array} extract\n * @param {?Object} thumbnail\n * @return {PreviewModel}\n */\nexports.createModel = function createModel(\n\ttitle,\n\turl,\n\tlanguageCode,\n\tlanguageDirection,\n\textract,\n\tthumbnail\n) {\n\tvar processedExtract = processExtract( extract );\n\n\treturn {\n\t\ttitle: title,\n\t\turl: url,\n\t\tlanguageCode: languageCode,\n\t\tlanguageDirection: languageDirection,\n\t\textract: processedExtract,\n\t\ttype: processedExtract === undefined ? TYPE_GENERIC : TYPE_PAGE,\n\t\tthumbnail: thumbnail\n\t};\n};\n\n/**\n * Processes the extract returned by the TextExtracts MediaWiki API query\n * module.\n *\n * If the extract is `undefined`, `null`, or empty, then `undefined` is\n * returned.\n *\n * @param {?Array} extract\n * @return {?String}\n */\nfunction processExtract( extract ) {\n\tif ( extract === undefined || extract.length === 0 ) {\n\t\treturn undefined;\n\t}\n\treturn extract;\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/preview/model.js\n// module id = ./src/preview/model.js\n// module chunks = 0","/**\n * @module previewBehaviour\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * A collection of event handlers specific to how the user interacts with all\n * previews. The event handlers are are agnostic to how/when they are bound\n * //but not to what they are bound//, i.e. the showSettings event handler is\n * written to be bound to either an `<a>` or `<button>` element.\n *\n * @typedef {Object} ext.popups.PreviewBehavior\n * @property {String} settingsUrl\n * @property {Function} showSettings\n * @property {Function} previewDwell\n * @property {Function} previewAbandon\n * @property {Function} click handler for the entire preview\n */\n\n/**\n * Creates an instance of `ext.popups.PreviewBehavior`.\n *\n * If the user is logged out, then clicking the cog should show the settings\n * modal.\n *\n * If the user is logged in, then clicking the cog should send them to the\n * Special:Preferences page with the \"Beta features\" tab open if Page Previews\n * is enabled as a beta feature, or the \"Appearance\" tab otherwise.\n *\n * @param {mw.Map} config\n * @param {mw.User} user\n * @param {Object} actions The action creators bound to the Redux store\n * @return {ext.popups.PreviewBehavior}\n */\nmodule.exports = function ( config, user, actions ) {\n\tvar isBetaFeature = config.get( 'wgPopupsBetaFeature' ),\n\t\trawTitle,\n\t\tsettingsUrl,\n\t\tshowSettings = $.noop;\n\n\tif ( user.isAnon() ) {\n\t\tshowSettings = function ( event ) {\n\t\t\tevent.preventDefault();\n\n\t\t\tactions.showSettings();\n\t\t};\n\t} else {\n\t\trawTitle = 'Special:Preferences#mw-prefsection-';\n\t\trawTitle += isBetaFeature ? 'betafeatures' : 'rendering';\n\n\t\tsettingsUrl = mw.Title.newFromText( rawTitle )\n\t\t\t.getUrl();\n\t}\n\n\treturn {\n\t\tsettingsUrl: settingsUrl,\n\t\tshowSettings: showSettings,\n\t\tpreviewDwell: actions.previewDwell,\n\t\tpreviewAbandon: actions.abandon,\n\t\tpreviewShow: actions.previewShow,\n\t\tclick: actions.linkClick\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/previewBehavior.js\n// module id = ./src/previewBehavior.js\n// module chunks = 0","/**\n * @module reducers/eventLogging\n */\n\nvar actionTypes = require( './../actionTypes' ),\n\tnextState = require( './nextState' ),\n\tcounts = require( './../counts' );\n\n/**\n * Initialize the data that's shared between all events.\n *\n * @param {Object} bootAction\n * @return {Object}\n */\nfunction getBaseData( bootAction ) {\n\tvar result = {\n\t\tpageTitleSource: bootAction.page.title,\n\t\tnamespaceIdSource: bootAction.page.namespaceID,\n\t\tpageIdSource: bootAction.page.id,\n\t\tisAnon: bootAction.user.isAnon,\n\t\tpopupEnabled: bootAction.isEnabled,\n\t\tpageToken: bootAction.pageToken,\n\t\tsessionToken: bootAction.sessionToken,\n\t\tpreviewCountBucket: counts.getPreviewCountBucket( bootAction.user.previewCount ),\n\t\thovercardsSuppressedByGadget: bootAction.isNavPopupsEnabled\n\t};\n\n\tif ( !bootAction.user.isAnon ) {\n\t\tresult.editCountBucket = counts.getEditCountBucket( bootAction.user.editCount );\n\t}\n\n\treturn result;\n}\n\n/**\n * Takes data specific to the action and adds the following properties:\n *\n * * `linkInteractionToken`;\n * * `pageTitleHover` and `namespaceIdHover`; and\n * * `previewType` and `perceivedWait`, if a preview has been shown.\n *\n * @param {Object} interaction\n * @param {Object} actionData Data specific to the action, e.g. see\n * {@link module:reducers/eventLogging~createClosingEvent `createClosingEvent`}\n * @return {Object}\n */\nfunction createEvent( interaction, actionData ) {\n\tactionData.linkInteractionToken = interaction.token;\n\tactionData.pageTitleHover = interaction.title;\n\tactionData.namespaceIdHover = interaction.namespaceID;\n\n\t// Has the preview been shown?\n\tif ( interaction.timeToPreviewShow !== undefined ) {\n\t\tactionData.previewType = interaction.previewType;\n\t\tactionData.perceivedWait = interaction.timeToPreviewShow;\n\t}\n\n\treturn actionData;\n}\n\n/**\n * Creates an event that, when mixed into the base data (see\n * {@link module:reducers/eventLogging~getBaseData `getBaseData`}), represents\n * the user abandoning a link or preview.\n *\n * Since the event should be logged when the user has either abandoned a link or\n * dwelled on a different link, we refer to these events as \"closing\" events as\n * the link interaction has finished and a new one will be created later.\n *\n * If the link interaction is finalized, then no closing event is created.\n *\n * @param {Object} interaction\n * @return {Object|undefined}\n */\nfunction createClosingEvent( interaction ) {\n\tvar actionData = {\n\t\ttotalInteractionTime: Math.round( interaction.finished - interaction.started )\n\t};\n\n\tif ( interaction.finalized ) {\n\t\treturn undefined;\n\t}\n\n\t// Has the preview been shown? If so, then, in the context of the\n\t// instrumentation, then the preview has been dismissed by the user\n\t// rather than the user has abandoned the link.\n\tactionData.action = interaction.timeToPreviewShow ? 'dismissed' : 'dwelledButAbandoned';\n\n\treturn createEvent( interaction, actionData );\n}\n\n/**\n * Reducer for actions that may result in an event being logged with [the\n * Popups schema][0] via EventLogging.\n *\n * The complexity of this reducer reflects the complexity of [the schema][0],\n * which is compounded by the introduction of two delays introduced by the\n * system to provide reasonable performance and a consistent UX.\n *\n * The reducer must:\n *\n * * Accumulate the state required to log events. This state is\n * referred to as \"the interaction state\" or \"the interaction\";\n * * Enforce the invariant that one event is logged per interaction;\n * * Defend against delayed actions being dispatched; and, as a direct\n * consequence\n * * Handle transitioning from one interaction to another at the same time.\n *\n * Furthermore, we distinguish between \"finalizing\" and \"closing\" the current\n * interaction state. Since only one event should be logged per link\n * interaction, we say that the interaction state is *finalized* when an event\n * has been logged and is *closed* when a new interaction should be created.\n * In practice, the interaction state is only finalized when the user clicks a\n * link or a preview.\n *\n * [0]: https://meta.wikimedia.org/wiki/Schema:Popups\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} The state resulting from reducing the action with the\n * current state\n */\nmodule.exports = function ( state, action ) {\n\tvar nextCount, newState,\n\t\tactionTypesWithTokens = [\n\t\t\tactionTypes.FETCH_COMPLETE,\n\t\t\tactionTypes.ABANDON_END,\n\t\t\tactionTypes.PREVIEW_SHOW\n\t\t];\n\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tpreviewCount: undefined,\n\t\t\tbaseData: {},\n\t\t\tinteraction: undefined,\n\t\t\tevent: undefined\n\t\t};\n\t}\n\n\t// Was the action delayed? Then it requires a token to be reduced. Enforce\n\t// this here to avoid repetion and reduce nesting below.\n\tif (\n\t\tactionTypesWithTokens.indexOf( action.type ) !== -1 &&\n\t\t( !state.interaction || action.token !== state.interaction.token )\n\t) {\n\t\treturn state;\n\t}\n\n\t// If there is no interaction ongoing, ignore all actions except for:\n\t// * Application initialization\n\t// * New link dwells (which start a new interaction)\n\t// * Clearing queued events\n\t//\n\t// For example, after ctrl+clicking a link or preview, any other actions\n\t// until the new interaction should be ignored.\n\tif (\n\t\t!state.interaction &&\n\t\taction.type !== actionTypes.BOOT &&\n\t\taction.type !== actionTypes.LINK_DWELL &&\n\t\taction.type !== actionTypes.EVENT_LOGGED\n\t) {\n\t\treturn state;\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tpreviewCount: action.user.previewCount,\n\t\t\t\tbaseData: getBaseData( action ),\n\t\t\t\tevent: {\n\t\t\t\t\taction: 'pageLoaded'\n\t\t\t\t}\n\t\t\t} );\n\n\t\tcase actionTypes.EVENT_LOGGED:\n\t\t\tnewState = nextState( state, {\n\t\t\t\tevent: undefined\n\t\t\t} );\n\n\t\t\t// If an event was logged with an interaction token, and it is still\n\t\t\t// the current interaction, finish the interaction since logging is\n\t\t\t// the exit point of the state machine and an interaction should never\n\t\t\t// be logged twice.\n\t\t\tif (\n\t\t\t\taction.event.linkInteractionToken &&\n\t\t\t\tstate.interaction &&\n\t\t\t\t( action.event.linkInteractionToken === state.interaction.token )\n\t\t\t) {\n\t\t\t\tnewState.interaction = undefined;\n\t\t\t}\n\n\t\t\treturn newState;\n\n\t\tcase actionTypes.FETCH_COMPLETE:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tpreviewType: action.result.type\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_SHOW:\n\t\t\tnextCount = state.previewCount + 1;\n\n\t\t\treturn nextState( state, {\n\t\t\t\tpreviewCount: nextCount,\n\t\t\t\tbaseData: nextState( state.baseData, {\n\t\t\t\t\tpreviewCountBucket: counts.getPreviewCountBucket( nextCount )\n\t\t\t\t} ),\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\ttimeToPreviewShow: Math.round( action.timestamp - state.interaction.started )\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_DWELL:\n\n\t\t\t// Not a new interaction?\n\t\t\tif ( state.interaction && action.el === state.interaction.link ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\t\tisUserDwelling: true\n\t\t\t\t\t} )\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn nextState( state, {\n\n\t\t\t\t// TODO: Extract this object into a module that can be shared between\n\t\t\t\t// this and the preview reducer.\n\t\t\t\tinteraction: {\n\t\t\t\t\tlink: action.el,\n\t\t\t\t\ttitle: action.title,\n\t\t\t\t\tnamespaceID: action.namespaceID,\n\t\t\t\t\ttoken: action.token,\n\t\t\t\t\tstarted: action.timestamp,\n\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t},\n\n\t\t\t\t// Was the user interacting with another link? If so, then log the\n\t\t\t\t// abandoned event.\n\t\t\t\tevent: state.interaction ? createClosingEvent( state.interaction ) : undefined\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_CLICK:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tfinalized: true\n\t\t\t\t} ),\n\t\t\t\tevent: createEvent( state.interaction, {\n\t\t\t\t\taction: 'opened',\n\t\t\t\t\ttotalInteractionTime: Math.round( action.timestamp - state.interaction.started )\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tinteraction: nextState( state.interaction, {\n\t\t\t\t\tfinished: action.timestamp,\n\n\t\t\t\t\tisUserDwelling: false\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_END:\n\t\t\tif ( !state.interaction.isUserDwelling ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tinteraction: undefined,\n\t\t\t\t\tevent: createClosingEvent( state.interaction )\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn state;\n\n\t\tcase actionTypes.SETTINGS_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\tevent: createEvent( state.interaction, {\n\t\t\t\t\taction: 'tapped settings cog'\n\t\t\t\t} )\n\t\t\t} );\n\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/eventLogging.js\n// module id = ./src/reducers/eventLogging.js\n// module chunks = 0","module.exports = {\n\teventLogging: require( './eventLogging' ),\n\tpreview: require( './preview' ),\n\tsettings: require( './settings' ),\n\tstatsv: require( './statsv' )\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/index.js\n// module id = ./src/reducers/index.js\n// module chunks = 0","/**\n * Creates the next state tree from the current state tree and some updates.\n *\n * N.B. OO.copy doesn't copy Element instances, whereas $.extend does.\n * However, OO.copy does copy properties whose values are undefined or null,\n * whereas $.extend doesn't. Since the state tree contains an Element instance\n * - the preview.activeLink property - and we want to copy undefined/null into\n * the state we need to manually iterate over updates and check with\n * hasOwnProperty to copy over to the new state.\n *\n * In [change listeners](/doc/change_listeners.md), for example, we talk about\n * the previous state and the current state (the `prevState` and `state`\n * parameters, respectively). Since\n * [reducers](http://redux.js.org/docs/basics/Reducers.html) take the current\n * state and an action and make updates, \"next state\" seems appropriate.\n *\n * @param {Object} state\n * @param {Object} updates\n * @return {Object}\n */\nmodule.exports = function ( state, updates ) {\n\tvar result = {},\n\t\tkey;\n\n\tfor ( key in state ) {\n\t\tif ( state.hasOwnProperty( key ) && !updates.hasOwnProperty( key ) ) {\n\t\t\tresult[ key ] = state[ key ];\n\t\t}\n\t}\n\n\tfor ( key in updates ) {\n\t\tif ( updates.hasOwnProperty( key ) ) {\n\t\t\tresult[ key ] = updates[ key ];\n\t\t}\n\t}\n\n\treturn result;\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/nextState.js\n// module id = ./src/reducers/nextState.js\n// module chunks = 0","var actionTypes = require( './../actionTypes' ),\n\tnextState = require( './nextState' );\n\n/**\n * Reducer for actions that modify the state of the preview model\n *\n * @param {Object} state before action\n * @param {Object} action Redux action that modified state.\n * Must have `type` property.\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tenabled: undefined,\n\t\t\tactiveLink: undefined,\n\t\t\tactiveEvent: undefined,\n\t\t\tactiveToken: '',\n\t\t\tshouldShow: false,\n\t\t\tisUserDwelling: false\n\t\t};\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tenabled: action.isEnabled\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_CHANGE:\n\t\t\treturn nextState( state, {\n\t\t\t\tenabled: action.enabled\n\t\t\t} );\n\t\tcase actionTypes.LINK_DWELL:\n\t\t\t// New interaction\n\t\t\tif ( action.el !== state.activeLink ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tactiveLink: action.el,\n\t\t\t\t\tactiveEvent: action.event,\n\t\t\t\t\tactiveToken: action.token,\n\n\t\t\t\t\t// When the user dwells on a link with their keyboard, a preview is\n\t\t\t\t\t// renderered, and then dwells on another link, the ABANDON_END\n\t\t\t\t\t// action will be ignored.\n\t\t\t\t\t//\n\t\t\t\t\t// Ensure that all the preview is hidden.\n\t\t\t\t\tshouldShow: false,\n\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\t// Dwelling back into the same link\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tisUserDwelling: true\n\t\t\t\t} );\n\t\t\t}\n\n\t\tcase actionTypes.ABANDON_END:\n\t\t\tif ( action.token === state.activeToken && !state.isUserDwelling ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tactiveLink: undefined,\n\t\t\t\t\tactiveToken: undefined,\n\t\t\t\t\tactiveEvent: undefined,\n\t\t\t\t\tfetchResponse: undefined,\n\t\t\t\t\tshouldShow: false\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn state;\n\n\t\tcase actionTypes.PREVIEW_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tisUserDwelling: true\n\t\t\t} );\n\n\t\tcase actionTypes.ABANDON_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tisUserDwelling: false\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tfetchResponse: undefined\n\t\t\t} );\n\t\tcase actionTypes.FETCH_COMPLETE:\n\t\t\tif ( action.token === state.activeToken ) {\n\t\t\t\treturn nextState( state, {\n\t\t\t\t\tfetchResponse: action.result,\n\t\t\t\t\tshouldShow: state.isUserDwelling\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t/* falls through */\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/preview.js\n// module id = ./src/reducers/preview.js\n// module chunks = 0","var actionTypes = require( './../actionTypes' ),\n\tnextState = require( './nextState' );\n\n/**\n * Reducer for actions that modify the state of the settings\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tif ( state === undefined ) {\n\t\tstate = {\n\t\t\tshouldShow: false,\n\t\t\tshowHelp: false,\n\t\t\tshouldShowFooterLink: false\n\t\t};\n\t}\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.SETTINGS_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShow: true,\n\t\t\t\tshowHelp: false\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_HIDE:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShow: false,\n\t\t\t\tshowHelp: false\n\t\t\t} );\n\t\tcase actionTypes.SETTINGS_CHANGE:\n\t\t\treturn action.wasEnabled === action.enabled ?\n\t\t\t\t// If the setting is the same, just hide the dialogs\n\t\t\t\tnextState( state, {\n\t\t\t\t\tshouldShow: false\n\t\t\t\t} ) :\n\t\t\t\t// If the settings have changed...\n\t\t\t\tnextState( state, {\n\t\t\t\t\t// If we enabled, we just hide directly, no help\n\t\t\t\t\t// If we disabled, keep it showing and let the ui show the help.\n\t\t\t\t\tshouldShow: !action.enabled,\n\t\t\t\t\tshowHelp: !action.enabled,\n\n\t\t\t\t\t// Since the footer link is only ever shown to anonymous users (see\n\t\t\t\t\t// the BOOT case below), state.userIsAnon is always truthy here.\n\t\t\t\t\tshouldShowFooterLink: !action.enabled\n\t\t\t\t} );\n\n\t\tcase actionTypes.BOOT:\n\t\t\treturn nextState( state, {\n\t\t\t\tshouldShowFooterLink: action.user.isAnon && !action.isEnabled\n\t\t\t} );\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/settings.js\n// module id = ./src/reducers/settings.js\n// module chunks = 0","var actionTypes = require( './../actionTypes' ),\n\tnextState = require( './nextState' );\n\n/**\n * Reducer for actions that may result in an event being logged via statsv.\n *\n * @param {Object} state\n * @param {Object} action\n * @return {Object} state after action\n */\nmodule.exports = function ( state, action ) {\n\tstate = state || {};\n\n\tswitch ( action.type ) {\n\t\tcase actionTypes.FETCH_START:\n\t\t\treturn nextState( state, {\n\t\t\t\tfetchStartedAt: action.timestamp\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_END:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'timing.PagePreviewsApiResponse',\n\t\t\t\tdata: action.timestamp - state.fetchStartedAt\n\t\t\t} );\n\n\t\tcase actionTypes.FETCH_FAILED:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'counter.PagePreviewsApiFailure',\n\t\t\t\tdata: 1\n\t\t\t} );\n\n\t\tcase actionTypes.LINK_DWELL:\n\t\t\treturn nextState( state, {\n\t\t\t\tlinkDwellStartedAt: action.timestamp\n\t\t\t} );\n\n\t\tcase actionTypes.PREVIEW_SHOW:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: 'timing.PagePreviewsPreviewShow',\n\t\t\t\tdata: action.timestamp - state.linkDwellStartedAt\n\t\t\t} );\n\n\t\tcase actionTypes.STATSV_LOGGED:\n\t\t\treturn nextState( state, {\n\t\t\t\taction: null,\n\t\t\t\tdata: null\n\t\t\t} );\n\n\t\tdefault:\n\t\t\treturn state;\n\t}\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/reducers/statsv.js\n// module id = ./src/reducers/statsv.js\n// module chunks = 0","/**\n * @module renderer\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery,\n\twait = require( './wait' ),\n\tSIZES = {\n\t\tportraitImage: {\n\t\t\th: 250, // Exact height\n\t\t\tw: 203 // Max width\n\t\t},\n\t\tlandscapeImage: {\n\t\t\th: 200, // Max height\n\t\t\tw: 300 // Exact Width\n\t\t},\n\t\tlandscapePopupWidth: 450,\n\t\tportraitPopupWidth: 300,\n\t\tpokeySize: 8 // Height of the pokey.\n\t},\n\t$window = $( window );\n\n/**\n * Extracted from `mw.popups.createSVGMasks`.\n * @private\n * @param {Object} container DOM object to which pokey masks are appended\n */\nfunction createPokeyMasks( container ) {\n\t$( '<div>' )\n\t\t.attr( 'id', 'mwe-popups-svg' )\n\t\t.html(\n\t\t\t'<svg width=\"0\" height=\"0\">' +\n\t\t\t\t'<defs>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-mask\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 10 8, 18 0, 26 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-mask-flip\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 274 8, 282 0, 290 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-landscape-mask\">' +\n\t\t\t\t\t\t'<polygon points=\"0 8, 174 8, 182 0, 190 8, 1000 8, 1000 1000, 0 1000\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t\t'<clippath id=\"mwe-popups-landscape-mask-flip\">' +\n\t\t\t\t\t\t'<polygon points=\"0 0, 1000 0, 1000 242, 190 242, 182 250, 174 242, 0 242\"/>' +\n\t\t\t\t\t'</clippath>' +\n\t\t\t\t'</defs>' +\n\t\t\t'</svg>'\n\t\t)\n\t\t.appendTo( container );\n}\n\n/**\n * Initializes the renderer.\n */\nfunction init() {\n\tcreatePokeyMasks( document.body );\n}\n\n/**\n * The model of how a view is rendered, which is constructed from a response\n * from the gateway.\n *\n * TODO: Rename `isTall` to `isPortrait`.\n *\n * @typedef {Object} ext.popups.Preview\n * @property {jQuery} el\n * @property {Boolean} hasThumbnail\n * @property {Object} thumbnail\n * @property {Boolean} isTall Sugar around\n * `preview.hasThumbnail && thumbnail.isTall`\n */\n\n/**\n * Renders a preview given data from the {@link gateway ext.popups.Gateway}.\n * The preview is rendered and added to the DOM but will remain hidden until\n * the `show` method is called.\n *\n * Previews are rendered at:\n *\n * # The position of the mouse when the user dwells on the link with their\n * mouse.\n * # The centermost point of the link when the user dwells on the link with\n * their keboard or other assistive device.\n *\n * Since the content of the preview doesn't change but its position might, we\n * distinguish between \"rendering\" - generating HTML from a MediaWiki API\n * response - and \"showing/hiding\" - positioning the layout and changing its\n * orientation, if necessary.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction render( model ) {\n\tvar preview = model.extract === undefined ? createEmptyPreview( model ) : createPreview( model );\n\n\treturn {\n\n\t\t/**\n\t\t * Shows the preview given an event representing the user's interaction\n\t\t * with the active link, e.g. an instance of\n\t\t * [MouseEvent](https://developer.mozilla.org/en/docs/Web/API/MouseEvent).\n\t\t *\n\t\t * See `show` for more detail.\n\t\t *\n\t\t * @param {Event} event\n\t\t * @param {Object} boundActions The\n\t\t * [bound action creators](http://redux.js.org/docs/api/bindActionCreators.html)\n\t\t * that were (likely) created in [boot.js](./boot.js).\n\t\t * @param {String} token The unique token representing the link interaction\n\t\t * that resulted in showing the preview\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tshow: function ( event, boundActions, token ) {\n\t\t\treturn show(\n\t\t\t\tpreview, event, $( event.target ), boundActions, token,\n\t\t\t\tdocument.body\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Hides the preview.\n\t\t *\n\t\t * See `hide` for more detail.\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\thide: function () {\n\t\t\treturn hide( preview );\n\t\t}\n\t};\n}\n\n/**\n * Creates an instance of the DTO backing a preview.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction createPreview( model ) {\n\tvar templateData,\n\t\tthumbnail = createThumbnail( model.thumbnail ),\n\t\thasThumbnail = thumbnail !== null,\n\t\textract = model.extract,\n\t\t$el;\n\n\ttemplateData = $.extend( {}, model, {\n\t\thasThumbnail: hasThumbnail\n\t} );\n\n\t$el = mw.template.get( 'ext.popups', 'preview.mustache' )\n\t\t.render( templateData );\n\n\tif ( hasThumbnail ) {\n\t\t$el.find( '.mwe-popups-discreet' ).append( thumbnail.el );\n\t}\n\tif ( extract ) {\n\t\t$el.find( '.mwe-popups-extract' ).append( extract );\n\t}\n\n\treturn {\n\t\tel: $el,\n\t\thasThumbnail: hasThumbnail,\n\t\tthumbnail: thumbnail,\n\t\tisTall: hasThumbnail && thumbnail.isTall\n\t};\n}\n\n/**\n * Creates an instance of the DTO backing a preview. In this case the DTO\n * represents a generic preview, which covers the following scenarios:\n *\n * * The page doesn't exist, i.e. the user hovered over a redlink or a\n * redirect to a page that doesn't exist.\n * * The page doesn't have a viable extract.\n *\n * @param {ext.popups.PreviewModel} model\n * @return {ext.popups.Preview}\n */\nfunction createEmptyPreview( model ) {\n\tvar templateData,\n\t\t$el;\n\n\ttemplateData = $.extend( {}, model, {\n\t\textractMsg: mw.msg( 'popups-preview-no-preview' ),\n\t\treadMsg: mw.msg( 'popups-preview-footer-read' )\n\t} );\n\n\t$el = mw.template.get( 'ext.popups', 'preview-empty.mustache' )\n\t\t.render( templateData );\n\n\treturn {\n\t\tel: $el,\n\t\thasThumbnail: false,\n\t\tisTall: false\n\t};\n}\n\n/**\n * Shows the preview.\n *\n * Extracted from `mw.popups.render.openPopup`.\n *\n * TODO: From the perspective of the client, there's no need to distinguish\n * between renderering and showing a preview. Merge #render and Preview#show.\n *\n * @param {ext.popups.Preview} preview\n * @param {Event} event\n * @param {jQuery} $link event target\n * @param {ext.popups.PreviewBehavior} behavior\n * @param {String} token\n * @param {Object} container DOM object to which pokey masks are appended\n * @return {jQuery.Promise} A promise that resolves when the promise has faded\n * in\n */\nfunction show( preview, event, $link, behavior, token, container ) {\n\tvar layout = createLayout(\n\t\tpreview.isTall,\n\t\t{\n\t\t\tpageX: event.pageX,\n\t\t\tpageY: event.pageY,\n\t\t\tclientY: event.clientY\n\t\t},\n\t\t{\n\t\t\tclientRects: $link.get( 0 ).getClientRects(),\n\t\t\toffset: $link.offset(),\n\t\t\twidth: $link.width(),\n\t\t\theight: $link.height()\n\t\t},\n\t\t{\n\t\t\tscrollTop: $window.scrollTop(),\n\t\t\twidth: $window.width(),\n\t\t\theight: $window.height()\n\t\t},\n\t\tSIZES.pokeySize\n\t);\n\n\tpreview.el.appendTo( container );\n\n\tlayoutPreview(\n\t\tpreview, layout, getClasses( preview, layout ),\n\t\tSIZES.landscapeImage.h, SIZES.pokeySize\n\t);\n\n\tpreview.el.show();\n\n\treturn wait( 200 )\n\t\t.then( function () {\n\t\t\tbindBehavior( preview, behavior );\n\t\t} )\n\t\t.then( function () {\n\t\t\tbehavior.previewShow( token );\n\t\t} );\n}\n\n/**\n * Binds the behavior to the interactive elements of the preview.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewBehavior} behavior\n */\nfunction bindBehavior( preview, behavior ) {\n\tpreview.el.hover( behavior.previewDwell, behavior.previewAbandon );\n\n\tpreview.el.click( behavior.click );\n\n\tpreview.el.find( '.mwe-popups-settings-icon' )\n\t\t.attr( 'href', behavior.settingsUrl )\n\t\t.click( function ( event ) {\n\t\t\tevent.stopPropagation();\n\n\t\t\tbehavior.showSettings( event );\n\t\t} );\n}\n\n/**\n * Extracted from `mw.popups.render.closePopup`.\n *\n * @param {ext.popups.Preview} preview\n * @return {jQuery.Promise} A promise that resolves when the preview has faded\n * out\n */\nfunction hide( preview ) {\n\tvar fadeInClass,\n\t\tfadeOutClass;\n\n\t// FIXME: This method clearly needs access to the layout of the preview.\n\tfadeInClass = ( preview.el.hasClass( 'mwe-popups-fade-in-up' ) ) ?\n\t\t'mwe-popups-fade-in-up' :\n\t\t'mwe-popups-fade-in-down';\n\n\tfadeOutClass = ( fadeInClass === 'mwe-popups-fade-in-up' ) ?\n\t\t'mwe-popups-fade-out-down' :\n\t\t'mwe-popups-fade-out-up';\n\n\tpreview.el\n\t\t.removeClass( fadeInClass )\n\t\t.addClass( fadeOutClass );\n\n\treturn wait( 150 ).then( function () {\n\t\tpreview.el.remove();\n\t} );\n}\n\n/**\n * @typedef {Object} ext.popups.Thumbnail\n * @property {Element} el\n * @property {Boolean} isTall Whether or not the thumbnail is portrait\n */\n\n/**\n * Creates a thumbnail from the representation of a thumbnail returned by the\n * PageImages MediaWiki API query module.\n *\n * If there's no thumbnail, the thumbnail is too small, or the thumbnail's URL\n * contains characters that could be used to perform an\n * [XSS attack via CSS](https://www.owasp.org/index.php/Testing_for_CSS_Injection_(OTG-CLIENT-005)),\n * then `null` is returned.\n *\n * Extracted from `mw.popups.renderer.article.createThumbnail`.\n *\n * @param {Object} rawThumbnail\n * @return {ext.popups.Thumbnail|null}\n */\nfunction createThumbnail( rawThumbnail ) {\n\tvar tall, thumbWidth, thumbHeight,\n\t\tx, y, width, height, clipPath,\n\t\tdevicePixelRatio = $.bracketedDevicePixelRatio();\n\n\tif ( !rawThumbnail ) {\n\t\treturn null;\n\t}\n\n\ttall = rawThumbnail.width < rawThumbnail.height;\n\tthumbWidth = rawThumbnail.width / devicePixelRatio;\n\tthumbHeight = rawThumbnail.height / devicePixelRatio;\n\n\tif (\n\t\t// Image too small for landscape display\n\t\t( !tall && thumbWidth < SIZES.landscapeImage.w ) ||\n\t\t// Image too small for portrait display\n\t\t( tall && thumbHeight < SIZES.portraitImage.h ) ||\n\t\t// These characters in URL that could inject CSS and thus JS\n\t\t(\n\t\t\trawThumbnail.source.indexOf( '\\\\' ) > -1 ||\n\t\t\trawThumbnail.source.indexOf( '\\'' ) > -1 ||\n\t\t\trawThumbnail.source.indexOf( '\\\"' ) > -1\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\tif ( tall ) {\n\t\tx = ( thumbWidth > SIZES.portraitImage.w ) ?\n\t\t\t( ( thumbWidth - SIZES.portraitImage.w ) / -2 ) :\n\t\t\t( SIZES.portraitImage.w - thumbWidth );\n\t\ty = ( thumbHeight > SIZES.portraitImage.h ) ?\n\t\t\t( ( thumbHeight - SIZES.portraitImage.h ) / -2 ) : 0;\n\t\twidth = SIZES.portraitImage.w;\n\t\theight = SIZES.portraitImage.h;\n\t} else {\n\t\tx = 0;\n\t\ty = ( thumbHeight > SIZES.landscapeImage.h ) ?\n\t\t\t( ( thumbHeight - SIZES.landscapeImage.h ) / -2 ) : 0;\n\t\twidth = SIZES.landscapeImage.w + 3;\n\t\theight = ( thumbHeight > SIZES.landscapeImage.h ) ?\n\t\t\tSIZES.landscapeImage.h : thumbHeight;\n\t\tclipPath = 'mwe-popups-mask';\n\t}\n\n\treturn {\n\t\tel: createThumbnailElement(\n\t\t\ttall ? 'mwe-popups-is-tall' : 'mwe-popups-is-not-tall',\n\t\t\trawThumbnail.source,\n\t\t\tx,\n\t\t\ty,\n\t\t\tthumbWidth,\n\t\t\tthumbHeight,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tclipPath\n\t\t),\n\t\tisTall: tall,\n\t\twidth: thumbWidth,\n\t\theight: thumbHeight\n\t};\n}\n\n/**\n * Creates the SVG image element that represents the thumbnail.\n *\n * This function is distinct from `createThumbnail` as it abstracts away some\n * browser issues that are uncovered when manipulating elements across\n * namespaces.\n *\n * @param {String} className\n * @param {String} url\n * @param {Number} x\n * @param {Number} y\n * @param {Number} thumbnailWidth\n * @param {Number} thumbnailHeight\n * @param {Number} width\n * @param {Number} height\n * @param {String} clipPath\n * @return {jQuery}\n */\nfunction createThumbnailElement( className, url, x, y, thumbnailWidth, thumbnailHeight, width, height, clipPath ) {\n\tvar $thumbnailSVGImage, $thumbnail,\n\t\tnsSvg = 'http://www.w3.org/2000/svg',\n\t\tnsXlink = 'http://www.w3.org/1999/xlink';\n\n\t$thumbnailSVGImage = $( document.createElementNS( nsSvg, 'image' ) );\n\t$thumbnailSVGImage[ 0 ].setAttributeNS( nsXlink, 'href', url );\n\t$thumbnailSVGImage\n\t\t.addClass( className )\n\t\t.attr( {\n\t\t\tx: x,\n\t\t\ty: y,\n\t\t\twidth: thumbnailWidth,\n\t\t\theight: thumbnailHeight,\n\t\t\t'clip-path': 'url(#' + clipPath + ')'\n\t\t} );\n\n\t$thumbnail = $( document.createElementNS( nsSvg, 'svg' ) )\n\t\t.attr( {\n\t\t\txmlns: nsSvg,\n\t\t\twidth: width,\n\t\t\theight: height\n\t\t} )\n\t\t.append( $thumbnailSVGImage );\n\n\treturn $thumbnail;\n}\n\n/**\n * Represents the layout of a preview, which consists of a position (`offset`)\n * and whether or not the preview should be flipped horizontally or\n * vertically (`flippedX` and `flippedY` respectively).\n *\n * @typedef {Object} ext.popups.PreviewLayout\n * @property {Object} offset\n * @property {number} offset.top\n * @property {number} offset.left\n * @property {Boolean} flippedX\n * @property {Boolean} flippedY\n */\n\n/**\n * @param {isPreviewTall} isPreviewTall\n * @param {Object} eventData Data related to the event that triggered showing a popup\n * @param {number} eventData.pageX\n * @param {number} eventData.pageY\n * @param {number} eventData.clientY\n * @param {Object} linkData Data related to the link thats used for showing a popup\n * @param {ClientRectList} linkData.clientRects list of rectangles defined by four edges\n * @param {Object} linkData.offset\n * @param {number} linkData.width\n * @param {number} linkData.height\n * @param {Object} windowData Data related to the window\n * @param {number} windowData.scrollTop\n * @param {number} windowData.width\n * @param {number} windowData.height\n * @param {number} pokeySize Space reserved for the pokey\n * @return {ext.popups.PreviewLayout}\n */\nfunction createLayout( isPreviewTall, eventData, linkData, windowData, pokeySize ) {\n\tvar flippedX = false,\n\t\tflippedY = false,\n\t\toffsetTop = ( eventData.pageY ) ? // If it was a mouse event\n\t\t\t// Position according to mouse\n\t\t\t// Since client rectangles are relative to the viewport,\n\t\t\t// take scroll position into account.\n\t\t\tgetClosestYPosition(\n\t\t\t\teventData.pageY - windowData.scrollTop,\n\t\t\t\tlinkData.clientRects,\n\t\t\t\tfalse\n\t\t\t) + windowData.scrollTop + pokeySize :\n\t\t\t// Position according to link position or size\n\t\t\tlinkData.offset.top + linkData.height + pokeySize,\n\t\tclientTop = ( eventData.clientY ) ?\n\t\t\teventData.clientY :\n\t\t\toffsetTop,\n\t\toffsetLeft = ( eventData.pageX ) ?\n\t\t\teventData.pageX :\n\t\t\tlinkData.offset.left;\n\n\t// X Flip\n\tif ( offsetLeft > ( windowData.width / 2 ) ) {\n\t\toffsetLeft += ( !eventData.pageX ) ? linkData.width : 0;\n\t\toffsetLeft -= !isPreviewTall ?\n\t\t\tSIZES.portraitPopupWidth :\n\t\t\tSIZES.landscapePopupWidth;\n\t\tflippedX = true;\n\t}\n\n\tif ( eventData.pageX ) {\n\t\toffsetLeft += ( flippedX ) ? 20 : -20;\n\t}\n\n\t// Y Flip\n\tif ( clientTop > ( windowData.height / 2 ) ) {\n\t\tflippedY = true;\n\n\t\t// Mirror the positioning of the preview when there's no \"Y flip\": rest\n\t\t// the pokey on the edge of the link's bounding rectangle. In this case\n\t\t// the edge is the top-most.\n\t\toffsetTop = linkData.offset.top;\n\n\t\t// Change the Y position to the top of the link\n\t\tif ( eventData.pageY ) {\n\t\t\t// Since client rectangles are relative to the viewport,\n\t\t\t// take scroll position into account.\n\t\t\toffsetTop = getClosestYPosition(\n\t\t\t\teventData.pageY - windowData.scrollTop,\n\t\t\t\tlinkData.clientRects,\n\t\t\t\ttrue\n\t\t\t) + windowData.scrollTop;\n\t\t}\n\n\t\toffsetTop -= pokeySize;\n\t}\n\n\treturn {\n\t\toffset: {\n\t\t\ttop: offsetTop,\n\t\t\tleft: offsetLeft\n\t\t},\n\t\tflippedX: flippedX,\n\t\tflippedY: flippedY\n\t};\n}\n\n/**\n * Generates a list of declarative CSS classes that represent the layout of\n * the preview.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewLayout} layout\n * @return {String[]}\n */\nfunction getClasses( preview, layout ) {\n\tvar classes = [];\n\n\tif ( layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-fade-in-down' );\n\t} else {\n\t\tclasses.push( 'mwe-popups-fade-in-up' );\n\t}\n\n\tif ( layout.flippedY && layout.flippedX ) {\n\t\tclasses.push( 'flipped_x_y' );\n\t}\n\n\tif ( layout.flippedY && !layout.flippedX ) {\n\t\tclasses.push( 'flipped_y' );\n\t}\n\n\tif ( layout.flippedX && !layout.flippedY ) {\n\t\tclasses.push( 'flipped_x' );\n\t}\n\n\tif ( ( !preview.hasThumbnail || preview.isTall ) && !layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-no-image-tri' );\n\t}\n\n\tif ( ( preview.hasThumbnail && !preview.isTall ) && !layout.flippedY ) {\n\t\tclasses.push( 'mwe-popups-image-tri' );\n\t}\n\n\tif ( preview.isTall ) {\n\t\tclasses.push( 'mwe-popups-is-tall' );\n\t} else {\n\t\tclasses.push( 'mwe-popups-is-not-tall' );\n\t}\n\n\treturn classes;\n}\n\n/**\n * Lays out the preview given the layout.\n *\n * If the preview should be oriented differently, then the pokey is updated,\n * e.g. if the preview should be flipped vertically, then the pokey is\n * removed.\n *\n * If the thumbnail is landscape and isn't the full height of the thumbnail\n * container, then pull the extract up to keep whitespace consistent across\n * previews.\n *\n * @param {ext.popups.Preview} preview\n * @param {ext.popups.PreviewLayout} layout\n * @param {string[]} classes class names used for layout out the preview\n * @param {number} predefinedLandscapeImageHeight landscape image height\n * @param {number} pokeySize\n */\nfunction layoutPreview( preview, layout, classes, predefinedLandscapeImageHeight, pokeySize ) {\n\tvar popup = preview.el,\n\t\tisTall = preview.isTall,\n\t\thasThumbnail = preview.hasThumbnail,\n\t\tthumbnail = preview.thumbnail,\n\t\tflippedY = layout.flippedY,\n\t\tflippedX = layout.flippedX,\n\t\toffsetTop = layout.offset.top;\n\n\tif (\n\t\t!flippedY && !isTall && hasThumbnail &&\n\t\t\tthumbnail.height < predefinedLandscapeImageHeight\n\t) {\n\t\tpopup.find( '.mwe-popups-extract' ).css(\n\t\t\t'margin-top',\n\t\t\tthumbnail.height - pokeySize\n\t\t);\n\t}\n\n\tpopup.addClass( classes.join( ' ' ) );\n\n\tif ( flippedY ) {\n\t\toffsetTop -= popup.outerHeight();\n\t}\n\n\tpopup.css( {\n\t\ttop: offsetTop,\n\t\tleft: layout.offset.left + 'px'\n\t} );\n\n\tif ( flippedY && hasThumbnail ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.removeAttribute( 'clip-path' );\n\t}\n\n\tif ( flippedY && flippedX && hasThumbnail && isTall ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask-flip)' );\n\t}\n\n\tif ( flippedX && !flippedY && hasThumbnail && !isTall ) {\n\t\tpopup.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );\n\t}\n\n\tif ( flippedX && !flippedY && hasThumbnail && isTall ) {\n\t\tpopup.removeClass( 'mwe-popups-no-image-tri' )\n\t\t\t.find( 'image' )[ 0 ]\n\t\t\t.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );\n\t}\n}\n\n/**\n * Given the rectangular box(es) find the 'y' boundary of the closest\n * rectangle to the point 'y'. The point 'y' is the location of the mouse\n * on the 'y' axis and the rectangular box(es) are the borders of the\n * element over which the mouse is located. There will be more than one\n * rectangle in case the element spans multiple lines.\n *\n * In the majority of cases the mouse pointer will be inside a rectangle.\n * However, some browsers (i.e. Chrome) trigger a hover action even when\n * the mouse pointer is just outside a bounding rectangle. That's why\n * we need to look at all rectangles and not just the rectangle that\n * encloses the point.\n *\n * @private\n * @param {Number} y the point for which the closest location is being\n * looked for\n * @param {ClientRectList} rects list of rectangles defined by four edges\n * @param {Boolean} [isTop] should the resulting rectangle's top 'y'\n * boundary be returned. By default the bottom 'y' value is returned.\n * @return {Number}\n */\nfunction getClosestYPosition( y, rects, isTop ) {\n\tvar result,\n\t\tdeltaY,\n\t\tminY = null;\n\n\t$.each( rects, function ( i, rect ) {\n\t\tdeltaY = Math.abs( y - rect.top + y - rect.bottom );\n\n\t\tif ( minY === null || minY > deltaY ) {\n\t\t\tminY = deltaY;\n\t\t\t// Make sure the resulting point is at or outside the rectangle\n\t\t\t// boundaries.\n\t\t\tresult = ( isTop ) ? Math.floor( rect.top ) : Math.ceil( rect.bottom );\n\t\t}\n\t} );\n\n\treturn result;\n}\n\nmodule.exports = {\n\trender: render,\n\tinit: init,\n\t// The following are exposed for teseting purposes only\n\tcreatePokeyMasks: createPokeyMasks,\n\tcreatePreview: createPreview,\n\tcreateEmptyPreview: createEmptyPreview,\n\tbindBehavior: bindBehavior,\n\tshow: show,\n\thide: hide,\n\tcreateThumbnail: createThumbnail,\n\tcreateThumbnailElement: createThumbnailElement,\n\tcreateLayout: createLayout,\n\tgetClasses: getClasses,\n\tlayoutPreview: layoutPreview,\n\tgetClosestYPosition: getClosestYPosition\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/renderer.js\n// module id = ./src/renderer.js\n// module chunks = 0","/**\n * @module schema\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates an instance of the [EventLogging Schema class][0] with a sampling\n * rate of `wgPopupsSchemaSamplingRate` if the UA supports [the Beacon API][1]\n * or `0` if it doesn't.\n *\n * [0]: https://github.com/wikimedia/mediawiki-extensions-EventLogging/blob/master/modules/ext.eventLogging.Schema.js\n * [1]: https://w3c.github.io/beacon/\n *\n * @param {mw.Map} config\n * @param {Window} window\n * @return {mw.eventLog.Schema}\n */\nmodule.exports = function ( config, window ) {\n\tvar samplingRate = config.get( 'wgPopupsSchemaSamplingRate', 0 );\n\n\tif (\n\t\t!window.navigator ||\n\t\t!$.isFunction( window.navigator.sendBeacon )\n\t) {\n\t\tsamplingRate = 0;\n\t}\n\n\treturn new mw.eventLog.Schema( 'Popups', samplingRate );\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/schema.js\n// module id = ./src/schema.js\n// module chunks = 0","/**\n * @module settingsDialog\n */\n\nvar mw = window.mediaWiki,\n\t$ = jQuery;\n\n/**\n * Creates a render function that will create the settings dialog and return\n * a set of methods to operate on it\n * @returns {Function} render function\n */\nmodule.exports = function () {\n\n\t/**\n\t * Cached settings dialog\n\t *\n\t * @type {jQuery}\n\t */\n\tvar $dialog,\n\t\t/**\n\t\t * Cached settings overlay\n\t\t *\n\t\t * @type {jQuery}\n\t\t */\n\t\t$overlay;\n\n\t/**\n\t * Renders the relevant form and labels in the settings dialog\n\t * @param {Object} boundActions\n\t * @returns {Object} object with methods to affect the rendered UI\n\t */\n\treturn function ( boundActions ) {\n\n\t\tif ( !$dialog ) {\n\t\t\t$dialog = createSettingsDialog();\n\t\t\t$overlay = $( '<div>' ).addClass( 'mwe-popups-overlay' );\n\n\t\t\t// Setup event bindings\n\n\t\t\t$dialog.find( '.save' ).click( function () {\n\t\t\t\t// Find the selected value (simple|advanced|off)\n\t\t\t\tvar selected = getSelectedSetting( $dialog ),\n\t\t\t\t\t// Only simple means enabled, advanced is disabled in favor of\n\t\t\t\t\t// NavPops and off means disabled.\n\t\t\t\t\tenabled = selected === 'simple';\n\n\t\t\t\tboundActions.saveSettings( enabled );\n\t\t\t} );\n\t\t\t$dialog.find( '.close, .okay' ).click( boundActions.hideSettings );\n\t\t}\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Append the dialog and overlay to a DOM element\n\t\t\t * @param {HTMLElement} el\n\t\t\t */\n\t\t\tappendTo: function ( el ) {\n\t\t\t\t$overlay.appendTo( el );\n\t\t\t\t$dialog.appendTo( el );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Show the settings element and position it correctly\n\t\t\t */\n\t\t\tshow: function () {\n\t\t\t\tvar h = $( window ).height(),\n\t\t\t\t\tw = $( window ).width();\n\n\t\t\t\t$overlay.show();\n\n\t\t\t\t// FIXME: Should recalc on browser resize\n\t\t\t\t$dialog\n\t\t\t\t\t.show()\n\t\t\t\t\t.css( 'left', ( w - $dialog.outerWidth( true ) ) / 2 )\n\t\t\t\t\t.css( 'top', ( h - $dialog.outerHeight( true ) ) / 2 );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Hide the settings dialog.\n\t\t\t */\n\t\t\thide: function () {\n\t\t\t\t$overlay.hide();\n\t\t\t\t$dialog.hide();\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Toggle the help dialog on or off\n\t\t\t * @param {Boolean} visible if you want to show or hide the help dialog\n\t\t\t */\n\t\t\ttoggleHelp: function ( visible ) {\n\t\t\t\ttoggleHelp( $dialog, visible );\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Update the form depending on the enabled flag\n\t\t\t *\n\t\t\t * If false and no navpops, then checks 'off'\n\t\t\t * If true, then checks 'on'\n\t\t\t * If false, and there are navpops, then checks 'advanced'\n\t\t\t *\n\t\t\t * @param {Boolean} enabled if page previews are enabled\n\t\t\t */\n\t\t\tsetEnabled: function ( enabled ) {\n\t\t\t\tvar name = 'off';\n\t\t\t\tif ( enabled ) {\n\t\t\t\t\tname = 'simple';\n\t\t\t\t} else if ( isNavPopupsEnabled() ) {\n\t\t\t\t\tname = 'advanced';\n\t\t\t\t}\n\n\t\t\t\t// Check the appropiate radio button\n\t\t\t\t$dialog.find( '#mwe-popups-settings-' + name )\n\t\t\t\t\t.prop( 'checked', true );\n\t\t\t}\n\t\t};\n\t};\n};\n\n/**\n * Create the settings dialog\n *\n * @return {jQuery} settings dialog\n */\nfunction createSettingsDialog() {\n\tvar $el,\n\t\tpath = mw.config.get( 'wgExtensionAssetsPath' ) + '/Popups/resources/ext.popups/images/',\n\t\tchoices = [\n\t\t\t{\n\t\t\t\tid: 'simple',\n\t\t\t\tname: mw.msg( 'popups-settings-option-simple' ),\n\t\t\t\tdescription: mw.msg( 'popups-settings-option-simple-description' ),\n\t\t\t\timage: path + 'hovercard.svg',\n\t\t\t\tisChecked: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'advanced',\n\t\t\t\tname: mw.msg( 'popups-settings-option-advanced' ),\n\t\t\t\tdescription: mw.msg( 'popups-settings-option-advanced-description' ),\n\t\t\t\timage: path + 'navpop.svg'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'off',\n\t\t\t\tname: mw.msg( 'popups-settings-option-off' )\n\t\t\t}\n\t\t];\n\n\tif ( !isNavPopupsEnabled() ) {\n\t\t// remove the advanced option\n\t\tchoices.splice( 1, 1 );\n\t}\n\n\t// render the template\n\t$el = mw.template.get( 'ext.popups', 'settings.mustache' ).render( {\n\t\theading: mw.msg( 'popups-settings-title' ),\n\t\tcloseLabel: mw.msg( 'popups-settings-cancel' ),\n\t\tsaveLabel: mw.msg( 'popups-settings-save' ),\n\t\thelpText: mw.msg( 'popups-settings-help' ),\n\t\tokLabel: mw.msg( 'popups-settings-help-ok' ),\n\t\tdescriptionText: mw.msg( 'popups-settings-description' ),\n\t\tchoices: choices\n\t} );\n\n\treturn $el;\n}\n\n/**\n * Get the selected value on the radio button\n *\n * @param {jQuery.Object} $el the element to extract the setting from\n * @return {String} Which should be (simple|advanced|off)\n */\nfunction getSelectedSetting( $el ) {\n\treturn $el.find(\n\t\t'input[name=mwe-popups-setting]:checked, #mwe-popups-settings'\n\t).val();\n}\n\n/**\n * Toggles the visibility between a form and the help\n * @param {jQuery.Object} $el element that contains form and help\n * @param {Boolean} visible if the help should be visible, or the form\n */\nfunction toggleHelp( $el, visible ) {\n\tvar $dialog = $( '#mwe-popups-settings' ),\n\t\tformSelectors = 'main, .save, .close',\n\t\thelpSelectors = '.mwe-popups-settings-help, .okay';\n\n\tif ( visible ) {\n\t\t$dialog.find( formSelectors ).hide();\n\t\t$dialog.find( helpSelectors ).show();\n\t} else {\n\t\t$dialog.find( formSelectors ).show();\n\t\t$dialog.find( helpSelectors ).hide();\n\t}\n}\n\n/**\n * Checks if the NavigationPopups gadget is enabled by looking at the global\n * variables\n * @returns {Boolean} if navpops was found to be enabled\n */\nfunction isNavPopupsEnabled() {\n\t/* global pg: false*/\n\treturn typeof pg !== 'undefined' && pg.fn.disablePopups !== undefined;\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/settingsDialog.js\n// module id = ./src/settingsDialog.js\n// module chunks = 0","/**\n * @module statsvInstrumentation\n */\n\n/**\n * Gets whether Graphite logging (via [the statsv HTTP endpoint][0]) is enabled\n * for duration of the browser session. The sampling rate is controlled by\n * `wgPopupsStatsvSamplingRate`.\n *\n * [0]: https://wikitech.wikimedia.org/wiki/Graphite#statsv\n *\n * @param {mw.user} user The `mw.user` singleton instance\n * @param {mw.Map} config The `mw.config` singleton instance\n * @param {mw.experiments} experiments The `mw.experiments` singleton instance\n * @returns {Boolean}\n */\nexports.isEnabled = function isEnabled( user, config, experiments ) {\n\tvar samplingRate = config.get( 'wgPopupsStatsvSamplingRate', 0 ),\n\t\tbucket = experiments.getBucket( {\n\t\t\tname: 'ext.Popups.statsv',\n\t\t\tenabled: true,\n\t\t\tbuckets: {\n\t\t\t\tcontrol: 1 - samplingRate,\n\t\t\t\tA: samplingRate\n\t\t\t}\n\t\t}, user.sessionId() );\n\n\treturn bucket === 'A';\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/statsvInstrumentation.js\n// module id = ./src/statsvInstrumentation.js\n// module chunks = 0","/**\n * @module title\n */\n\nvar mw = window.mediaWiki;\n\n/**\n * Gets the title of a local page from an href given some configuration.\n *\n * @param {String} href\n * @param {mw.Map} config\n * @return {String|undefined}\n */\nfunction getTitle( href, config ) {\n\tvar linkHref,\n\t\tmatches,\n\t\tqueryLength,\n\t\ttitleRegex = new RegExp( mw.RegExp.escape( config.get( 'wgArticlePath' ) )\n\t\t\t.replace( '\\\\$1', '(.+)' ) );\n\n\t// Skip every URI that mw.Uri cannot parse\n\ttry {\n\t\tlinkHref = new mw.Uri( href );\n\t} catch ( e ) {\n\t\treturn undefined;\n\t}\n\n\t// External links\n\tif ( linkHref.host !== location.hostname ) {\n\t\treturn undefined;\n\t}\n\n\tqueryLength = Object.keys( linkHref.query ).length;\n\n\t// No query params (pretty URL)\n\tif ( !queryLength ) {\n\t\tmatches = titleRegex.exec( linkHref.path );\n\t\treturn matches ? decodeURIComponent( matches[ 1 ] ) : undefined;\n\t} else if ( queryLength === 1 && linkHref.query.hasOwnProperty( 'title' ) ) {\n\t\t// URL is not pretty, but only has a `title` parameter\n\t\treturn linkHref.query.title;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Given a page title it will return the mediawiki.Title if it is an eligible\n * link for showing page previews, null otherwise\n *\n * @param {String} title page title to check if it should show preview\n * @param {Number[]} contentNamespaces contentNamespaces as specified in\n * wgContentNamespaces\n * @returns {mw.Title|null}\n */\nfunction isValid( title, contentNamespaces ) {\n\tvar mwTitle;\n\n\tif ( !title ) {\n\t\treturn null;\n\t}\n\n\t// Is title in a content namespace?\n\tmwTitle = mw.Title.newFromText( title );\n\tif ( mwTitle && ( $.inArray( mwTitle.namespace, contentNamespaces ) >= 0 ) ) {\n\t\treturn mwTitle;\n\t}\n\n\treturn null;\n}\n\n/**\n * Return a mw.Title from a HTMLElement if valid for hovercards. Convenience\n * method\n *\n * @param {Element} el\n * @param {mw.Map} config\n * @return {mw.Title|null}\n */\nfunction fromElement( el, config ) {\n\treturn isValid( getTitle( el.href, config ), config.get( 'wgContentNamespaces' ) );\n}\n\nmodule.exports = {\n\tgetTitle: getTitle,\n\tisValid: isValid,\n\tfromElement: fromElement\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/title.js\n// module id = ./src/title.js\n// module chunks = 0","/**\n * @module userSettings\n */\n\n/**\n * @interface UserSettings\n *\n * @global\n */\n\nvar IS_ENABLED_KEY = 'mwe-popups-enabled',\n\tPREVIEW_COUNT_KEY = 'ext.popups.core.previewCount';\n\n/**\n * Creates an object whose methods encapsulate all interactions with the UA's\n * storage.\n *\n * @param {Object} storage The `mw.storage` singleton instance\n *\n * @return {UserSettings}\n */\nmodule.exports = function ( storage ) {\n\treturn {\n\n\t\t/**\n\t\t * Gets whether the user has previously enabled Page Previews.\n\t\t *\n\t\t * N.B. that if the user hasn't previously enabled or disabled Page\n\t\t * Previews, i.e. userSettings.setIsEnabled(true), then they are treated as\n\t\t * if they have enabled them.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#getIsEnabled\n\t\t * @return {Boolean}\n\t\t */\n\t\tgetIsEnabled: function () {\n\t\t\treturn storage.get( IS_ENABLED_KEY ) !== '0';\n\t\t},\n\n\t\t/**\n\t\t * Sets whether the user has enabled Page Previews.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#setIsEnabled\n\t\t * @param {Boolean} isEnabled\n\t\t */\n\t\tsetIsEnabled: function ( isEnabled ) {\n\t\t\tstorage.set( IS_ENABLED_KEY, isEnabled ? '1' : '0' );\n\t\t},\n\n\t\t/**\n\t\t * Gets whether the user has previously enabled **or disabled** Page\n\t\t * Previews.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#hasIsEnabled\n\t\t * @return {Boolean}\n\t\t */\n\t\thasIsEnabled: function () {\n\t\t\tvar value = storage.get( IS_ENABLED_KEY );\n\n\t\t\treturn Boolean( value ) !== false;\n\t\t},\n\n\t\t/**\n\t\t * Gets the number of previews that the user has seen.\n\t\t *\n\t\t * If the storage isn't available, then -1 is returned.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#getPreviewCount\n\t\t * @return {Number}\n\t\t */\n\t\tgetPreviewCount: function () {\n\t\t\tvar result = storage.get( PREVIEW_COUNT_KEY );\n\n\t\t\tif ( result === false ) {\n\t\t\t\treturn -1;\n\t\t\t} else if ( result === null ) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn parseInt( result, 10 );\n\t\t},\n\n\t\t/**\n\t\t * Sets the number of previews that the user has seen.\n\t\t *\n\t\t * @function\n\t\t * @name UserSettings#setPreviewCount\n\t\t * @param {Number} count\n\t\t */\n\t\tsetPreviewCount: function ( count ) {\n\t\t\tstorage.set( PREVIEW_COUNT_KEY, count.toString() );\n\t\t}\n\t};\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/userSettings.js\n// module id = ./src/userSettings.js\n// module chunks = 0","/**\n * @module wait\n */\n\nvar $ = jQuery;\n\n/**\n * Sugar around `window.setTimeout`.\n *\n * @example\n * var wait = require( './wait' );\n *\n * wait( 150 )\n * .then( function () {\n * // Continue processing...\n * } );\n *\n * @param {Number} delay The number of milliseconds to wait\n * @return {jQuery.Promise}\n */\nmodule.exports = function ( delay ) {\n\tvar result = $.Deferred();\n\n\tsetTimeout( function () {\n\t\tresult.resolve();\n\t}, delay );\n\n\treturn result.promise();\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/wait.js\n// module id = ./src/wait.js\n// module chunks = 0"],"sourceRoot":""}