mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-09-23 10:21:11 +00:00
Tooling: Begin to use webpack for JS code generation
Generate changeListeners via webpack We now use a build folder to build the JavaScript for our ResourceLoader modules. This is the first change in a line of changes. A source map is provided for debug support. Bug: T156333 Change-Id: I771843d1ddb4b50adedc3fa53b30c2f1d8a76acb
This commit is contained in:
parent
fa0426e008
commit
49df4b9572
|
@ -3,6 +3,7 @@
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jquery": true,
|
"jquery": true,
|
||||||
|
"commonjs": true,
|
||||||
"qunit": true
|
"qunit": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/vendor/
|
/vendor/
|
||||||
/composer.lock
|
/composer.lock
|
||||||
|
resources/**/*.map
|
||||||
|
|
|
@ -23,8 +23,10 @@ module.exports = function ( grunt ) {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
all: [
|
all: [
|
||||||
|
'build/**',
|
||||||
'resources/ext.popups/*.js',
|
'resources/ext.popups/*.js',
|
||||||
'resources/ext.popups/**/*.js',
|
'resources/ext.popups/**/*.js',
|
||||||
|
'!resources/ext.popups/changeListeners/*.js',
|
||||||
'!docs/**',
|
'!docs/**',
|
||||||
'!node_modules/**'
|
'!node_modules/**'
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
( function ( mw, $ ) {
|
( function ( $ ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of the event logging change listener.
|
* Creates an instance of the event logging change listener.
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
* @param {mw.eventLog.Schema} schema
|
* @param {mw.eventLog.Schema} schema
|
||||||
* @return {ext.popups.ChangeListener}
|
* @return {ext.popups.ChangeListener}
|
||||||
*/
|
*/
|
||||||
mw.popups.changeListeners.eventLogging = function ( boundActions, schema ) {
|
module.exports = function ( boundActions, schema ) {
|
||||||
return function ( _, state ) {
|
return function ( _, state ) {
|
||||||
var eventLogging = state.eventLogging,
|
var eventLogging = state.eventLogging,
|
||||||
event = eventLogging.event;
|
event = eventLogging.event;
|
||||||
|
@ -25,4 +25,4 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}( mediaWiki, jQuery ) );
|
}( jQuery ) );
|
|
@ -51,7 +51,7 @@
|
||||||
* @param {Object} boundActions
|
* @param {Object} boundActions
|
||||||
* @return {ext.popups.ChangeListener}
|
* @return {ext.popups.ChangeListener}
|
||||||
*/
|
*/
|
||||||
mw.popups.changeListeners.footerLink = function ( boundActions ) {
|
module.exports = function ( boundActions ) {
|
||||||
var $footerLink;
|
var $footerLink;
|
||||||
|
|
||||||
return function ( prevState, state ) {
|
return function ( prevState, state ) {
|
10
build/ext.popups/changeListeners/index.js
Normal file
10
build/ext.popups/changeListeners/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
( function ( mw ) {
|
||||||
|
mw.popups.changeListeners = {
|
||||||
|
footerLink: require( './footerLink' ),
|
||||||
|
eventLogging: require( './eventLogging' ),
|
||||||
|
linkTitle: require( './linkTitle' ),
|
||||||
|
render: require( './render' ),
|
||||||
|
settings: require( './settings' ),
|
||||||
|
syncUserSettings: require( './syncUserSettings' )
|
||||||
|
};
|
||||||
|
}( mediaWiki ) );
|
|
@ -1,4 +1,4 @@
|
||||||
( function ( mw, $ ) {
|
( function ( $ ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of the link title change listener.
|
* Creates an instance of the link title change listener.
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
*
|
*
|
||||||
* @return {ext.popups.ChangeListener}
|
* @return {ext.popups.ChangeListener}
|
||||||
*/
|
*/
|
||||||
mw.popups.changeListeners.linkTitle = function () {
|
module.exports = function () {
|
||||||
var title;
|
var title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,4 +62,4 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}( mediaWiki, jQuery ) );
|
}( jQuery ) );
|
|
@ -6,7 +6,7 @@
|
||||||
* @param {ext.popups.PreviewBehavior} previewBehavior
|
* @param {ext.popups.PreviewBehavior} previewBehavior
|
||||||
* @return {ext.popups.ChangeListener}
|
* @return {ext.popups.ChangeListener}
|
||||||
*/
|
*/
|
||||||
mw.popups.changeListeners.render = function ( previewBehavior ) {
|
module.exports = function ( previewBehavior ) {
|
||||||
var preview;
|
var preview;
|
||||||
|
|
||||||
return function ( prevState, state ) {
|
return function ( prevState, state ) {
|
44
build/ext.popups/changeListeners/settings.js
Normal file
44
build/ext.popups/changeListeners/settings.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Creates an instance of the settings change listener.
|
||||||
|
*
|
||||||
|
* @param {Object} boundActions
|
||||||
|
* @param {Object} render function that renders a jQuery el with the settings
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( boundActions, render ) {
|
||||||
|
var settings;
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
if ( !prevState ) {
|
||||||
|
// Nothing to do on initialization
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update global modal visibility
|
||||||
|
if (
|
||||||
|
prevState.settings.shouldShow === false &&
|
||||||
|
state.settings.shouldShow === true
|
||||||
|
) {
|
||||||
|
// Lazily instantiate the settings UI
|
||||||
|
if ( !settings ) {
|
||||||
|
settings = render( boundActions );
|
||||||
|
settings.appendTo( document.body );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the UI settings with the current settings
|
||||||
|
settings.setEnabled( state.preview.enabled );
|
||||||
|
|
||||||
|
settings.show();
|
||||||
|
} else if (
|
||||||
|
prevState.settings.shouldShow === true &&
|
||||||
|
state.settings.shouldShow === false
|
||||||
|
) {
|
||||||
|
settings.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update help visibility
|
||||||
|
if ( prevState.settings.showHelp !== state.settings.showHelp ) {
|
||||||
|
settings.toggleHelp( state.settings.showHelp );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
60
build/ext.popups/changeListeners/syncUserSettings.js
Normal file
60
build/ext.popups/changeListeners/syncUserSettings.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Creates an instance of the user settings sync change listener.
|
||||||
|
*
|
||||||
|
* This change listener syncs certain parts of the state tree to user
|
||||||
|
* settings when they change.
|
||||||
|
*
|
||||||
|
* Used for:
|
||||||
|
*
|
||||||
|
* * Enabled state: If the previews are enabled or disabled.
|
||||||
|
* * Preview count: When the user dwells on a link for long enough that
|
||||||
|
* a preview is shown, then their preview count will be incremented (see
|
||||||
|
* `mw.popups.reducers.eventLogging`, and is persisted to local storage.
|
||||||
|
*
|
||||||
|
* @param {ext.popups.UserSettings} userSettings
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( userSettings ) {
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
|
||||||
|
syncIfChanged(
|
||||||
|
prevState, state, 'eventLogging', 'previewCount',
|
||||||
|
userSettings.setPreviewCount
|
||||||
|
);
|
||||||
|
syncIfChanged(
|
||||||
|
prevState, state, 'preview', 'enabled',
|
||||||
|
userSettings.setIsEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a state tree, reducer and property, safely return the value of the
|
||||||
|
* property if the reducer and property exist
|
||||||
|
* @param {Object} state tree
|
||||||
|
* @param {String} reducer key to access on the state tree
|
||||||
|
* @param {String} prop key to access on the reducer key of the state tree
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function get( state, reducer, prop ) {
|
||||||
|
return state[ reducer ] && state[ reducer ][ prop ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a sync function if the property prop on the property reducer on
|
||||||
|
* the state trees has changed value.
|
||||||
|
* @param {Object} prevState
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {String} reducer key to access on the state tree
|
||||||
|
* @param {String} prop key to access on the reducer key of the state tree
|
||||||
|
* @param {Function} sync function to be called with the newest value if
|
||||||
|
* changed
|
||||||
|
*/
|
||||||
|
function syncIfChanged( prevState, state, reducer, prop, sync ) {
|
||||||
|
var current = get( state, reducer, prop );
|
||||||
|
if ( prevState && ( get( prevState, reducer, prop ) !== current ) ) {
|
||||||
|
sync( current );
|
||||||
|
}
|
||||||
|
}
|
63
doc/adr/0004-use-webpack.md
Normal file
63
doc/adr/0004-use-webpack.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# 4. Use webpack
|
||||||
|
|
||||||
|
Date: 02/02/2017
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Discussed by entire team, but predominately Sam Smith, Joaquin Hernandez and
|
||||||
|
Jon Robson.
|
||||||
|
|
||||||
|
As our JavaScript becomes more complex we are making it increasingly difficult
|
||||||
|
to maintain dependencies via extension.json. Dependencies and file order have
|
||||||
|
to be managed and every new file creation requires an edit to extension.json.
|
||||||
|
This slows down development. In Vagrant for instance NTFS file systems
|
||||||
|
experience slowdown when loading many files.
|
||||||
|
|
||||||
|
There are many tools that bundle JavaScript out there that can do this for us.
|
||||||
|
|
||||||
|
** Pros **
|
||||||
|
* mw.popups no longer needs to be exposed as a global object
|
||||||
|
* Dependency management is no longer a manual process but automated by webpack
|
||||||
|
* Would allow us to explore template pre-compiling
|
||||||
|
* More reliable debug via source map support
|
||||||
|
* For non-MediaWiki developers it should be easier to understand our
|
||||||
|
development workflow.
|
||||||
|
|
||||||
|
**Cons**
|
||||||
|
* There is now a build step. New developers to the extension may try to
|
||||||
|
directly edit the distribution files.
|
||||||
|
* Likely to be more merge conflicts, but this could be addressed by additional
|
||||||
|
tooling (e.g. post-merge build step)
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
There are various bundlers to choose from, but Webpack was chosen on the basis
|
||||||
|
that
|
||||||
|
1) It was easy to switch to another
|
||||||
|
2) It is popular and well maintained.
|
||||||
|
3) Many members of the team are familiar with it.
|
||||||
|
|
||||||
|
https://medium.com/@tomchentw/why-webpack-is-awesome-9691044b6b8e#.mi0mmz75y
|
||||||
|
provides a good write up.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
While we migrate directory structure is likely to go through a series of
|
||||||
|
changes. Specifically template loading is likely to change in future.
|
||||||
|
|
||||||
|
New JavaScript files should import and export other files via commonjs and
|
||||||
|
not rely on global variables.
|
||||||
|
|
||||||
|
extension.json still needs to be updated to point to modules in MediaWiki
|
||||||
|
core.
|
||||||
|
|
||||||
|
Care should be taken when including node module libraries to ensure they
|
||||||
|
are not loaded by other extensions.
|
||||||
|
|
||||||
|
Developers working on the repository are now required to run `npm run build`
|
||||||
|
in a pre-commit hook to ensure that the right JavaScript is sent to users.
|
||||||
|
|
|
@ -97,12 +97,7 @@
|
||||||
"resources/ext.popups/reducers/settings.js",
|
"resources/ext.popups/reducers/settings.js",
|
||||||
|
|
||||||
"resources/ext.popups/changeListener.js",
|
"resources/ext.popups/changeListener.js",
|
||||||
"resources/ext.popups/changeListeners/footerLink.js",
|
"resources/ext.popups/changeListeners/index.js",
|
||||||
"resources/ext.popups/changeListeners/linkTitle.js",
|
|
||||||
"resources/ext.popups/changeListeners/render.js",
|
|
||||||
"resources/ext.popups/changeListeners/eventLogging.js",
|
|
||||||
"resources/ext.popups/changeListeners/syncUserSettings.js",
|
|
||||||
"resources/ext.popups/changeListeners/settings.js",
|
|
||||||
|
|
||||||
"resources/ext.popups/settingsDialog.js",
|
"resources/ext.popups/settingsDialog.js",
|
||||||
"resources/ext.popups/pageVisibility.js",
|
"resources/ext.popups/pageVisibility.js",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "webpack",
|
||||||
"test": "grunt lint",
|
"test": "grunt lint",
|
||||||
"doc": "jsduck"
|
"doc": "jsduck"
|
||||||
},
|
},
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
"grunt-eslint": "19.0.0",
|
"grunt-eslint": "19.0.0",
|
||||||
"grunt-jsonlint": "1.1.0",
|
"grunt-jsonlint": "1.1.0",
|
||||||
"grunt-stylelint": "^0.6.0",
|
"grunt-stylelint": "^0.6.0",
|
||||||
"stylelint-config-wikimedia": "0.3.0"
|
"stylelint-config-wikimedia": "0.3.0",
|
||||||
|
"webpack": "^1.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
391
resources/ext.popups/changeListeners/index.js
Normal file
391
resources/ext.popups/changeListeners/index.js
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId])
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ exports: {},
|
||||||
|
/******/ id: moduleId,
|
||||||
|
/******/ loaded: false
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.loaded = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(0);
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ([
|
||||||
|
/* 0 */
|
||||||
|
/***/ function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
( function ( mw ) {
|
||||||
|
mw.popups.changeListeners = {
|
||||||
|
footerLink: __webpack_require__( 1 ),
|
||||||
|
eventLogging: __webpack_require__( 2 ),
|
||||||
|
linkTitle: __webpack_require__( 3 ),
|
||||||
|
render: __webpack_require__( 4 ),
|
||||||
|
settings: __webpack_require__( 5 ),
|
||||||
|
syncUserSettings: __webpack_require__( 6 )
|
||||||
|
};
|
||||||
|
}( mediaWiki ) );
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 1 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the link element and appends it to the footer element.
|
||||||
|
*
|
||||||
|
* The following elements are considered to be the footer element (highest
|
||||||
|
* priority to lowest):
|
||||||
|
*
|
||||||
|
* # `#footer-places`
|
||||||
|
* # `#f-list`
|
||||||
|
* # The parent element of `#footer li`, which is either an `ol` or `ul`.
|
||||||
|
*
|
||||||
|
* @return {jQuery} The link element
|
||||||
|
*/
|
||||||
|
function createFooterLink() {
|
||||||
|
var $link = $( '<li>' ).append(
|
||||||
|
$( '<a>' )
|
||||||
|
.attr( 'href', '#' )
|
||||||
|
.text( mw.message( 'popups-settings-enable' ).text() )
|
||||||
|
),
|
||||||
|
$footer;
|
||||||
|
|
||||||
|
// As yet, we don't know whether the link should be visible.
|
||||||
|
$link.hide();
|
||||||
|
|
||||||
|
// From https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js,
|
||||||
|
// which was written by Yair rand <https://en.wikipedia.org/wiki/User:Yair_rand>.
|
||||||
|
$footer = $( '#footer-places, #f-list' );
|
||||||
|
|
||||||
|
if ( $footer.length === 0 ) {
|
||||||
|
$footer = $( '#footer li' ).parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
$footer.append( $link );
|
||||||
|
|
||||||
|
return $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the footer link change listener.
|
||||||
|
*
|
||||||
|
* The change listener covers the following behaviour:
|
||||||
|
*
|
||||||
|
* * The "Enable previews" link (the "link") is appended to the footer menu
|
||||||
|
* (see `createFooterLink` above).
|
||||||
|
* * When Page Previews are disabled, then the link is shown; otherwise, the
|
||||||
|
* link is hidden.
|
||||||
|
* * When the user clicks the link, then the `showSettings` bound action
|
||||||
|
* creator is called.
|
||||||
|
*
|
||||||
|
* @param {Object} boundActions
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( boundActions ) {
|
||||||
|
var $footerLink;
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
if ( $footerLink === undefined ) {
|
||||||
|
$footerLink = createFooterLink();
|
||||||
|
$footerLink.click( function ( e ) {
|
||||||
|
e.preventDefault();
|
||||||
|
boundActions.showSettings();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( state.settings.shouldShowFooterLink ) {
|
||||||
|
$footerLink.show();
|
||||||
|
} else {
|
||||||
|
$footerLink.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}( mediaWiki, jQuery ) );
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 2 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
( function ( $ ) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the event logging change listener.
|
||||||
|
*
|
||||||
|
* When an event is enqueued to be logged it'll be logged using the schema.
|
||||||
|
* Since it's the responsibility of EventLogging (and the UA) to deliver
|
||||||
|
* logged events, the `EVENT_LOGGED` is immediately dispatched rather than
|
||||||
|
* waiting for some indicator of completion.
|
||||||
|
*
|
||||||
|
* @param {Object} boundActions
|
||||||
|
* @param {mw.eventLog.Schema} schema
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( boundActions, schema ) {
|
||||||
|
return function ( _, state ) {
|
||||||
|
var eventLogging = state.eventLogging,
|
||||||
|
event = eventLogging.event;
|
||||||
|
|
||||||
|
if ( event ) {
|
||||||
|
schema.log( $.extend( true, {}, eventLogging.baseData, event ) );
|
||||||
|
|
||||||
|
boundActions.eventLogged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}( jQuery ) );
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 3 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
( function ( $ ) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the link title change listener.
|
||||||
|
*
|
||||||
|
* While the user dwells on a link, then it becomes the active link. The
|
||||||
|
* change listener will remove a link's `title` attribute while it's the
|
||||||
|
* active link.
|
||||||
|
*
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function () {
|
||||||
|
var title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the title attribute of the element, storing its value in local
|
||||||
|
* state so that it can be restored later (see `restoreTitleAttr`).
|
||||||
|
*
|
||||||
|
* @param {Element} el
|
||||||
|
*/
|
||||||
|
function destroyTitleAttr( el ) {
|
||||||
|
var $el = $( el );
|
||||||
|
|
||||||
|
// Has the user dwelled on a link? If we've already removed its title
|
||||||
|
// attribute, then NOOP.
|
||||||
|
if ( title ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
title = $el.attr( 'title' );
|
||||||
|
|
||||||
|
$el.attr( 'title', '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the title attribute of the element.
|
||||||
|
*
|
||||||
|
* @param {Element} el
|
||||||
|
*/
|
||||||
|
function restoreTitleAttr( el ) {
|
||||||
|
$( el ).attr( 'title', title );
|
||||||
|
|
||||||
|
title = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
var hasPrevActiveLink = prevState && prevState.preview.activeLink;
|
||||||
|
|
||||||
|
if ( hasPrevActiveLink ) {
|
||||||
|
|
||||||
|
// Has the user dwelled on a link immediately after abandoning another
|
||||||
|
// (remembering that the ABANDON_END action is delayed by
|
||||||
|
// ~10e2 ms).
|
||||||
|
if ( prevState.preview.activeLink !== state.preview.activeLink ) {
|
||||||
|
restoreTitleAttr( prevState.preview.activeLink );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( state.preview.activeLink ) {
|
||||||
|
destroyTitleAttr( state.preview.activeLink );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}( jQuery ) );
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 4 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
( function ( mw ) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the render change listener.
|
||||||
|
*
|
||||||
|
* @param {ext.popups.PreviewBehavior} previewBehavior
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( previewBehavior ) {
|
||||||
|
var preview;
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
if ( state.preview.shouldShow && !preview ) {
|
||||||
|
preview = mw.popups.renderer.render( state.preview.fetchResponse );
|
||||||
|
preview.show( state.preview.activeEvent, previewBehavior );
|
||||||
|
} else if ( !state.preview.shouldShow && preview ) {
|
||||||
|
preview.hide();
|
||||||
|
preview = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}( mediaWiki ) );
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 5 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the settings change listener.
|
||||||
|
*
|
||||||
|
* @param {Object} boundActions
|
||||||
|
* @param {Object} render function that renders a jQuery el with the settings
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( boundActions, render ) {
|
||||||
|
var settings;
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
if ( !prevState ) {
|
||||||
|
// Nothing to do on initialization
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update global modal visibility
|
||||||
|
if (
|
||||||
|
prevState.settings.shouldShow === false &&
|
||||||
|
state.settings.shouldShow === true
|
||||||
|
) {
|
||||||
|
// Lazily instantiate the settings UI
|
||||||
|
if ( !settings ) {
|
||||||
|
settings = render( boundActions );
|
||||||
|
settings.appendTo( document.body );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the UI settings with the current settings
|
||||||
|
settings.setEnabled( state.preview.enabled );
|
||||||
|
|
||||||
|
settings.show();
|
||||||
|
} else if (
|
||||||
|
prevState.settings.shouldShow === true &&
|
||||||
|
state.settings.shouldShow === false
|
||||||
|
) {
|
||||||
|
settings.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update help visibility
|
||||||
|
if ( prevState.settings.showHelp !== state.settings.showHelp ) {
|
||||||
|
settings.toggleHelp( state.settings.showHelp );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 6 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the user settings sync change listener.
|
||||||
|
*
|
||||||
|
* This change listener syncs certain parts of the state tree to user
|
||||||
|
* settings when they change.
|
||||||
|
*
|
||||||
|
* Used for:
|
||||||
|
*
|
||||||
|
* * Enabled state: If the previews are enabled or disabled.
|
||||||
|
* * Preview count: When the user dwells on a link for long enough that
|
||||||
|
* a preview is shown, then their preview count will be incremented (see
|
||||||
|
* `mw.popups.reducers.eventLogging`, and is persisted to local storage.
|
||||||
|
*
|
||||||
|
* @param {ext.popups.UserSettings} userSettings
|
||||||
|
* @return {ext.popups.ChangeListener}
|
||||||
|
*/
|
||||||
|
module.exports = function ( userSettings ) {
|
||||||
|
|
||||||
|
return function ( prevState, state ) {
|
||||||
|
|
||||||
|
syncIfChanged(
|
||||||
|
prevState, state, 'eventLogging', 'previewCount',
|
||||||
|
userSettings.setPreviewCount
|
||||||
|
);
|
||||||
|
syncIfChanged(
|
||||||
|
prevState, state, 'preview', 'enabled',
|
||||||
|
userSettings.setIsEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a state tree, reducer and property, safely return the value of the
|
||||||
|
* property if the reducer and property exist
|
||||||
|
* @param {Object} state tree
|
||||||
|
* @param {String} reducer key to access on the state tree
|
||||||
|
* @param {String} prop key to access on the reducer key of the state tree
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function get( state, reducer, prop ) {
|
||||||
|
return state[ reducer ] && state[ reducer ][ prop ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a sync function if the property prop on the property reducer on
|
||||||
|
* the state trees has changed value.
|
||||||
|
* @param {Object} prevState
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {String} reducer key to access on the state tree
|
||||||
|
* @param {String} prop key to access on the reducer key of the state tree
|
||||||
|
* @param {Function} sync function to be called with the newest value if
|
||||||
|
* changed
|
||||||
|
*/
|
||||||
|
function syncIfChanged( prevState, state, reducer, prop, sync ) {
|
||||||
|
var current = get( state, reducer, prop );
|
||||||
|
if ( prevState && ( get( prevState, reducer, prop ) !== current ) ) {
|
||||||
|
sync( current );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }
|
||||||
|
/******/ ]);
|
||||||
|
//# sourceMappingURL=index.js.map
|
|
@ -1,48 +0,0 @@
|
||||||
( function ( mw ) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of the settings change listener.
|
|
||||||
*
|
|
||||||
* @param {Object} boundActions
|
|
||||||
* @param {Object} render function that renders a jQuery el with the settings
|
|
||||||
* @return {ext.popups.ChangeListener}
|
|
||||||
*/
|
|
||||||
mw.popups.changeListeners.settings = function ( boundActions, render ) {
|
|
||||||
var settings;
|
|
||||||
|
|
||||||
return function ( prevState, state ) {
|
|
||||||
if ( !prevState ) {
|
|
||||||
// Nothing to do on initialization
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update global modal visibility
|
|
||||||
if (
|
|
||||||
prevState.settings.shouldShow === false &&
|
|
||||||
state.settings.shouldShow === true
|
|
||||||
) {
|
|
||||||
// Lazily instantiate the settings UI
|
|
||||||
if ( !settings ) {
|
|
||||||
settings = render( boundActions );
|
|
||||||
settings.appendTo( document.body );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the UI settings with the current settings
|
|
||||||
settings.setEnabled( state.preview.enabled );
|
|
||||||
|
|
||||||
settings.show();
|
|
||||||
} else if (
|
|
||||||
prevState.settings.shouldShow === true &&
|
|
||||||
state.settings.shouldShow === false
|
|
||||||
) {
|
|
||||||
settings.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update help visibility
|
|
||||||
if ( prevState.settings.showHelp !== state.settings.showHelp ) {
|
|
||||||
settings.toggleHelp( state.settings.showHelp );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}( mediaWiki ) );
|
|
|
@ -1,64 +0,0 @@
|
||||||
( function ( mw ) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of the user settings sync change listener.
|
|
||||||
*
|
|
||||||
* This change listener syncs certain parts of the state tree to user
|
|
||||||
* settings when they change.
|
|
||||||
*
|
|
||||||
* Used for:
|
|
||||||
*
|
|
||||||
* * Enabled state: If the previews are enabled or disabled.
|
|
||||||
* * Preview count: When the user dwells on a link for long enough that
|
|
||||||
* a preview is shown, then their preview count will be incremented (see
|
|
||||||
* `mw.popups.reducers.eventLogging`, and is persisted to local storage.
|
|
||||||
*
|
|
||||||
* @param {ext.popups.UserSettings} userSettings
|
|
||||||
* @return {ext.popups.ChangeListener}
|
|
||||||
*/
|
|
||||||
mw.popups.changeListeners.syncUserSettings = function ( userSettings ) {
|
|
||||||
|
|
||||||
return function ( prevState, state ) {
|
|
||||||
|
|
||||||
syncIfChanged(
|
|
||||||
prevState, state, 'eventLogging', 'previewCount',
|
|
||||||
userSettings.setPreviewCount
|
|
||||||
);
|
|
||||||
syncIfChanged(
|
|
||||||
prevState, state, 'preview', 'enabled',
|
|
||||||
userSettings.setIsEnabled
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a state tree, reducer and property, safely return the value of the
|
|
||||||
* property if the reducer and property exist
|
|
||||||
* @param {Object} state tree
|
|
||||||
* @param {String} reducer key to access on the state tree
|
|
||||||
* @param {String} prop key to access on the reducer key of the state tree
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
function get( state, reducer, prop ) {
|
|
||||||
return state[ reducer ] && state[ reducer ][ prop ];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a sync function if the property prop on the property reducer on
|
|
||||||
* the state trees has changed value.
|
|
||||||
* @param {Object} prevState
|
|
||||||
* @param {Object} state
|
|
||||||
* @param {String} reducer key to access on the state tree
|
|
||||||
* @param {String} prop key to access on the reducer key of the state tree
|
|
||||||
* @param {Function} sync function to be called with the newest value if
|
|
||||||
* changed
|
|
||||||
*/
|
|
||||||
function syncIfChanged( prevState, state, reducer, prop, sync ) {
|
|
||||||
var current = get( state, reducer, prop );
|
|
||||||
if ( prevState && ( get( prevState, reducer, prop ) !== current ) ) {
|
|
||||||
sync( current );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}( mediaWiki ) );
|
|
19
webpack.config.js
Normal file
19
webpack.config.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
var path = require( 'path' );
|
||||||
|
var webpack = require( 'webpack' );
|
||||||
|
var PUBLIC_PATH = '/w/extensions/Popups';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
output: {
|
||||||
|
// The absolute path to the output directory.
|
||||||
|
path: path.resolve( __dirname, 'resources/' ),
|
||||||
|
devtoolModuleFilenameTemplate: PUBLIC_PATH + '/[resource-path]',
|
||||||
|
|
||||||
|
// Write each chunk (entries, here) to a file named after the entry, e.g.
|
||||||
|
// the "index" entry gets written to index.js.
|
||||||
|
filename: '/[name]/index.js'
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
'ext.popups/changeListeners': './build/ext.popups/changeListeners/index.js'
|
||||||
|
},
|
||||||
|
devtool: 'source-map'
|
||||||
|
};
|
Loading…
Reference in a new issue