build: Replace jscs+jshint with eslint
authorEd Sanders <esanders@wikimedia.org>
Tue, 15 Nov 2016 21:02:04 +0000 (21:02 +0000)
committerJames D. Forrester <jforrester@wikimedia.org>
Tue, 15 Nov 2016 23:15:52 +0000 (15:15 -0800)
Change-Id: Id0a23c03aabadfaf2ec705528ae4b3bd0908fa3e

78 files changed:
.eslintrc.json [new file with mode: 0644]
.jscsrc [deleted file]
.jshintignore [deleted file]
.jshintrc [deleted file]
Gruntfile.js
package.json
resources/src/jquery/jquery.accessKeyLabel.js
resources/src/jquery/jquery.autoEllipsis.js
resources/src/jquery/jquery.byteLimit.js
resources/src/jquery/jquery.color.js
resources/src/jquery/jquery.colorUtil.js
resources/src/jquery/jquery.hidpi.js
resources/src/jquery/jquery.highlightText.js
resources/src/jquery/jquery.localize.js
resources/src/jquery/jquery.mwExtension.js
resources/src/jquery/jquery.placeholder.js
resources/src/jquery/jquery.qunit.completenessTest.js
resources/src/jquery/jquery.spinner.js
resources/src/jquery/jquery.suggestions.js
resources/src/jquery/jquery.tablesorter.js
resources/src/jquery/jquery.textSelection.js
resources/src/mediawiki.action/mediawiki.action.edit.stash.js
resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js
resources/src/mediawiki.action/mediawiki.action.view.redirect.js
resources/src/mediawiki.language/languages/ga.js
resources/src/mediawiki.language/languages/he.js
resources/src/mediawiki.language/languages/hy.js
resources/src/mediawiki.language/mediawiki.language.js
resources/src/mediawiki.language/mediawiki.language.numbers.js
resources/src/mediawiki.legacy/protect.js
resources/src/mediawiki.legacy/wikibits.js
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.factory.js
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki.special/mediawiki.special.edittags.js
resources/src/mediawiki.special/mediawiki.special.upload.js
resources/src/mediawiki.widgets.datetime/DateTimeFormatter.js
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.js
resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
resources/src/mediawiki/ForeignApi.js
resources/src/mediawiki/api.js
resources/src/mediawiki/api/edit.js
resources/src/mediawiki/api/parse.js
resources/src/mediawiki/api/upload.js
resources/src/mediawiki/htmlform/hide-if.js
resources/src/mediawiki/htmlform/multiselect.js
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.Title.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.Upload.Dialog.js
resources/src/mediawiki/mediawiki.Uri.js
resources/src/mediawiki/mediawiki.checkboxtoggle.js
resources/src/mediawiki/mediawiki.confirmCloseWindow.js
resources/src/mediawiki/mediawiki.debug.js
resources/src/mediawiki/mediawiki.experiments.js
resources/src/mediawiki/mediawiki.feedback.js
resources/src/mediawiki/mediawiki.filewarning.js
resources/src/mediawiki/mediawiki.inspect.js
resources/src/mediawiki/mediawiki.jqueryMsg.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.log.js
resources/src/mediawiki/mediawiki.notification.js
resources/src/mediawiki/mediawiki.template.mustache.js
resources/src/mediawiki/mediawiki.user.js
resources/src/mediawiki/mediawiki.util.js
resources/src/mediawiki/mediawiki.viewport.js
resources/src/mediawiki/page/gallery-slideshow.js
resources/src/mediawiki/page/image-pagination.js
resources/src/mediawiki/page/patrol.ajax.js
resources/src/mediawiki/page/rollback.js
resources/src/mediawiki/page/startup.js
resources/src/moment-dmy.js
resources/src/moment-locale-overrides.js
resources/src/polyfill-nodeTypes.js
resources/src/startup.js

diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644 (file)
index 0000000..135d3b9
--- /dev/null
@@ -0,0 +1,19 @@
+{
+       "extends": "wikimedia",
+       "env": {
+               "browser": true,
+               "jquery": true,
+               "qunit": true
+       },
+       "globals": {
+               "require": false,
+               "module": false,
+               "mediaWiki": false,
+               "mwPerformance": false,
+               "OO": false
+       },
+       "rules": {
+               "dot-notation": 0,
+               "valid-jsdoc": 0
+       }
+}
diff --git a/.jscsrc b/.jscsrc
deleted file mode 100644 (file)
index 3f7e90d..0000000
--- a/.jscsrc
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-       "preset": "wikimedia",
-       "es3": true,
-
-       "requireVarDeclFirst": null,
-
-       "requireDotNotation": { "allExcept": [ "keywords" ] },
-       "jsDoc": {
-               "checkAnnotations": {
-                       "preset": "jsduck5",
-                       "extra": {
-                               "context": "some",
-                               "see": "some"
-                       }
-               },
-               "checkParamNames": true,
-               "checkRedundantAccess": true,
-               "checkRedundantReturns": true,
-               "checkTypes": "strictNativeCase",
-               "requireNewlineAfterDescription": true,
-               "requireParamTypes": true,
-               "requireReturnTypes": true
-       },
-
-       "excludeFiles": [
-               "docs/**",
-               "extensions/**",
-               "node_modules/**",
-               "resources/lib/**",
-               "resources/src/jquery.tipsy/**",
-               "resources/src/jquery/jquery.farbtastic.js",
-               "resources/src/mediawiki.libs/**",
-               "skins/**",
-               "vendor/**"
-       ]
-}
diff --git a/.jshintignore b/.jshintignore
deleted file mode 100644 (file)
index fdde7d0..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Generated documentation
-docs/**
-
-# third-party libs
-extensions/**
-node_modules/**
-resources/lib/**
-resources/src/jquery.tipsy/**
-resources/src/jquery/jquery.farbtastic.js
-resources/src/mediawiki.libs/**
-skins/**
-vendor/**
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644 (file)
index 518d5cb..0000000
--- a/.jshintrc
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-       // Enforcing
-       "bitwise": true,
-       "eqeqeq": true,
-       "esversion": 3,
-       "freeze": true,
-       "futurehostile": true,
-       "latedef": "nofunc",
-       "noarg": true,
-       "nonew": true,
-       "strict": false,
-       "undef": true,
-       "unused": true,
-
-       // Relaxing
-       "laxbreak": true,
-       "multistr": true,
-       "-W024": false,
-
-       // Environment
-       "browser": true,
-
-       "globals": {
-               "require": false,
-               "module": false,
-               "mediaWiki": true,
-               "JSON": true,
-               "OO": true,
-               "mwPerformance": true,
-               "jQuery": false,
-               "QUnit": false,
-               "sinon": false
-       }
-}
index a08db5c..b38bc9a 100644 (file)
@@ -1,33 +1,44 @@
-/*jshint node:true */
+/* eslint-env node */
+
 module.exports = function ( grunt ) {
-       grunt.loadNpmTasks( 'grunt-contrib-copy' );
-       grunt.loadNpmTasks( 'grunt-contrib-jshint' );
-       grunt.loadNpmTasks( 'grunt-stylelint' );
-       grunt.loadNpmTasks( 'grunt-contrib-watch' );
-       grunt.loadNpmTasks( 'grunt-banana-checker' );
-       grunt.loadNpmTasks( 'grunt-jscs' );
-       grunt.loadNpmTasks( 'grunt-jsonlint' );
-       grunt.loadNpmTasks( 'grunt-karma' );
 
        var wgServer = process.env.MW_SERVER,
                wgScriptPath = process.env.MW_SCRIPT_PATH,
                karmaProxy = {};
 
+       grunt.loadNpmTasks( 'grunt-banana-checker' );
+       grunt.loadNpmTasks( 'grunt-contrib-copy' );
+       grunt.loadNpmTasks( 'grunt-contrib-watch' );
+       grunt.loadNpmTasks( 'grunt-eslint' );
+       grunt.loadNpmTasks( 'grunt-jsonlint' );
+       grunt.loadNpmTasks( 'grunt-karma' );
+       grunt.loadNpmTasks( 'grunt-stylelint' );
+
        karmaProxy[ wgScriptPath ] = wgServer + wgScriptPath;
 
        grunt.initConfig( {
-               jshint: {
-                       options: {
-                               jshintrc: true
-                       },
-                       all: '.'
-               },
-               jscs: {
-                       all: '.'
+               eslint: {
+                       all: [
+                               '**/*.js',
+                               '!docs/**',
+                               '!tests/**',
+                               '!extensions/**',
+                               '!node_modules/**',
+                               '!resources/lib/**',
+                               '!resources/src/jquery.tipsy/**',
+                               '!resources/src/jquery/jquery.farbtastic.js',
+                               '!resources/src/mediawiki.libs/**',
+                               '!skins/**',
+                               '!vendor/**',
+                               // Skip functions aren't even parseable
+                               '!resources/src/dom-level2-skip.js',
+                               '!resources/src/es5-skip.js',
+                               '!resources/src/json-skip.js',
+                               '!resources/src/mediawiki.hidpi-skip.js'
+                       ]
                },
                jsonlint: {
                        all: [
-                               '.jscsrc',
                                '**/*.json',
                                '!{docs/js,extensions,node_modules,skins,vendor}/**'
                        ]
@@ -48,7 +59,7 @@ module.exports = function ( grunt ) {
                },
                watch: {
                        files: [
-                               '.{stylelintrc,jscsrc,jshintignore,jshintrc}',
+                               '.{stylelintrc,eslintrc.json}',
                                '**/*',
                                '!{docs,extensions,node_modules,skins,vendor}/**'
                        ],
@@ -103,7 +114,7 @@ module.exports = function ( grunt ) {
                return !!( process.env.MW_SERVER && process.env.MW_SCRIPT_PATH );
        } );
 
-       grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana', 'stylelint' ] );
+       grunt.registerTask( 'lint', [ 'eslint', 'banana', 'stylelint' ] );
        grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] );
 
        grunt.registerTask( 'test', [ 'lint' ] );
index 3b75c3a..efc6c9f 100644 (file)
@@ -6,12 +6,12 @@
     "postdoc": "grunt copy:jsduck"
   },
   "devDependencies": {
+    "eslint-config-wikimedia": "0.2.0",
     "grunt": "1.0.1",
     "grunt-banana-checker": "0.5.0",
     "grunt-contrib-copy": "1.0.0",
-    "grunt-contrib-jshint": "1.0.0",
     "grunt-contrib-watch": "1.0.0",
-    "grunt-jscs": "2.8.0",
+    "grunt-eslint": "19.0.0",
     "grunt-jsonlint": "1.0.7",
     "grunt-karma": "2.0.0",
     "grunt-stylelint": "0.6.0",
index a6106e4..63b599b 100644 (file)
  */
 ( function ( $, mw ) {
 
-// Cached access key modifiers for used browser
-var cachedAccessKeyModifiers,
-
-       // Whether to use 'test-' instead of correct prefix (used for testing)
-       useTestPrefix = false,
-
-       // tag names which can have a label tag
-       // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content
-       labelable = 'button, input, textarea, keygen, meter, output, progress, select';
-
-/**
- * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
- *
- * The result is dependant on the ua paramater or the current platform.
- * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
- * Valid key values that are returned can be: ctrl, alt, option, shift, esc
- *
- * @private
- * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
- * @return {Array} Array with 0 or more of the string values: ctrl, option, alt, shift, esc
- */
-function getAccessKeyModifiers( ua ) {
-       // use cached prefix if possible
-       if ( !ua && cachedAccessKeyModifiers ) {
-               return cachedAccessKeyModifiers;
-       }
+       // Cached access key modifiers for used browser
+       var cachedAccessKeyModifiers,
+
+               // Whether to use 'test-' instead of correct prefix (used for testing)
+               useTestPrefix = false,
+
+               // tag names which can have a label tag
+               // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content
+               labelable = 'button, input, textarea, keygen, meter, output, progress, select';
+
+       /**
+        * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
+        *
+        * The result is dependant on the ua paramater or the current platform.
+        * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
+        * Valid key values that are returned can be: ctrl, alt, option, shift, esc
+        *
+        * @private
+        * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
+        * @return {Array} Array with 0 or more of the string values: ctrl, option, alt, shift, esc
+        */
+       function getAccessKeyModifiers( ua ) {
+               var profile, accessKeyModifiers;
+
+               // use cached prefix if possible
+               if ( !ua && cachedAccessKeyModifiers ) {
+                       return cachedAccessKeyModifiers;
+               }
 
-       var profile = $.client.profile( ua ),
+               profile = $.client.profile( ua );
                accessKeyModifiers = [ 'alt' ];
 
-       // Classic Opera on any platform
-       if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
-               accessKeyModifiers = [ 'shift', 'esc' ];
-
-       // Chrome and modern Opera on any platform
-       } else if ( profile.name === 'chrome' || profile.name === 'opera' ) {
-               accessKeyModifiers = (
-                       profile.platform === 'mac'
-                               // Chrome on Mac
-                               ? [ 'ctrl', 'option' ]
-                               // Chrome on Windows or Linux
-                               // (both alt- and alt-shift work, but alt with E, D, F etc does not
-                               // work since they are browser shortcuts)
-                               : [ 'alt', 'shift' ]
-               );
-
-       // Non-Windows Safari with webkit_version > 526
-       } else if ( profile.platform !== 'win'
-               && profile.name === 'safari'
-               && profile.layoutVersion > 526
-       ) {
-               accessKeyModifiers = [ 'ctrl', 'alt' ];
-
-       // Safari/Konqueror on any platform, or any browser on Mac
-       // (but not Safari on Windows)
-       } else if ( !( profile.platform === 'win' && profile.name === 'safari' )
-               && ( profile.name === 'safari'
-               || profile.platform === 'mac'
-               || profile.name === 'konqueror' )
-       ) {
-               accessKeyModifiers = [ 'ctrl' ];
-
-       // Firefox/Iceweasel 2.x and later
-       } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' )
-               && profile.versionBase > '1'
-       ) {
-               accessKeyModifiers = [ 'alt', 'shift' ];
-       }
+               // Classic Opera on any platform
+               if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
+                       accessKeyModifiers = [ 'shift', 'esc' ];
+
+               // Chrome and modern Opera on any platform
+               } else if ( profile.name === 'chrome' || profile.name === 'opera' ) {
+                       accessKeyModifiers = (
+                               profile.platform === 'mac' ?
+                                       // Chrome on Mac
+                                       [ 'ctrl', 'option' ] :
+                                       // Chrome on Windows or Linux
+                                       // (both alt- and alt-shift work, but alt with E, D, F etc does not
+                                       // work since they are browser shortcuts)
+                                       [ 'alt', 'shift' ]
+                       );
+
+               // Non-Windows Safari with webkit_version > 526
+               } else if ( profile.platform !== 'win' &&
+                       profile.name === 'safari' &&
+                       profile.layoutVersion > 526
+               ) {
+                       accessKeyModifiers = [ 'ctrl', 'alt' ];
+
+               // Safari/Konqueror on any platform, or any browser on Mac
+               // (but not Safari on Windows)
+               } else if (
+                       !( profile.platform === 'win' && profile.name === 'safari' ) &&
+                       (
+                               profile.name === 'safari' ||
+                               profile.platform === 'mac' ||
+                               profile.name === 'konqueror'
+                       )
+               ) {
+                       accessKeyModifiers = [ 'ctrl' ];
+
+               // Firefox/Iceweasel 2.x and later
+               } else if (
+                       ( profile.name === 'firefox' || profile.name === 'iceweasel' ) &&
+                       profile.versionBase > '1'
+               ) {
+                       accessKeyModifiers = [ 'alt', 'shift' ];
+               }
 
-       // cache modifiers
-       if ( !ua ) {
-               cachedAccessKeyModifiers = accessKeyModifiers;
+               // cache modifiers
+               if ( !ua ) {
+                       cachedAccessKeyModifiers = accessKeyModifiers;
+               }
+               return accessKeyModifiers;
        }
-       return accessKeyModifiers;
-}
 
-/**
- * Get the access key label for an element.
- *
- * Will use native accessKeyLabel if available (currently only in Firefox 8+),
- * falls back to #getAccessKeyModifiers.
- *
- * @private
- * @param {HTMLElement} element Element to get the label for
- * @return {string} Access key label
- */
-function getAccessKeyLabel( element ) {
-       // abort early if no access key
-       if ( !element.accessKey ) {
-               return '';
-       }
-       // use accessKeyLabel if possible
-       // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
-       if ( !useTestPrefix && element.accessKeyLabel ) {
-               return element.accessKeyLabel;
+       /**
+        * Get the access key label for an element.
+        *
+        * Will use native accessKeyLabel if available (currently only in Firefox 8+),
+        * falls back to #getAccessKeyModifiers.
+        *
+        * @private
+        * @param {HTMLElement} element Element to get the label for
+        * @return {string} Access key label
+        */
+       function getAccessKeyLabel( element ) {
+               // abort early if no access key
+               if ( !element.accessKey ) {
+                       return '';
+               }
+               // use accessKeyLabel if possible
+               // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
+               if ( !useTestPrefix && element.accessKeyLabel ) {
+                       return element.accessKeyLabel;
+               }
+               return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
        }
-       return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
-}
-
-/**
- * Update the title for an element (on the element with the access key or it's label) to show
- * the correct access key label.
- *
- * @private
- * @param {HTMLElement} element Element with the accesskey
- * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
- */
-function updateTooltipOnElement( element, titleElement ) {
-       var oldTitle, parts, regexp, newTitle, accessKeyLabel;
 
-       oldTitle = titleElement.title;
-       if ( !oldTitle ) {
-               // don't add a title if the element didn't have one before
-               return;
-       }
+       /**
+        * Update the title for an element (on the element with the access key or it's label) to show
+        * the correct access key label.
+        *
+        * @private
+        * @param {HTMLElement} element Element with the accesskey
+        * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
+        */
+       function updateTooltipOnElement( element, titleElement ) {
+               var oldTitle, parts, regexp, newTitle, accessKeyLabel;
+
+               oldTitle = titleElement.title;
+               if ( !oldTitle ) {
+                       // don't add a title if the element didn't have one before
+                       return;
+               }
 
-       parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' );
-       regexp = new RegExp( $.map( parts, mw.RegExp.escape ).join( '.*?' ) + '$' );
-       newTitle = oldTitle.replace( regexp, '' );
-       accessKeyLabel = getAccessKeyLabel( element );
+               parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' );
+               regexp = new RegExp( $.map( parts, mw.RegExp.escape ).join( '.*?' ) + '$' );
+               newTitle = oldTitle.replace( regexp, '' );
+               accessKeyLabel = getAccessKeyLabel( element );
 
-       if ( accessKeyLabel ) {
-               // Should be build the same as in Linker::titleAttrib
-               newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel );
-       }
-       if ( oldTitle !== newTitle ) {
-               titleElement.title = newTitle;
+               if ( accessKeyLabel ) {
+                       // Should be build the same as in Linker::titleAttrib
+                       newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel );
+               }
+               if ( oldTitle !== newTitle ) {
+                       titleElement.title = newTitle;
+               }
        }
-}
 
-/**
- * Update the title for an element to show the correct access key label.
- *
- * @private
- * @param {HTMLElement} element Element with the accesskey
- */
-function updateTooltip( element ) {
-       var id, $element, $label, $labelParent;
-       updateTooltipOnElement( element, element );
-
-       // update associated label if there is one
-       $element = $( element );
-       if ( $element.is( labelable ) ) {
-               // Search it using 'for' attribute
-               id = element.id.replace( /"/g, '\\"' );
-               if ( id ) {
-                       $label = $( 'label[for="' + id + '"]' );
-                       if ( $label.length === 1 ) {
-                               updateTooltipOnElement( element, $label[ 0 ] );
+       /**
+        * Update the title for an element to show the correct access key label.
+        *
+        * @private
+        * @param {HTMLElement} element Element with the accesskey
+        */
+       function updateTooltip( element ) {
+               var id, $element, $label, $labelParent;
+               updateTooltipOnElement( element, element );
+
+               // update associated label if there is one
+               $element = $( element );
+               if ( $element.is( labelable ) ) {
+                       // Search it using 'for' attribute
+                       id = element.id.replace( /"/g, '\\"' );
+                       if ( id ) {
+                               $label = $( 'label[for="' + id + '"]' );
+                               if ( $label.length === 1 ) {
+                                       updateTooltipOnElement( element, $label[ 0 ] );
+                               }
                        }
-               }
 
-               // Search it as parent, because the form control can also be inside the label element itself
-               $labelParent = $element.parents( 'label' );
-               if ( $labelParent.length === 1 ) {
-                       updateTooltipOnElement( element, $labelParent[ 0 ] );
+                       // Search it as parent, because the form control can also be inside the label element itself
+                       $labelParent = $element.parents( 'label' );
+                       if ( $labelParent.length === 1 ) {
+                               updateTooltipOnElement( element, $labelParent[ 0 ] );
+                       }
                }
        }
-}
-
-/**
- * Update the titles for all elements in a jQuery selection.
- *
- * @return {jQuery}
- * @chainable
- */
-$.fn.updateTooltipAccessKeys = function () {
-       return this.each( function () {
-               updateTooltip( this );
-       } );
-};
 
-/**
- * getAccessKeyModifiers
- *
- * @method updateTooltipAccessKeys_getAccessKeyModifiers
- * @inheritdoc #getAccessKeyModifiers
- */
-$.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
-
-/**
- * getAccessKeyLabel
- *
- * @method updateTooltipAccessKeys_getAccessKeyLabel
- * @inheritdoc #getAccessKeyLabel
- */
-$.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
-
-/**
- * getAccessKeyPrefix
- *
- * @method updateTooltipAccessKeys_getAccessKeyPrefix
- * @deprecated since 1.27 Use #getAccessKeyModifiers
- */
-$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
-       return getAccessKeyModifiers( ua ).join( '-' ) + '-';
-};
-
-/**
- * Switch test mode on and off.
- *
- * @method updateTooltipAccessKeys_setTestMode
- * @param {boolean} mode New mode
- */
-$.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) {
-       useTestPrefix = mode;
-};
-
-/**
- * @class jQuery
- * @mixins jQuery.plugin.accessKeyLabel
- */
+       /**
+        * Update the titles for all elements in a jQuery selection.
+        *
+        * @return {jQuery}
+        * @chainable
+        */
+       $.fn.updateTooltipAccessKeys = function () {
+               return this.each( function () {
+                       updateTooltip( this );
+               } );
+       };
+
+       /**
+        * getAccessKeyModifiers
+        *
+        * @method updateTooltipAccessKeys_getAccessKeyModifiers
+        * @inheritdoc #getAccessKeyModifiers
+        */
+       $.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
+
+       /**
+        * getAccessKeyLabel
+        *
+        * @method updateTooltipAccessKeys_getAccessKeyLabel
+        * @inheritdoc #getAccessKeyLabel
+        */
+       $.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
+
+       /**
+        * getAccessKeyPrefix
+        *
+        * @method updateTooltipAccessKeys_getAccessKeyPrefix
+        * @deprecated since 1.27 Use #getAccessKeyModifiers
+        */
+       $.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
+               return getAccessKeyModifiers( ua ).join( '-' ) + '-';
+       };
+
+       /**
+        * Switch test mode on and off.
+        *
+        * @method updateTooltipAccessKeys_setTestMode
+        * @param {boolean} mode New mode
+        */
+       $.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) {
+               useTestPrefix = mode;
+       };
+
+       /**
+        * @class jQuery
+        * @mixins jQuery.plugin.accessKeyLabel
+        */
 
 }( jQuery, mediaWiki ) );
index fd7e8d1..8716b69 100644 (file)
  */
 ( function ( $ ) {
 
-var
-       // Cache ellipsed substrings for every string-width-position combination
-       cache = {},
+       var
+               // Cache ellipsed substrings for every string-width-position combination
+               cache = {},
 
-       // Use a separate cache when match highlighting is enabled
-       matchTextCache = {};
+               // Use a separate cache when match highlighting is enabled
+               matchTextCache = {};
 
-// Due to <https://github.com/jscs-dev/jscs-jsdoc/issues/136>
-// jscs:disable jsDoc
-/**
- * Automatically truncate the plain text contents of an element and add an ellipsis
- *
- * @param {Object} options
- * @param {'left'|'center'|'right'} [options.position='center'] Where to remove text.
- * @param {boolean} [options.tooltip=false] Whether to show a tooltip with the remainder
- * of the text.
- * @param {boolean} [options.restoreText=false] Whether to save the text for restoring
- * later.
- * @param {boolean} [options.hasSpan=false] Whether the element is already a container,
- * or if the library should create a new container for it.
- * @param {string|null} [options.matchText=null] Text to highlight, e.g. search terms.
- * @return {jQuery}
- * @chainable
- */
-$.fn.autoEllipsis = function ( options ) {
-       options = $.extend( {
-               position: 'center',
-               tooltip: false,
-               restoreText: false,
-               hasSpan: false,
-               matchText: null
-       }, options );
+       // Due to <https://github.com/jscs-dev/jscs-jsdoc/issues/136>
+       // jscs:disable jsDoc
+       /**
       * Automatically truncate the plain text contents of an element and add an ellipsis
       *
       * @param {Object} options
       * @param {'left'|'center'|'right'} [options.position='center'] Where to remove text.
       * @param {boolean} [options.tooltip=false] Whether to show a tooltip with the remainder
       * of the text.
       * @param {boolean} [options.restoreText=false] Whether to save the text for restoring
       * later.
       * @param {boolean} [options.hasSpan=false] Whether the element is already a container,
       * or if the library should create a new container for it.
       * @param {string|null} [options.matchText=null] Text to highlight, e.g. search terms.
       * @return {jQuery}
       * @chainable
       */
+       $.fn.autoEllipsis = function ( options ) {
+               options = $.extend( {
+                       position: 'center',
+                       tooltip: false,
+                       restoreText: false,
+                       hasSpan: false,
+                       matchText: null
+               }, options );
 
-       return this.each( function () {
-               var $trimmableText,
-                       text, trimmableText, w, pw,
-                       l, r, i, side, m,
-                       // container element - used for measuring against
-                       $container = $( this );
+               return this.each( function () {
+                       var $trimmableText,
+                               text, trimmableText, w, pw,
+                               l, r, i, side, m,
+                               // container element - used for measuring against
+                               $container = $( this );
 
-               if ( options.restoreText ) {
-                       if ( !$container.data( 'autoEllipsis.originalText' ) ) {
-                               $container.data( 'autoEllipsis.originalText', $container.text() );
-                       } else {
-                               $container.text( $container.data( 'autoEllipsis.originalText' ) );
+                       if ( options.restoreText ) {
+                               if ( !$container.data( 'autoEllipsis.originalText' ) ) {
+                                       $container.data( 'autoEllipsis.originalText', $container.text() );
+                               } else {
+                                       $container.text( $container.data( 'autoEllipsis.originalText' ) );
+                               }
                        }
-               }
 
-               // trimmable text element - only the text within this element will be trimmed
-               if ( options.hasSpan ) {
-                       $trimmableText = $container.children( options.selector );
-               } else {
-                       $trimmableText = $( '<span>' )
-                               .css( 'whiteSpace', 'nowrap' )
-                               .text( $container.text() );
-                       $container
-                               .empty()
-                               .append( $trimmableText );
-               }
+                       // trimmable text element - only the text within this element will be trimmed
+                       if ( options.hasSpan ) {
+                               $trimmableText = $container.children( options.selector );
+                       } else {
+                               $trimmableText = $( '<span>' )
+                                       .css( 'whiteSpace', 'nowrap' )
+                                       .text( $container.text() );
+                               $container
+                                       .empty()
+                                       .append( $trimmableText );
+                       }
 
-               text = $container.text();
-               trimmableText = $trimmableText.text();
-               w = $container.width();
-               pw = 0;
+                       text = $container.text();
+                       trimmableText = $trimmableText.text();
+                       w = $container.width();
+                       pw = 0;
 
-               // Try cache
-               if ( options.matchText ) {
-                       if ( !( text in matchTextCache ) ) {
-                               matchTextCache[ text ] = {};
-                       }
-                       if ( !( options.matchText in matchTextCache[ text ] ) ) {
-                               matchTextCache[ text ][ options.matchText ] = {};
-                       }
-                       if ( !( w in matchTextCache[ text ][ options.matchText ] ) ) {
-                               matchTextCache[ text ][ options.matchText ][ w ] = {};
-                       }
-                       if ( options.position in matchTextCache[ text ][ options.matchText ][ w ] ) {
-                               $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] );
-                               if ( options.tooltip ) {
-                                       $container.attr( 'title', text );
+                       // Try cache
+                       if ( options.matchText ) {
+                               if ( !( text in matchTextCache ) ) {
+                                       matchTextCache[ text ] = {};
                                }
-                               return;
-                       }
-               } else {
-                       if ( !( text in cache ) ) {
-                               cache[ text ] = {};
-                       }
-                       if ( !( w in cache[ text ] ) ) {
-                               cache[ text ][ w ] = {};
-                       }
-                       if ( options.position in cache[ text ][ w ] ) {
-                               $container.html( cache[ text ][ w ][ options.position ] );
-                               if ( options.tooltip ) {
-                                       $container.attr( 'title', text );
+                               if ( !( options.matchText in matchTextCache[ text ] ) ) {
+                                       matchTextCache[ text ][ options.matchText ] = {};
+                               }
+                               if ( !( w in matchTextCache[ text ][ options.matchText ] ) ) {
+                                       matchTextCache[ text ][ options.matchText ][ w ] = {};
+                               }
+                               if ( options.position in matchTextCache[ text ][ options.matchText ][ w ] ) {
+                                       $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] );
+                                       if ( options.tooltip ) {
+                                               $container.attr( 'title', text );
+                                       }
+                                       return;
+                               }
+                       } else {
+                               if ( !( text in cache ) ) {
+                                       cache[ text ] = {};
+                               }
+                               if ( !( w in cache[ text ] ) ) {
+                                       cache[ text ][ w ] = {};
+                               }
+                               if ( options.position in cache[ text ][ w ] ) {
+                                       $container.html( cache[ text ][ w ][ options.position ] );
+                                       if ( options.tooltip ) {
+                                               $container.attr( 'title', text );
+                                       }
+                                       return;
                                }
-                               return;
                        }
-               }
 
-               if ( $trimmableText.width() + pw > w ) {
-                       switch ( options.position ) {
-                               case 'right':
-                                       // Use binary search-like technique for efficiency
-                                       l = 0;
-                                       r = trimmableText.length;
-                                       do {
-                                               m = Math.ceil( ( l + r ) / 2 );
-                                               $trimmableText.text( trimmableText.slice( 0, m ) + '...' );
-                                               if ( $trimmableText.width() + pw > w ) {
-                                                       // Text is too long
-                                                       r = m - 1;
-                                               } else {
-                                                       l = m;
+                       if ( $trimmableText.width() + pw > w ) {
+                               switch ( options.position ) {
+                                       case 'right':
+                                               // Use binary search-like technique for efficiency
+                                               l = 0;
+                                               r = trimmableText.length;
+                                               do {
+                                                       m = Math.ceil( ( l + r ) / 2 );
+                                                       $trimmableText.text( trimmableText.slice( 0, m ) + '...' );
+                                                       if ( $trimmableText.width() + pw > w ) {
+                                                               // Text is too long
+                                                               r = m - 1;
+                                                       } else {
+                                                               l = m;
+                                                       }
+                                               } while ( l < r );
+                                               $trimmableText.text( trimmableText.slice( 0, l ) + '...' );
+                                               break;
+                                       case 'center':
+                                               // TODO: Use binary search like for 'right'
+                                               i = [ Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 ) ];
+                                               // Begin with making the end shorter
+                                               side = 1;
+                                               while ( $trimmableText.outerWidth() + pw > w && i[ 0 ] > 0 ) {
+                                                       $trimmableText.text( trimmableText.slice( 0, i[ 0 ] ) + '...' + trimmableText.slice( i[ 1 ] ) );
+                                                       // Alternate between trimming the end and begining
+                                                       if ( side === 0 ) {
+                                                               // Make the begining shorter
+                                                               i[ 0 ]--;
+                                                               side = 1;
+                                                       } else {
+                                                               // Make the end shorter
+                                                               i[ 1 ]++;
+                                                               side = 0;
+                                                       }
                                                }
-                                       } while ( l < r );
-                                       $trimmableText.text( trimmableText.slice( 0, l ) + '...' );
-                                       break;
-                               case 'center':
-                                       // TODO: Use binary search like for 'right'
-                                       i = [ Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 ) ];
-                                       // Begin with making the end shorter
-                                       side = 1;
-                                       while ( $trimmableText.outerWidth() + pw > w && i[ 0 ] > 0 ) {
-                                               $trimmableText.text( trimmableText.slice( 0, i[ 0 ] ) + '...' + trimmableText.slice( i[ 1 ] ) );
-                                               // Alternate between trimming the end and begining
-                                               if ( side === 0 ) {
-                                                       // Make the begining shorter
-                                                       i[ 0 ]--;
-                                                       side = 1;
-                                               } else {
-                                                       // Make the end shorter
-                                                       i[ 1 ]++;
-                                                       side = 0;
+                                               break;
+                                       case 'left':
+                                               // TODO: Use binary search like for 'right'
+                                               r = 0;
+                                               while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) {
+                                                       $trimmableText.text( '...' + trimmableText.slice( r ) );
+                                                       r++;
                                                }
-                                       }
-                                       break;
-                               case 'left':
-                                       // TODO: Use binary search like for 'right'
-                                       r = 0;
-                                       while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) {
-                                               $trimmableText.text( '...' + trimmableText.slice( r ) );
-                                               r++;
-                                       }
-                                       break;
+                                               break;
+                               }
+                       }
+                       if ( options.tooltip ) {
+                               $container.attr( 'title', text );
+                       }
+                       if ( options.matchText ) {
+                               $container.highlightText( options.matchText );
+                               matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html();
+                       } else {
+                               cache[ text ][ w ][ options.position ] = $container.html();
                        }
-               }
-               if ( options.tooltip ) {
-                       $container.attr( 'title', text );
-               }
-               if ( options.matchText ) {
-                       $container.highlightText( options.matchText );
-                       matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html();
-               } else {
-                       cache[ text ][ w ][ options.position ] = $container.html();
-               }
 
-       } );
-};
-// jscs:enable jsDoc
+               } );
+       };
+       // jscs:enable jsDoc
 
-/**
- * @class jQuery
- * @mixins jQuery.plugin.autoEllipsis
- */
+       /**
       * @class jQuery
       * @mixins jQuery.plugin.autoEllipsis
       */
 
 }( jQuery ) );
index b1692bb..567bec8 100644 (file)
@@ -3,6 +3,17 @@
  */
 ( function ( $ ) {
 
+       var eventKeys = [
+               'keyup.byteLimit',
+               'keydown.byteLimit',
+               'change.byteLimit',
+               'mouseup.byteLimit',
+               'cut.byteLimit',
+               'paste.byteLimit',
+               'focus.byteLimit',
+               'blur.byteLimit'
+       ].join( ' ' );
+
        /**
         * Utility function to trim down a string, based on byteLimit
         * and given a safe start position. It supports insertion anywhere
                };
        };
 
-       var eventKeys = [
-               'keyup.byteLimit',
-               'keydown.byteLimit',
-               'change.byteLimit',
-               'mouseup.byteLimit',
-               'cut.byteLimit',
-               'paste.byteLimit',
-               'focus.byteLimit',
-               'blur.byteLimit'
-       ].join( ' ' );
-
        /**
         * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well,
         * when, for example, a database field has a byte limit rather than a character limit.
index a3cc8fc..70dc105 100644 (file)
@@ -10,7 +10,6 @@
 ( function ( $ ) {
 
        function getColor( elem, attr ) {
-               /*jshint boss:true */
                var color;
 
                do {
@@ -22,6 +21,7 @@
                        }
 
                        attr = 'backgroundColor';
+               // eslint-disable-next-line no-cond-assign
                } while ( elem = elem.parentNode );
 
                return $.colorUtil.getRGB( color );
index c14f2c8..c53ec3b 100644 (file)
@@ -23,7 +23,6 @@
                 * @return {Array}
                 */
                getRGB: function ( color ) {
-                       /*jshint boss:true */
                        var result;
 
                        // Check if we're already dealing with an array of colors
@@ -32,6 +31,7 @@
                        }
 
                        // Look for rgb(num,num,num)
+                       // eslint-disable-next-line no-cond-assign
                        if ( result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec( color ) ) {
                                return [
                                        parseInt( result[ 1 ], 10 ),
@@ -41,6 +41,7 @@
                        }
 
                        // Look for rgb(num%,num%,num%)
+                       // eslint-disable-next-line no-cond-assign
                        if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec( color ) ) {
                                return [
                                        parseFloat( result[ 1 ] ) * 2.55,
@@ -50,6 +51,7 @@
                        }
 
                        // Look for #a0b1c2
+                       // eslint-disable-next-line no-cond-assign
                        if ( result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec( color ) ) {
                                return [
                                        parseInt( result[ 1 ], 16 ),
@@ -59,6 +61,7 @@
                        }
 
                        // Look for #fff
+                       // eslint-disable-next-line no-cond-assign
                        if ( result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec( color ) ) {
                                return [
                                        parseInt( result[ 1 ] + result[ 1 ], 16 ),
@@ -68,6 +71,7 @@
                        }
 
                        // Look for rgba(0, 0, 0, 0) == transparent in Safari 3
+                       // eslint-disable-next-line no-cond-assign
                        if ( result = /rgba\(0, 0, 0, 0\)/.exec( color ) ) {
                                return $.colorUtil.colors.transparent;
                        }
                 * @return {number[]} The HSL representation
                 */
                rgbToHsl: function ( r, g, b ) {
+                       var d, h, s, l, min, max;
+
                        r = r / 255;
                        g = g / 255;
                        b = b / 255;
 
-                       var d,
-                               max = Math.max( r, g, b ),
-                               min = Math.min( r, g, b ),
-                               h,
-                               s,
-                               l = ( max + min ) / 2;
+                       max = Math.max( r, g, b );
+                       min = Math.min( r, g, b );
+                       l = ( max + min ) / 2;
 
                        if ( max === min ) {
                                // achromatic
index 7d308f8..ed31240 100644 (file)
  */
 ( function ( $ ) {
 
-/**
- * Get reported or approximate device pixel ratio.
- *
- * - 1.0 means 1 CSS pixel is 1 hardware pixel
- * - 2.0 means 1 CSS pixel is 2 hardware pixels
- * - etc.
- *
- * Uses `window.devicePixelRatio` if available, or CSS media queries on IE.
- *
- * @static
- * @inheritable
- * @return {number} Device pixel ratio
- */
-$.devicePixelRatio = function () {
-       if ( window.devicePixelRatio !== undefined ) {
-               // Most web browsers:
-               // * WebKit/Blink (Safari, Chrome, Android browser, etc)
-               // * Opera
-               // * Firefox 18+
-               // * Microsoft Edge (Windows 10)
-               return window.devicePixelRatio;
-       } else if ( window.msMatchMedia !== undefined ) {
-               // Windows 8 desktops / tablets, probably Windows Phone 8
-               //
-               // IE 10/11 doesn't report pixel ratio directly, but we can get the
-               // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for
-               // simplicity, but you may get different values depending on zoom
-               // factor, size of screen and orientation in Metro IE.
-               if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) {
+       /**
+        * Get reported or approximate device pixel ratio.
+        *
+        * - 1.0 means 1 CSS pixel is 1 hardware pixel
+        * - 2.0 means 1 CSS pixel is 2 hardware pixels
+        * - etc.
+        *
+        * Uses `window.devicePixelRatio` if available, or CSS media queries on IE.
+        *
+        * @static
+        * @inheritable
+        * @return {number} Device pixel ratio
+        */
+       $.devicePixelRatio = function () {
+               if ( window.devicePixelRatio !== undefined ) {
+                       // Most web browsers:
+                       // * WebKit/Blink (Safari, Chrome, Android browser, etc)
+                       // * Opera
+                       // * Firefox 18+
+                       // * Microsoft Edge (Windows 10)
+                       return window.devicePixelRatio;
+               } else if ( window.msMatchMedia !== undefined ) {
+                       // Windows 8 desktops / tablets, probably Windows Phone 8
+                       //
+                       // IE 10/11 doesn't report pixel ratio directly, but we can get the
+                       // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for
+                       // simplicity, but you may get different values depending on zoom
+                       // factor, size of screen and orientation in Metro IE.
+                       if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) {
+                               return 2;
+                       } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) {
+                               return 1.5;
+                       } else {
+                               return 1;
+                       }
+               } else {
+                       // Legacy browsers...
+                       // Assume 1 if unknown.
+                       return 1;
+               }
+       };
+
+       /**
+        * Bracket a given device pixel ratio to one of [1, 1.5, 2].
+        *
+        * This is useful for grabbing images on the fly with sizes based on the display
+        * density, without causing slowdown and extra thumbnail renderings on devices
+        * that are slightly different from the most common sizes.
+        *
+        * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
+        * so will be consistent with default renderings.
+        *
+        * @static
+        * @inheritable
+        * @return {number} Device pixel ratio
+        */
+       $.bracketDevicePixelRatio = function ( baseRatio ) {
+               if ( baseRatio > 1.5 ) {
                        return 2;
-               } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) {
+               } else if ( baseRatio > 1 ) {
                        return 1.5;
                } else {
                        return 1;
                }
-       } else {
-               // Legacy browsers...
-               // Assume 1 if unknown.
-               return 1;
-       }
-};
-
-/**
- * Bracket a given device pixel ratio to one of [1, 1.5, 2].
- *
- * This is useful for grabbing images on the fly with sizes based on the display
- * density, without causing slowdown and extra thumbnail renderings on devices
- * that are slightly different from the most common sizes.
- *
- * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
- * so will be consistent with default renderings.
- *
- * @static
- * @inheritable
- * @return {number} Device pixel ratio
- */
-$.bracketDevicePixelRatio = function ( baseRatio ) {
-       if ( baseRatio > 1.5 ) {
-               return 2;
-       } else if ( baseRatio > 1 ) {
-               return 1.5;
-       } else {
-               return 1;
-       }
-};
+       };
 
-/**
- * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2].
- *
- * This is useful for grabbing images on the fly with sizes based on the display
- * density, without causing slowdown and extra thumbnail renderings on devices
- * that are slightly different from the most common sizes.
- *
- * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
- * so will be consistent with default renderings.
- *
- * - 1.0 means 1 CSS pixel is 1 hardware pixel
- * - 1.5 means 1 CSS pixel is 1.5 hardware pixels
- * - 2.0 means 1 CSS pixel is 2 hardware pixels
- *
- * @static
- * @inheritable
- * @return {number} Device pixel ratio
- */
-$.bracketedDevicePixelRatio = function () {
-       return $.bracketDevicePixelRatio( $.devicePixelRatio() );
-};
+       /**
       * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2].
       *
       * This is useful for grabbing images on the fly with sizes based on the display
       * density, without causing slowdown and extra thumbnail renderings on devices
       * that are slightly different from the most common sizes.
       *
       * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails,
       * so will be consistent with default renderings.
       *
       * - 1.0 means 1 CSS pixel is 1 hardware pixel
       * - 1.5 means 1 CSS pixel is 1.5 hardware pixels
       * - 2.0 means 1 CSS pixel is 2 hardware pixels
       *
       * @static
       * @inheritable
       * @return {number} Device pixel ratio
       */
+       $.bracketedDevicePixelRatio = function () {
+               return $.bracketDevicePixelRatio( $.devicePixelRatio() );
+       };
 
-/**
- * Implement responsive images based on srcset attributes, if browser has no
- * native srcset support.
- *
- * @return {jQuery} This selection
- * @chainable
- */
-$.fn.hidpi = function () {
-       var $target = this,
-               // TODO add support for dpi media query checks on Firefox, IE
-               devicePixelRatio = $.devicePixelRatio(),
-               testImage = new Image();
+       /**
       * Implement responsive images based on srcset attributes, if browser has no
       * native srcset support.
       *
       * @return {jQuery} This selection
       * @chainable
       */
+       $.fn.hidpi = function () {
+               var $target = this,
+                       // TODO add support for dpi media query checks on Firefox, IE
+                       devicePixelRatio = $.devicePixelRatio(),
+                       testImage = new Image();
 
-       if ( devicePixelRatio > 1 && testImage.srcset === undefined ) {
-               // No native srcset support.
-               $target.find( 'img' ).each( function () {
-                       var $img = $( this ),
-                               srcset = $img.attr( 'srcset' ),
-                               match;
-                       if ( typeof srcset === 'string' && srcset !== '' ) {
-                               match = $.matchSrcSet( devicePixelRatio, srcset );
-                               if ( match !== null ) {
-                                       $img.attr( 'src', match );
+               if ( devicePixelRatio > 1 && testImage.srcset === undefined ) {
+                       // No native srcset support.
+                       $target.find( 'img' ).each( function () {
+                               var $img = $( this ),
+                                       srcset = $img.attr( 'srcset' ),
+                                       match;
+                               if ( typeof srcset === 'string' && srcset !== '' ) {
+                                       match = $.matchSrcSet( devicePixelRatio, srcset );
+                                       if ( match !== null ) {
+                                               $img.attr( 'src', match );
+                                       }
                                }
-                       }
-               } );
-       }
+                       } );
+               }
 
-       return $target;
-};
+               return $target;
+       };
 
-/**
- * Match a srcset entry for the given device pixel ratio
- *
- * Exposed for testing.
- *
- * @private
- * @static
- * @param {number} devicePixelRatio
- * @param {string} srcset
- * @return {Mixed} null or the matching src string
- */
-$.matchSrcSet = function ( devicePixelRatio, srcset ) {
-       var candidates,
-               candidate,
-               bits,
-               src,
-               i,
-               ratioStr,
-               ratio,
-               selectedRatio = 1,
-               selectedSrc = null;
-       candidates = srcset.split( / *, */ );
-       for ( i = 0; i < candidates.length; i++ ) {
-               candidate = candidates[ i ];
-               bits = candidate.split( / +/ );
-               src = bits[ 0 ];
-               if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) {
-                       ratioStr = bits[ 1 ].slice( 0, -1 );
-                       ratio = parseFloat( ratioStr );
-                       if ( ratio <= devicePixelRatio && ratio > selectedRatio ) {
-                               selectedRatio = ratio;
-                               selectedSrc = src;
+       /**
+        * Match a srcset entry for the given device pixel ratio
+        *
+        * Exposed for testing.
+        *
+        * @private
+        * @static
+        * @param {number} devicePixelRatio
+        * @param {string} srcset
+        * @return {Mixed} null or the matching src string
+        */
+       $.matchSrcSet = function ( devicePixelRatio, srcset ) {
+               var candidates,
+                       candidate,
+                       bits,
+                       src,
+                       i,
+                       ratioStr,
+                       ratio,
+                       selectedRatio = 1,
+                       selectedSrc = null;
+               candidates = srcset.split( / *, */ );
+               for ( i = 0; i < candidates.length; i++ ) {
+                       candidate = candidates[ i ];
+                       bits = candidate.split( / +/ );
+                       src = bits[ 0 ];
+                       if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) {
+                               ratioStr = bits[ 1 ].slice( 0, -1 );
+                               ratio = parseFloat( ratioStr );
+                               if ( ratio <= devicePixelRatio && ratio > selectedRatio ) {
+                                       selectedRatio = ratio;
+                                       selectedSrc = src;
+                               }
                        }
                }
-       }
-       return selectedSrc;
-};
+               return selectedSrc;
+       };
 
-/**
- * @class jQuery
- * @mixins jQuery.plugin.hidpi
- */
+       /**
       * @class jQuery
       * @mixins jQuery.plugin.hidpi
       */
 
 }( jQuery ) );
index e37f19b..3feca81 100644 (file)
                                        // replace the matched node, with our span-wrapped clone of the matched node
                                        middlebit.parentNode.replaceChild( spannode, middlebit );
                                }
-                       } else if ( node.nodeType === Node.ELEMENT_NODE
+                       } else if (
+                               node.nodeType === Node.ELEMENT_NODE &&
                                // element with childnodes, and not a script, style or an element we created
-                               && node.childNodes
-                               && !/(script|style)/i.test( node.tagName )
-                               && !( node.tagName.toLowerCase() === 'span'
-                                       && node.className.match( /\bhighlight/ )
+                               node.childNodes &&
+                               !/(script|style)/i.test( node.tagName ) &&
+                               !(
+                                       node.tagName.toLowerCase() === 'span' &&
+                                       node.className.match( /\bhighlight/ )
                                )
                        ) {
                                for ( i = 0; i < node.childNodes.length; ++i ) {
index f5932b2..05b3891 100644 (file)
  */
 ( function ( $, mw ) {
 
-/**
- * Gets a localized message, using parameters from options if present.
- *
- * @ignore
- * @param {Object} options
- * @param {string} key
- * @return {string} Localized message
- */
-function msg( options, key ) {
-       var args = options.params[ key ] || [];
-       // Format: mw.msg( key [, p1, p2, ...] )
-       args.unshift( options.prefix + ( options.keys[ key ] || key ) );
-       return mw.msg.apply( mw, args );
-}
+       /**
       * Gets a localized message, using parameters from options if present.
       *
       * @ignore
       * @param {Object} options
       * @param {string} key
       * @return {string} Localized message
       */
+       function msg( options, key ) {
+               var args = options.params[ key ] || [];
+               // Format: mw.msg( key [, p1, p2, ...] )
+               args.unshift( options.prefix + ( options.keys[ key ] || key ) );
+               return mw.msg.apply( mw, args );
+       }
 
-/**
- * Localizes a DOM selection by replacing <html:msg /> elements with localized text and adding
- * localized title and alt attributes to elements with title-msg and alt-msg attributes
- * respectively.
- *
- * Call on a selection of HTML which contains `<html:msg key="message-key" />` elements or elements
- * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes.
- * `<html:msg />` elements will be replaced with localized text, *-msg attributes will be replaced
- * with attributes that do not have the "-msg" suffix and contain a localized message.
- *
- * Example:
- *     // Messages: { 'title': 'Awesome', 'desc': 'Cat doing backflip' 'search' contains 'Search' }
- *     var html = '\
- *         <p>\
- *             <html:msg key="title" />\
- *             <img src="something.jpg" title-msg="title" alt-msg="desc" />\
- *             <input type="text" placeholder-msg="search" />\
- *         </p>';
- *     $( 'body' ).append( $( html ).localize() );
- *
- * Appends something like this to the body...
- *     <p>
- *         Awesome
- *         <img src="something.jpg" title="Awesome" alt="Cat doing backflip" />
- *         <input type="text" placeholder="Search" />
- *     </p>
- *
- * Arguments can be passed into uses of a message using the params property of the options object
- * given to .localize(). Multiple messages can be given parameters, because the params property is
- * an object keyed by the message key to apply the parameters to, each containing an array of
- * parameters to use. The limitation is that you can not use different parameters to individual uses
- * of a message in the same selection being localized - they will all recieve the same parameters.
- *
- * Example:
- *     // Messages: { 'easy-as': 'Easy as $1 $2 $3.' }
- *     var html = '<p><html:msg key="easy-as" /></p>';
- *     $( 'body' ).append( $( html ).localize( { 'params': { 'easy-as': ['a', 'b', 'c'] } } ) );
- *
- * Appends something like this to the body...
- *     <p>Easy as a, b, c</p>
- *
- * Raw HTML content can be used, instead of it being escaped as text. To do this, just use the raw
- * attribute on a msg element.
- *
- * Example:
- *     // Messages: { 'hello': '<b><i>Hello</i> $1!</b>' }
- *     var html = '\
- *         <p>\
- *             <!-- escaped: --><html:msg key="hello" />\
- *             <!-- raw: --><html:msg key="hello" raw />\
- *         </p>';
- *     $( 'body' ).append( $( html ).localize( { 'params': { 'hello': ['world'] } } ) );
- *
- * Appends something like this to the body...
- *     <p>
- *         <!-- escaped: -->&lt;b&gt;&lt;i&gt;Hello&lt;/i&gt; world!&lt;/b&gt;
- *         <!-- raw: --><b><i>Hello</i> world!</b>
- *     </p>
- *
- * Message keys can also be remapped, allowing the same generic template to be used with a variety
- * of messages. This is important for improving re-usability of templates.
- *
- * Example:
- *     // Messages: { 'good-afternoon': 'Good afternoon' }
- *     var html = '<p><html:msg key="greeting" /></p>';
- *     $( 'body' ).append( $( html ).localize( { 'keys': { 'greeting': 'good-afternoon' } } ) );
- *
- * Appends something like this to the body...
- *     <p>Good afternoon</p>
- *
- * Message keys can also be prefixed globally, which is handy when writing extensions, where by
- * convention all messages are prefixed with the extension's name.
- *
- * Example:
- *     // Messages: { 'teleportation-warning': 'You may not get there all in one piece.' }
- *     var html = '<p><html:msg key="warning" /></p>';
- *     $( 'body' ).append( $( html ).localize( { 'prefix': 'teleportation-' } ) );
- *
- * Appends something like this to the body...
- *     <p>You may not get there all in one piece.</p>
- *
- * @param {Object} options Map of options to be used while localizing
- * @param {string} options.prefix String to prepend to all message keys
- * @param {Object} options.keys Message key aliases, used for remapping keys to a template
- * @param {Object} options.params Lists of parameters to use with certain message keys
- * @return {jQuery}
- * @chainable
- */
-$.fn.localize = function ( options ) {
-       var $target = this,
-               attributes = [ 'title', 'alt', 'placeholder' ];
+       /**
       * Localizes a DOM selection by replacing <html:msg /> elements with localized text and adding
       * localized title and alt attributes to elements with title-msg and alt-msg attributes
       * respectively.
       *
       * Call on a selection of HTML which contains `<html:msg key="message-key" />` elements or elements
       * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes.
       * `<html:msg />` elements will be replaced with localized text, *-msg attributes will be replaced
       * with attributes that do not have the "-msg" suffix and contain a localized message.
       *
       * Example:
       *     // Messages: { 'title': 'Awesome', 'desc': 'Cat doing backflip' 'search' contains 'Search' }
       *     var html = '\
       *         <p>\
       *             <html:msg key="title" />\
       *             <img src="something.jpg" title-msg="title" alt-msg="desc" />\
       *             <input type="text" placeholder-msg="search" />\
       *         </p>';
       *     $( 'body' ).append( $( html ).localize() );
       *
       * Appends something like this to the body...
       *     <p>
       *         Awesome
       *         <img src="something.jpg" title="Awesome" alt="Cat doing backflip" />
       *         <input type="text" placeholder="Search" />
       *     </p>
       *
       * Arguments can be passed into uses of a message using the params property of the options object
       * given to .localize(). Multiple messages can be given parameters, because the params property is
       * an object keyed by the message key to apply the parameters to, each containing an array of
       * parameters to use. The limitation is that you can not use different parameters to individual uses
       * of a message in the same selection being localized - they will all recieve the same parameters.
       *
       * Example:
       *     // Messages: { 'easy-as': 'Easy as $1 $2 $3.' }
       *     var html = '<p><html:msg key="easy-as" /></p>';
       *     $( 'body' ).append( $( html ).localize( { 'params': { 'easy-as': ['a', 'b', 'c'] } } ) );
       *
       * Appends something like this to the body...
       *     <p>Easy as a, b, c</p>
       *
       * Raw HTML content can be used, instead of it being escaped as text. To do this, just use the raw
       * attribute on a msg element.
       *
       * Example:
       *     // Messages: { 'hello': '<b><i>Hello</i> $1!</b>' }
       *     var html = '\
       *         <p>\
       *             <!-- escaped: --><html:msg key="hello" />\
       *             <!-- raw: --><html:msg key="hello" raw />\
       *         </p>';
       *     $( 'body' ).append( $( html ).localize( { 'params': { 'hello': ['world'] } } ) );
       *
       * Appends something like this to the body...
       *     <p>
       *         <!-- escaped: -->&lt;b&gt;&lt;i&gt;Hello&lt;/i&gt; world!&lt;/b&gt;
       *         <!-- raw: --><b><i>Hello</i> world!</b>
       *     </p>
       *
       * Message keys can also be remapped, allowing the same generic template to be used with a variety
       * of messages. This is important for improving re-usability of templates.
       *
       * Example:
       *     // Messages: { 'good-afternoon': 'Good afternoon' }
       *     var html = '<p><html:msg key="greeting" /></p>';
       *     $( 'body' ).append( $( html ).localize( { 'keys': { 'greeting': 'good-afternoon' } } ) );
       *
       * Appends something like this to the body...
       *     <p>Good afternoon</p>
       *
       * Message keys can also be prefixed globally, which is handy when writing extensions, where by
       * convention all messages are prefixed with the extension's name.
       *
       * Example:
       *     // Messages: { 'teleportation-warning': 'You may not get there all in one piece.' }
       *     var html = '<p><html:msg key="warning" /></p>';
       *     $( 'body' ).append( $( html ).localize( { 'prefix': 'teleportation-' } ) );
       *
       * Appends something like this to the body...
       *     <p>You may not get there all in one piece.</p>
       *
       * @param {Object} options Map of options to be used while localizing
       * @param {string} options.prefix String to prepend to all message keys
       * @param {Object} options.keys Message key aliases, used for remapping keys to a template
       * @param {Object} options.params Lists of parameters to use with certain message keys
       * @return {jQuery}
       * @chainable
       */
+       $.fn.localize = function ( options ) {
+               var $target = this,
+                       attributes = [ 'title', 'alt', 'placeholder' ];
 
-       // Extend options
-       options = $.extend( {
-               prefix: '',
-               keys: {},
-               params: {}
-       }, options );
+               // Extend options
+               options = $.extend( {
+                       prefix: '',
+                       keys: {},
+                       params: {}
+               }, options );
 
-       // Elements
-       // Ok, so here's the story on this selector. In IE 6/7, searching for 'msg' turns up the
-       // 'html:msg', but searching for 'html:msg' doesn't. In later IE and other browsers, searching
-       // for 'html:msg' turns up the 'html:msg', but searching for 'msg' doesn't. So searching for
-       // both 'msg' and 'html:msg' seems to get the job done. This feels pretty icky, though.
-       $target.find( 'msg,html\\:msg' ).each( function () {
-               var $el = $( this );
-               // Escape by default
-               if ( $el.attr( 'raw' ) ) {
-                       $el.html( msg( options, $el.attr( 'key' ) ) );
-               } else {
-                       $el.text( msg( options, $el.attr( 'key' ) ) );
-               }
-               // Remove wrapper
-               $el.replaceWith( $el.html() );
-       } );
-
-       // Attributes
-       // Note: there's no way to prevent escaping of values being injected into attributes, this is
-       // on purpose, not a design flaw.
-       $.each( attributes, function ( i, attr ) {
-               var msgAttr = attr + '-msg';
-               $target.find( '[' + msgAttr + ']' ).each( function () {
+               // Elements
+               // Ok, so here's the story on this selector. In IE 6/7, searching for 'msg' turns up the
+               // 'html:msg', but searching for 'html:msg' doesn't. In later IE and other browsers, searching
+               // for 'html:msg' turns up the 'html:msg', but searching for 'msg' doesn't. So searching for
+               // both 'msg' and 'html:msg' seems to get the job done. This feels pretty icky, though.
+               $target.find( 'msg,html\\:msg' ).each( function () {
                        var $el = $( this );
-                       $el.attr( attr, msg( options, $el.attr( msgAttr ) ) ).removeAttr( msgAttr );
+                       // Escape by default
+                       if ( $el.attr( 'raw' ) ) {
+                               $el.html( msg( options, $el.attr( 'key' ) ) );
+                       } else {
+                               $el.text( msg( options, $el.attr( 'key' ) ) );
+                       }
+                       // Remove wrapper
+                       $el.replaceWith( $el.html() );
                } );
-       } );
 
-       // HTML, Text for elements which cannot have children e.g. OPTION
-       $target.find( '[data-msg-text]' ).each( function () {
-               var $el = $( this );
-               $el.text( msg( options, $el.attr( 'data-msg-text' ) ) );
-       } );
+               // Attributes
+               // Note: there's no way to prevent escaping of values being injected into attributes, this is
+               // on purpose, not a design flaw.
+               $.each( attributes, function ( i, attr ) {
+                       var msgAttr = attr + '-msg';
+                       $target.find( '[' + msgAttr + ']' ).each( function () {
+                               var $el = $( this );
+                               $el.attr( attr, msg( options, $el.attr( msgAttr ) ) ).removeAttr( msgAttr );
+                       } );
+               } );
 
-       $target.find( '[data-msg-html]' ).each( function () {
-               var $el = $( this );
-               $el.html( msg( options, $el.attr( 'data-msg-html' ) ) );
-       } );
+               // HTML, Text for elements which cannot have children e.g. OPTION
+               $target.find( '[data-msg-text]' ).each( function () {
+                       var $el = $( this );
+                       $el.text( msg( options, $el.attr( 'data-msg-text' ) ) );
+               } );
+
+               $target.find( '[data-msg-html]' ).each( function () {
+                       var $el = $( this );
+                       $el.html( msg( options, $el.attr( 'data-msg-html' ) ) );
+               } );
 
-       return $target;
-};
+               return $target;
+       };
 
-// Let IE know about the msg tag before it's used...
-document.createElement( 'msg' );
+       // Let IE know about the msg tag before it's used...
+       document.createElement( 'msg' );
 
-/**
- * @class jQuery
- * @mixins jQuery.plugin.localize
- */
+       /**
       * @class jQuery
       * @mixins jQuery.plugin.localize
       */
 
 }( jQuery, mediaWiki ) );
index 27ceb2b..f9675fa 100644 (file)
                        return false;
                },
                compareArray: function ( arrThis, arrAgainst ) {
+                       var i;
                        if ( arrThis.length !== arrAgainst.length ) {
                                return false;
                        }
-                       for ( var i = 0; i < arrThis.length; i++ ) {
+                       for ( i = 0; i < arrThis.length; i++ ) {
                                if ( $.isArray( arrThis[ i ] ) ) {
                                        if ( !$.compareArray( arrThis[ i ], arrAgainst[ i ] ) ) {
                                                return false;
                return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
        }, 'Use mediawiki.RegExp instead.' );
 
-} )( jQuery, mediaWiki );
+}( jQuery, mediaWiki ) );
index c472ac7..ae9c943 100644 (file)
@@ -35,7 +35,7 @@
        function args( elem ) {
                // Return an object of element attributes
                var newAttrs = {},
-                               rinlinejQuery = /^jQuery\d+$/;
+                       rinlinejQuery = /^jQuery\d+$/;
                $.each( elem.attributes, function ( i, attr ) {
                        if ( attr.specified && !rinlinejQuery.test( attr.name ) ) {
                                newAttrs[ attr.name ] = attr.value;
@@ -46,7 +46,7 @@
 
        function clearPlaceholder( event, value ) {
                var input = this,
-                               $input = $( input );
+                       $input = $( input );
                if ( input.value === $input.attr( 'placeholder' ) && $input.hasClass( 'placeholder' ) ) {
                        if ( $input.data( 'placeholder-password' ) ) {
                                $input = $input.hide().next().show().attr( 'id', $input.removeAttr( 'id' ).data( 'placeholder-id' ) );
@@ -68,9 +68,9 @@
 
        function setPlaceholder() {
                var $replacement,
-                               input = this,
-                               $input = $( input ),
-                               id = this.id;
+                       input = this,
+                       $input = $( input ),
+                       id = this.id;
                if ( !input.value ) {
                        if ( input.type === 'password' ) {
                                if ( !$input.data( 'placeholder-textinput' ) ) {
 
        function changePlaceholder( text ) {
                var hasArgs = arguments.length,
-                               $input = this;
+                       $input = this;
                if ( hasArgs ) {
                        if ( $input.attr( 'placeholder' ) !== text ) {
                                $input.prop( 'placeholder', text );
index 8d263fb..68bf830 100644 (file)
@@ -17,9 +17,9 @@
 
        var util,
                hasOwn = Object.prototype.hasOwnProperty,
-               log = ( window.console && window.console.log )
-                       ? function () { return window.console.log.apply( window.console, arguments ); }
-                       function () {};
+               log = ( window.console && window.console.log ) ?
+                       function () { return window.console.log.apply( window.console, arguments ); } :
+                       function () {};
 
        // Simplified version of a few jQuery methods, except that they don't
        // call other jQuery methods. Required to be able to run the CompletenessTest
                } );
 
                QUnit.done( function () {
+                       var toolbar, testResults, cntTotal, cntCalled, cntMissing;
+
                        that.populateMissingTests();
                        log( 'CompletenessTest/populateMissingTests', that );
 
-                       var toolbar, testResults, cntTotal, cntCalled, cntMissing;
-
                        cntTotal = util.keys( that.injectionTracker ).length;
                        cntCalled = util.keys( that.methodCallTracker ).length;
                        cntMissing = util.keys( that.missingTests ).length;
                        // Make the spy inherit from the original so that its static methods are also
                        // visible in the spy (e.g. when we inject a check into mw.log, mw.log.warn
                        // must remain accessible).
-                       // XXX: https://github.com/jshint/jshint/issues/2656
-                       /*jshint ignore:start */
-                       /*jshint proto:true */
                        spy.__proto__ = val;
-                       /*jshint ignore:end */
 
                        // Objects are by reference, members (unless objects) are not.
                        obj[ key ] = spy;
index af5a97d..9079cc0 100644 (file)
@@ -59,6 +59,8 @@
                 * @return {jQuery}
                 */
                createSpinner: function ( opts ) {
+                       var $spinner;
+
                        if ( opts !== undefined && $.type( opts ) !== 'object' ) {
                                opts = {
                                        id: opts
@@ -67,7 +69,7 @@
 
                        opts = $.extend( {}, defaults, opts );
 
-                       var $spinner = $( '<div>' ).addClass( 'mw-spinner' ).attr( 'title', '...' );
+                       $spinner = $( '<div>' ).addClass( 'mw-spinner' ).attr( 'title', '...' );
                        if ( opts.id !== undefined ) {
                                $spinner.attr( 'id', 'mw-spinner-' + opts.id );
                        }
index 884ecb6..f3e4e09 100644 (file)
                                                        $results.empty();
                                                        expWidth = -1;
                                                        for ( i = 0; i < context.config.suggestions.length; i++ ) {
-                                                               /*jshint loopfunc:true */
                                                                text = context.config.suggestions[ i ];
                                                                $result = $( '<div>' )
                                                                        .addClass( 'suggestions-result' )
                                                        46, // delete
                                                        8   // backspace
                                                ];
-                                               if ( context.data.keypressedCount === 0
-                                                       && e.which === context.data.keypressed
-                                                       && $.inArray( e.which, allowed ) !== -1
+                                               if ( context.data.keypressedCount === 0 &&
+                                                       e.which === context.data.keypressed &&
+                                                       $.inArray( e.which, allowed ) !== -1
                                                ) {
                                                        $.suggestions.keypress( e, context, context.data.keypressed );
                                                }
index 62be0d8..3b85f9a 100644 (file)
        }
 
        function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) {
+               var i, len;
                // Remove all header information and reset titles to default message
                $headers.removeClass( css[ 0 ] ).removeClass( css[ 1 ] ).attr( 'title', msg[ 1 ] );
 
-               for ( var i = 0; i < list.length; i++ ) {
+               for ( i = 0, len = list.length; i < len; i++ ) {
                        $headers
                                .eq( columnToHeader[ list[ i ][ 0 ] ] )
                                .addClass( css[ list[ i ][ 1 ] ] )
        }
 
        function buildCollationTable() {
+               var key, keys = [];
                ts.collationTable = mw.config.get( 'tableSorterCollation' );
                ts.collationRegex = null;
                if ( ts.collationTable ) {
-                       var key,
-                               keys = [];
-
                        // Build array of key names
                        for ( key in ts.collationTable ) {
                                // Check hasOwn to be safe
        /* Public scope */
 
        $.tablesorter = {
-                       defaultOptions: {
-                               cssHeader: 'headerSort',
-                               cssAsc: 'headerSortUp',
-                               cssDesc: 'headerSortDown',
-                               cssChildRow: 'expand-child',
-                               sortMultiSortKey: 'shiftKey',
-                               unsortableClass: 'unsortable',
-                               parsers: [],
-                               cancelSelection: true,
-                               sortList: [],
-                               headerList: [],
-                               headerToColumns: [],
-                               columnToHeader: [],
-                               columns: 0
-                       },
-
-                       dateRegex: [],
-                       monthNames: {},
-
-                       /**
-                        * @param {jQuery} $tables
-                        * @param {Object} [settings]
-                        */
-                       construct: function ( $tables, settings ) {
-                               return $tables.each( function ( i, table ) {
-                                       // Declare and cache.
-                                       var $headers, cache, config, sortCSS, sortMsg,
-                                               $table = $( table ),
-                                               firstTime = true;
-
-                                       // Quit if no tbody
-                                       if ( !table.tBodies ) {
+               defaultOptions: {
+                       cssHeader: 'headerSort',
+                       cssAsc: 'headerSortUp',
+                       cssDesc: 'headerSortDown',
+                       cssChildRow: 'expand-child',
+                       sortMultiSortKey: 'shiftKey',
+                       unsortableClass: 'unsortable',
+                       parsers: [],
+                       cancelSelection: true,
+                       sortList: [],
+                       headerList: [],
+                       headerToColumns: [],
+                       columnToHeader: [],
+                       columns: 0
+               },
+
+               dateRegex: [],
+               monthNames: {},
+
+               /**
+                * @param {jQuery} $tables
+                * @param {Object} [settings]
+                */
+               construct: function ( $tables, settings ) {
+                       return $tables.each( function ( i, table ) {
+                               // Declare and cache.
+                               var $headers, cache, config, sortCSS, sortMsg,
+                                       $table = $( table ),
+                                       firstTime = true;
+
+                               // Quit if no tbody
+                               if ( !table.tBodies ) {
+                                       return;
+                               }
+                               if ( !table.tHead ) {
+                                       // No thead found. Look for rows with <th>s and
+                                       // move them into a <thead> tag or a <tfoot> tag
+                                       emulateTHeadAndFoot( $table );
+
+                                       // Still no thead? Then quit
+                                       if ( !table.tHead ) {
                                                return;
                                        }
-                                       if ( !table.tHead ) {
-                                               // No thead found. Look for rows with <th>s and
-                                               // move them into a <thead> tag or a <tfoot> tag
-                                               emulateTHeadAndFoot( $table );
+                               }
+                               $table.addClass( 'jquery-tablesorter' );
 
-                                               // Still no thead? Then quit
-                                               if ( !table.tHead ) {
-                                                       return;
-                                               }
-                                       }
-                                       $table.addClass( 'jquery-tablesorter' );
-
-                                       // Merge and extend
-                                       config = $.extend( {}, $.tablesorter.defaultOptions, settings );
-
-                                       // Save the settings where they read
-                                       $.data( table, 'tablesorter', { config: config } );
-
-                                       // Get the CSS class names, could be done elsewhere
-                                       sortCSS = [ config.cssAsc, config.cssDesc ];
-                                       // Messages tell the the user what the *next* state will be
-                                       // so are in reverse order to the CSS classes.
-                                       sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ];
-
-                                       // Build headers
-                                       $headers = buildHeaders( table, sortMsg );
-
-                                       // Grab and process locale settings.
-                                       buildTransformTable();
-                                       buildDateTable();
-
-                                       // Precaching regexps can bring 10 fold
-                                       // performance improvements in some browsers.
-                                       cacheRegexs();
-
-                                       function setupForFirstSort() {
-                                               firstTime = false;
-
-                                               // Defer buildCollationTable to first sort. As user and site scripts
-                                               // may customize tableSorterCollation but load after $.ready(), other
-                                               // scripts may call .tablesorter() before they have done the
-                                               // tableSorterCollation customizations.
-                                               buildCollationTable();
-
-                                               // Legacy fix of .sortbottoms
-                                               // Wrap them inside a tfoot (because that's what they actually want to be)
-                                               // and put the <tfoot> at the end of the <table>
-                                               var $tfoot,
-                                                       $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
-                                               if ( $sortbottoms.length ) {
-                                                       $tfoot = $table.children( 'tfoot' );
-                                                       if ( $tfoot.length ) {
-                                                               $tfoot.eq( 0 ).prepend( $sortbottoms );
-                                                       } else {
-                                                               $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
-                                                       }
-                                               }
+                               // Merge and extend
+                               config = $.extend( {}, $.tablesorter.defaultOptions, settings );
 
-                                               explodeRowspans( $table );
-                                               manageColspans( $table );
+                               // Save the settings where they read
+                               $.data( table, 'tablesorter', { config: config } );
 
-                                               // Try to auto detect column type, and store in tables config
-                                               config.parsers = buildParserCache( table, $headers );
-                                       }
+                               // Get the CSS class names, could be done elsewhere
+                               sortCSS = [ config.cssAsc, config.cssDesc ];
+                               // Messages tell the the user what the *next* state will be
+                               // so are in reverse order to the CSS classes.
+                               sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ];
 
-                                       // Apply event handling to headers
-                                       // this is too big, perhaps break it out?
-                                       $headers.on( 'keypress click', function ( e ) {
-                                               var cell, $cell, columns, newSortList, i,
-                                                       totalRows,
-                                                       j, s, o;
-
-                                               if ( e.type === 'click' && e.target.nodeName.toLowerCase() === 'a' ) {
-                                                       // The user clicked on a link inside a table header.
-                                                       // Do nothing and let the default link click action continue.
-                                                       return true;
-                                               }
+                               // Build headers
+                               $headers = buildHeaders( table, sortMsg );
 
-                                               if ( e.type === 'keypress' && e.which !== 13 ) {
-                                                       // Only handle keypresses on the "Enter" key.
-                                                       return true;
-                                               }
+                               // Grab and process locale settings.
+                               buildTransformTable();
+                               buildDateTable();
 
-                                               if ( firstTime ) {
-                                                       setupForFirstSort();
+                               // Precaching regexps can bring 10 fold
+                               // performance improvements in some browsers.
+                               cacheRegexs();
+
+                               function setupForFirstSort() {
+                                       var $tfoot, $sortbottoms;
+
+                                       firstTime = false;
+
+                                       // Defer buildCollationTable to first sort. As user and site scripts
+                                       // may customize tableSorterCollation but load after $.ready(), other
+                                       // scripts may call .tablesorter() before they have done the
+                                       // tableSorterCollation customizations.
+                                       buildCollationTable();
+
+                                       // Legacy fix of .sortbottoms
+                                       // Wrap them inside a tfoot (because that's what they actually want to be)
+                                       // and put the <tfoot> at the end of the <table>
+                                       $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
+                                       if ( $sortbottoms.length ) {
+                                               $tfoot = $table.children( 'tfoot' );
+                                               if ( $tfoot.length ) {
+                                                       $tfoot.eq( 0 ).prepend( $sortbottoms );
+                                               } else {
+                                                       $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
                                                }
+                                       }
 
-                                               // Build the cache for the tbody cells
-                                               // to share between calculations for this sort action.
-                                               // Re-calculated each time a sort action is performed due to possiblity
-                                               // that sort values change. Shouldn't be too expensive, but if it becomes
-                                               // too slow an event based system should be implemented somehow where
-                                               // cells get event .change() and bubbles up to the <table> here
-                                               cache = buildCache( table );
-
-                                               totalRows = ( $table[ 0 ].tBodies[ 0 ] && $table[ 0 ].tBodies[ 0 ].rows.length ) || 0;
-                                               if ( totalRows > 0 ) {
-                                                       cell = this;
-                                                       $cell = $( cell );
-
-                                                       // Get current column sort order
-                                                       $cell.data( {
-                                                               order: $cell.data( 'count' ) % 2,
-                                                               count: $cell.data( 'count' ) + 1
-                                                       } );
+                                       explodeRowspans( $table );
+                                       manageColspans( $table );
 
-                                                       cell = this;
-                                                       // Get current column index
-                                                       columns = config.headerToColumns[ $cell.data( 'headerIndex' ) ];
-                                                       newSortList = $.map( columns, function ( c ) {
-                                                               // jQuery "helpfully" flattens the arrays...
-                                                               return [ [ c, $cell.data( 'order' ) ] ];
-                                                       } );
-                                                       // Index of first column belonging to this header
-                                                       i = columns[ 0 ];
+                                       // Try to auto detect column type, and store in tables config
+                                       config.parsers = buildParserCache( table, $headers );
+                               }
 
-                                                       if ( !e[ config.sortMultiSortKey ] ) {
-                                                               // User only wants to sort on one column set
-                                                               // Flush the sort list and add new columns
-                                                               config.sortList = newSortList;
-                                                       } else {
-                                                               // Multi column sorting
-                                                               // It is not possible for one column to belong to multiple headers,
-                                                               // so this is okay - we don't need to check for every value in the columns array
-                                                               if ( isValueInArray( i, config.sortList ) ) {
-                                                                       // The user has clicked on an already sorted column.
-                                                                       // Reverse the sorting direction for all tables.
-                                                                       for ( j = 0; j < config.sortList.length; j++ ) {
-                                                                               s = config.sortList[ j ];
-                                                                               o = config.headerList[ config.columnToHeader[ s[ 0 ] ] ];
-                                                                               if ( isValueInArray( s[ 0 ], newSortList ) ) {
-                                                                                       $( o ).data( 'count', s[ 1 ] + 1 );
-                                                                                       s[ 1 ] = $( o ).data( 'count' ) % 2;
-                                                                               }
+                               // Apply event handling to headers
+                               // this is too big, perhaps break it out?
+                               $headers.on( 'keypress click', function ( e ) {
+                                       var cell, $cell, columns, newSortList, i,
+                                               totalRows,
+                                               j, s, o;
+
+                                       if ( e.type === 'click' && e.target.nodeName.toLowerCase() === 'a' ) {
+                                               // The user clicked on a link inside a table header.
+                                               // Do nothing and let the default link click action continue.
+                                               return true;
+                                       }
+
+                                       if ( e.type === 'keypress' && e.which !== 13 ) {
+                                               // Only handle keypresses on the "Enter" key.
+                                               return true;
+                                       }
+
+                                       if ( firstTime ) {
+                                               setupForFirstSort();
+                                       }
+
+                                       // Build the cache for the tbody cells
+                                       // to share between calculations for this sort action.
+                                       // Re-calculated each time a sort action is performed due to possiblity
+                                       // that sort values change. Shouldn't be too expensive, but if it becomes
+                                       // too slow an event based system should be implemented somehow where
+                                       // cells get event .change() and bubbles up to the <table> here
+                                       cache = buildCache( table );
+
+                                       totalRows = ( $table[ 0 ].tBodies[ 0 ] && $table[ 0 ].tBodies[ 0 ].rows.length ) || 0;
+                                       if ( totalRows > 0 ) {
+                                               cell = this;
+                                               $cell = $( cell );
+
+                                               // Get current column sort order
+                                               $cell.data( {
+                                                       order: $cell.data( 'count' ) % 2,
+                                                       count: $cell.data( 'count' ) + 1
+                                               } );
+
+                                               cell = this;
+                                               // Get current column index
+                                               columns = config.headerToColumns[ $cell.data( 'headerIndex' ) ];
+                                               newSortList = $.map( columns, function ( c ) {
+                                                       // jQuery "helpfully" flattens the arrays...
+                                                       return [ [ c, $cell.data( 'order' ) ] ];
+                                               } );
+                                               // Index of first column belonging to this header
+                                               i = columns[ 0 ];
+
+                                               if ( !e[ config.sortMultiSortKey ] ) {
+                                                       // User only wants to sort on one column set
+                                                       // Flush the sort list and add new columns
+                                                       config.sortList = newSortList;
+                                               } else {
+                                                       // Multi column sorting
+                                                       // It is not possible for one column to belong to multiple headers,
+                                                       // so this is okay - we don't need to check for every value in the columns array
+                                                       if ( isValueInArray( i, config.sortList ) ) {
+                                                               // The user has clicked on an already sorted column.
+                                                               // Reverse the sorting direction for all tables.
+                                                               for ( j = 0; j < config.sortList.length; j++ ) {
+                                                                       s = config.sortList[ j ];
+                                                                       o = config.headerList[ config.columnToHeader[ s[ 0 ] ] ];
+                                                                       if ( isValueInArray( s[ 0 ], newSortList ) ) {
+                                                                               $( o ).data( 'count', s[ 1 ] + 1 );
+                                                                               s[ 1 ] = $( o ).data( 'count' ) % 2;
                                                                        }
-                                                               } else {
-                                                                       // Add columns to sort list array
-                                                                       config.sortList = config.sortList.concat( newSortList );
                                                                }
+                                                       } else {
+                                                               // Add columns to sort list array
+                                                               config.sortList = config.sortList.concat( newSortList );
                                                        }
+                                               }
 
-                                                       // Reset order/counts of cells not affected by sorting
-                                                       setHeadersOrder( $headers, config.sortList, config.headerToColumns );
+                                               // Reset order/counts of cells not affected by sorting
+                                               setHeadersOrder( $headers, config.sortList, config.headerToColumns );
 
-                                                       // Set CSS for headers
-                                                       setHeadersCss( $table[ 0 ], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader );
-                                                       appendToTable(
-                                                               $table[ 0 ], multisort( $table[ 0 ], config.sortList, cache )
-                                                       );
+                                               // Set CSS for headers
+                                               setHeadersCss( $table[ 0 ], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader );
+                                               appendToTable(
+                                                       $table[ 0 ], multisort( $table[ 0 ], config.sortList, cache )
+                                               );
 
-                                                       // Stop normal event by returning false
-                                                       return false;
-                                               }
+                                               // Stop normal event by returning false
+                                               return false;
+                                       }
 
-                                       // Cancel selection
-                                       } ).mousedown( function () {
-                                               if ( config.cancelSelection ) {
-                                                       this.onselectstart = function () {
-                                                               return false;
-                                                       };
+                               // Cancel selection
+                               } ).mousedown( function () {
+                                       if ( config.cancelSelection ) {
+                                               this.onselectstart = function () {
                                                        return false;
-                                               }
-                                       } );
+                                               };
+                                               return false;
+                                       }
+                               } );
 
-                                       /**
-                                        * Sorts the table. If no sorting is specified by passing a list of sort
-                                        * objects, the table is sorted according to the initial sorting order.
-                                        * Passing an empty array will reset sorting (basically just reset the headers
-                                        * making the table appear unsorted).
-                                        *
-                                        * @param {Array} [sortList] List of sort objects.
-                                        */
-                                       $table.data( 'tablesorter' ).sort = function ( sortList ) {
-
-                                               if ( firstTime ) {
-                                                       setupForFirstSort();
-                                               }
+                               /**
+                                * Sorts the table. If no sorting is specified by passing a list of sort
+                                * objects, the table is sorted according to the initial sorting order.
+                                * Passing an empty array will reset sorting (basically just reset the headers
+                                * making the table appear unsorted).
+                                *
+                                * @param {Array} [sortList] List of sort objects.
+                                */
+                               $table.data( 'tablesorter' ).sort = function ( sortList ) {
+
+                                       if ( firstTime ) {
+                                               setupForFirstSort();
+                                       }
 
-                                               if ( sortList === undefined ) {
-                                                       sortList = config.sortList;
-                                               } else if ( sortList.length > 0 ) {
-                                                       sortList = convertSortList( sortList );
-                                               }
+                                       if ( sortList === undefined ) {
+                                               sortList = config.sortList;
+                                       } else if ( sortList.length > 0 ) {
+                                               sortList = convertSortList( sortList );
+                                       }
 
-                                               // Set each column's sort count to be able to determine the correct sort
-                                               // order when clicking on a header cell the next time
-                                               setHeadersOrder( $headers, sortList, config.headerToColumns );
+                                       // Set each column's sort count to be able to determine the correct sort
+                                       // order when clicking on a header cell the next time
+                                       setHeadersOrder( $headers, sortList, config.headerToColumns );
 
-                                               // re-build the cache for the tbody cells
-                                               cache = buildCache( table );
+                                       // re-build the cache for the tbody cells
+                                       cache = buildCache( table );
 
-                                               // set css for headers
-                                               setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, config.columnToHeader );
+                                       // set css for headers
+                                       setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, config.columnToHeader );
 
-                                               // sort the table and append it to the dom
-                                               appendToTable( table, multisort( table, sortList, cache ) );
-                                       };
+                                       // sort the table and append it to the dom
+                                       appendToTable( table, multisort( table, sortList, cache ) );
+                               };
 
-                                       // sort initially
-                                       if ( config.sortList.length > 0 ) {
-                                               config.sortList = convertSortList( config.sortList );
-                                               $table.data( 'tablesorter' ).sort();
-                                       }
+                               // sort initially
+                               if ( config.sortList.length > 0 ) {
+                                       config.sortList = convertSortList( config.sortList );
+                                       $table.data( 'tablesorter' ).sort();
+                               }
 
-                               } );
-                       },
+                       } );
+               },
 
-                       addParser: function ( parser ) {
-                               if ( !getParserById( parser.id ) ) {
-                                       parsers.push( parser );
-                               }
-                       },
-
-                       formatDigit: function ( s ) {
-                               var out, c, p, i;
-                               if ( ts.transformTable !== false ) {
-                                       out = '';
-                                       for ( p = 0; p < s.length; p++ ) {
-                                               c = s.charAt( p );
-                                               if ( c in ts.transformTable ) {
-                                                       out += ts.transformTable[ c ];
-                                               } else {
-                                                       out += c;
-                                               }
+               addParser: function ( parser ) {
+                       if ( !getParserById( parser.id ) ) {
+                               parsers.push( parser );
+                       }
+               },
+
+               formatDigit: function ( s ) {
+                       var out, c, p, i;
+                       if ( ts.transformTable !== false ) {
+                               out = '';
+                               for ( p = 0; p < s.length; p++ ) {
+                                       c = s.charAt( p );
+                                       if ( c in ts.transformTable ) {
+                                               out += ts.transformTable[ c ];
+                                       } else {
+                                               out += c;
                                        }
-                                       s = out;
                                }
-                               i = parseFloat( s.replace( /[, ]/g, '' ).replace( '\u2212', '-' ) );
-                               return isNaN( i ) ? 0 : i;
-                       },
+                               s = out;
+                       }
+                       i = parseFloat( s.replace( /[, ]/g, '' ).replace( '\u2212', '-' ) );
+                       return isNaN( i ) ? 0 : i;
+               },
 
-                       formatFloat: function ( s ) {
-                               var i = parseFloat( s );
-                               return isNaN( i ) ? 0 : i;
-                       },
+               formatFloat: function ( s ) {
+                       var i = parseFloat( s );
+                       return isNaN( i ) ? 0 : i;
+               },
 
-                       formatInt: function ( s ) {
-                               var i = parseInt( s, 10 );
-                               return isNaN( i ) ? 0 : i;
-                       },
+               formatInt: function ( s ) {
+                       var i = parseInt( s, 10 );
+                       return isNaN( i ) ? 0 : i;
+               },
 
-                       clearTableBody: function ( table ) {
-                               $( table.tBodies[ 0 ] ).empty();
-                       },
+               clearTableBody: function ( table ) {
+                       $( table.tBodies[ 0 ] ).empty();
+               },
 
-                       getParser: function ( id ) {
-                               buildTransformTable();
-                               buildDateTable();
-                               cacheRegexs();
-                               buildCollationTable();
+               getParser: function ( id ) {
+                       buildTransformTable();
+                       buildDateTable();
+                       cacheRegexs();
+                       buildCollationTable();
 
-                               return getParserById( id );
-                       },
+                       return getParserById( id );
+               },
 
-                       getParsers: function () {  // for table diagnosis
-                               return parsers;
-                       }
-               };
+               getParsers: function () {  // for table diagnosis
+                       return parsers;
+               }
+       };
 
        // Shortcut
        ts = $.tablesorter;
                        return true;
                },
                format: function ( s ) {
+                       var tsc;
                        s = $.trim( s.toLowerCase() );
                        if ( ts.collationRegex ) {
-                               var tsc = ts.collationTable;
+                               tsc = ts.collationTable;
                                s = s.replace( ts.collationRegex, function ( match ) {
                                        var r = tsc[ match ] ? tsc[ match ] : tsc[ match.toUpperCase() ];
                                        return r.toLowerCase();
                                if ( !matches ) {
                                        return $.tablesorter.formatFloat( 0 );
                                }
-                               isodate = new Date( matches[ 2 ]  + '/' + matches[ 3 ] + '/' + matches[ 1 ] );
+                               isodate = new Date( matches[ 2 ] + '/' + matches[ 3 ] + '/' + matches[ 1 ] );
                        } else {
                                matches = s.match( ts.rgx.isoDate[ 0 ] );
                                if ( !matches ) {
index 5e93ba6..b6fbe35 100644 (file)
                 * Helper function to get an IE TextRange object for an element
                 */
                function rangeForElementIE( e ) {
+                       var sel;
                        if ( e.nodeName.toLowerCase() === 'input' ) {
                                return e.createTextRange();
                        } else {
-                               var sel = document.body.createTextRange();
+                               sel = document.body.createTextRange();
                                sel.moveToElementText( e );
                                return sel;
                        }
                                        var caretPos = 0,
                                                endPos = 0,
                                                preText, rawPreText, periText,
-                                               rawPeriText, postText, rawPostText,
+                                               rawPeriText, postText,
                                                // IE Support
                                                preFinished,
                                                periFinished,
                                                // Load the text values we need to compare
                                                preText = rawPreText = preRange.text;
                                                periText = rawPeriText = periRange.text;
-                                               postText = rawPostText = postRange.text;
+                                               postText = postRange.text;
 
                                                /*
                                                 * Check each range for trimmed newlines by shrinking the range by 1
                                                                        postFinished = true;
                                                                } else {
                                                                        postRange.moveEnd( 'character', -1 );
-                                                                       if ( postRange.text === postText ) {
-                                                                               rawPostText += '\r\n';
-                                                                       } else {
+                                                                       if ( postRange.text !== postText ) {
                                                                                postFinished = true;
                                                                        }
                                                                }
index 6c63957..c8d3fad 100644 (file)
@@ -1,6 +1,7 @@
 /*!
  * Scripts for pre-emptive edit preparing on action=edit
  */
+/* eslint-disable no-use-before-define */
 ( function ( mw, $ ) {
        if ( !mw.config.get( 'wgAjaxEditStash' ) ) {
                return;
                        if (
                                // Reverts may involve use (undo) links; stash as they review the diff.
                                // Since the form has a pre-filled summary, stash the edit immediately.
-                               mw.util.getParamValue( 'undo' ) !== null
+                               mw.util.getParamValue( 'undo' ) !== null ||
                                // Pressing "show changes" and "preview" also signify that the user will
                                // probably save the page soon
-                               || $.inArray( $form.find( '#mw-edit-mode' ).val(), [ 'preview', 'diff' ] ) > -1
+                               $.inArray( $form.find( '#mw-edit-mode' ).val(), [ 'preview', 'diff' ] ) > -1
                        ) {
                                checkStash();
                        }
index 2be29f0..7439754 100644 (file)
@@ -4,11 +4,12 @@
 ( function ( mw, $ ) {
        $( function () {
                mw.util.$content.dblclick( function ( e ) {
+                       var $a;
                        // Recheck preference so extensions can do a hack to disable this code.
                        if ( parseInt( mw.user.options.get( 'editondblclick' ), 10 ) ) {
                                e.preventDefault();
                                // Trigger native HTMLElement click instead of opening URL (bug 43052)
-                               var $a = $( '#ca-edit a' );
+                               $a = $( '#ca-edit a' );
                                // Not every page has an edit link (bug 57713)
                                if ( $a.length ) {
                                        $a.get( 0 ).click();
index 29a5a79..39a122d 100644 (file)
@@ -28,7 +28,7 @@
                }
 
                // Note that this will update the hash in a modern browser, retaining back behaviour
-               history.replaceState( /*data=*/ history.state, /*title=*/ document.title, /*url=*/ canonical );
+               history.replaceState( /* data= */ history.state, /* title= */ document.title, /* url= */ canonical );
                if ( shouldChangeFragment ) {
                        // Specification for history.replaceState() doesn't require browser to scroll,
                        // so scroll to be sure (see also T110501). Support for IE9 and IE10.
index a4c911a..e136211 100644 (file)
@@ -3,7 +3,6 @@
  */
 
 mediaWiki.language.convertGrammar = function ( word, form ) {
-       /*jshint onecase:true */
        var grammarForms = mediaWiki.language.getData( 'ga', 'grammarForms' );
        if ( grammarForms && grammarForms[ form ] ) {
                return grammarForms[ form ][ word ];
index 945f02f..5bf8c4d 100644 (file)
@@ -21,7 +21,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
                        }
 
                        // Add a hyphen (maqaf) before numbers and non-Hebrew letters
-                       if ( word.slice( 0, 1 ) < 'א' ||  word.slice( 0, 1 ) > 'ת' ) {
+                       if ( word.slice( 0, 1 ) < 'א' || word.slice( 0, 1 ) > 'ת' ) {
                                word = '־' + word;
                        }
        }
index 935d466..bb6f61d 100644 (file)
@@ -3,7 +3,6 @@
  */
 
 mediaWiki.language.convertGrammar = function ( word, form ) {
-       /*jshint onecase:true */
        var grammarForms = mediaWiki.language.getData( 'hy', 'grammarForms' );
        if ( grammarForms && grammarForms[ form ] ) {
                return grammarForms[ form ][ word ];
index cf3ef79..fc2af3d 100644 (file)
  */
 ( function ( mw, $ ) {
 
-/**
- * @class mw.language
- */
-$.extend( mw.language, {
-
        /**
-        * Process the PLURAL template substitution
-        *
-        * @private
-        * @param {Object} template Template object
-        * @param {string} template.title
-        * @param {Array} template.parameters
-        * @return {string}
+        * @class mw.language
         */
-       procPLURAL: function ( template ) {
-               if ( template.title && template.parameters && mw.language.convertPlural ) {
-                       // Check if we have forms to replace
-                       if ( template.parameters.length === 0 ) {
-                               return '';
+       $.extend( mw.language, {
+
+               /**
+                * Process the PLURAL template substitution
+                *
+                * @private
+                * @param {Object} template Template object
+                * @param {string} template.title
+                * @param {Array} template.parameters
+                * @return {string}
+                */
+               procPLURAL: function ( template ) {
+                       var count;
+                       if ( template.title && template.parameters && mw.language.convertPlural ) {
+                               // Check if we have forms to replace
+                               if ( template.parameters.length === 0 ) {
+                                       return '';
+                               }
+                               // Restore the count into a Number ( if it got converted earlier )
+                               count = mw.language.convertNumber( template.title, true );
+                               // Do convertPlural call
+                               return mw.language.convertPlural( parseInt( count, 10 ), template.parameters );
                        }
-                       // Restore the count into a Number ( if it got converted earlier )
-                       var count = mw.language.convertNumber( template.title, true );
-                       // Do convertPlural call
-                       return mw.language.convertPlural( parseInt( count, 10 ), template.parameters );
-               }
-               // Could not process plural return first form or nothing
-               if ( template.parameters[ 0 ] ) {
-                       return template.parameters[ 0 ];
-               }
-               return '';
-       },
+                       // Could not process plural return first form or nothing
+                       if ( template.parameters[ 0 ] ) {
+                               return template.parameters[ 0 ];
+                       }
+                       return '';
+               },
 
-       /**
-        * Plural form transformations, needed for some languages.
-        *
-        * @param {number} count Non-localized quantifier
-        * @param {Array} forms List of plural forms
-        * @param {Object} [explicitPluralForms] List of explicit plural forms
-        * @return {string} Correct form for quantifier in this language
-        */
-       convertPlural: function ( count, forms, explicitPluralForms ) {
-               var pluralRules,
-                       pluralFormIndex = 0;
+               /**
+                * Plural form transformations, needed for some languages.
+                *
+                * @param {number} count Non-localized quantifier
+                * @param {Array} forms List of plural forms
+                * @param {Object} [explicitPluralForms] List of explicit plural forms
+                * @return {string} Correct form for quantifier in this language
+                */
+               convertPlural: function ( count, forms, explicitPluralForms ) {
+                       var pluralRules,
+                               pluralFormIndex = 0;
 
-               if ( explicitPluralForms && ( explicitPluralForms[ count ] !== undefined ) ) {
-                       return explicitPluralForms[ count ];
-               }
+                       if ( explicitPluralForms && ( explicitPluralForms[ count ] !== undefined ) ) {
+                               return explicitPluralForms[ count ];
+                       }
 
-               if ( !forms || forms.length === 0 ) {
-                       return '';
-               }
+                       if ( !forms || forms.length === 0 ) {
+                               return '';
+                       }
 
-               pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' );
-               if ( !pluralRules ) {
-                       // default fallback.
-                       return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ];
-               }
-               pluralFormIndex = mw.cldr.getPluralForm( count, pluralRules );
-               pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 );
-               return forms[ pluralFormIndex ];
-       },
+                       pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' );
+                       if ( !pluralRules ) {
+                               // default fallback.
+                               return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ];
+                       }
+                       pluralFormIndex = mw.cldr.getPluralForm( count, pluralRules );
+                       pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 );
+                       return forms[ pluralFormIndex ];
+               },
 
-       /**
-        * Pads an array to a specific length by copying the last one element.
-        *
-        * @private
-        * @param {Array} forms Number of forms given to convertPlural
-        * @param {number} count Number of forms required
-        * @return {Array} Padded array of forms
-        */
-       preConvertPlural: function ( forms, count ) {
-               while ( forms.length < count ) {
-                       forms.push( forms[ forms.length - 1 ] );
-               }
-               return forms;
-       },
+               /**
+                * Pads an array to a specific length by copying the last one element.
+                *
+                * @private
+                * @param {Array} forms Number of forms given to convertPlural
+                * @param {number} count Number of forms required
+                * @return {Array} Padded array of forms
+                */
+               preConvertPlural: function ( forms, count ) {
+                       while ( forms.length < count ) {
+                               forms.push( forms[ forms.length - 1 ] );
+                       }
+                       return forms;
+               },
 
-       /**
-        * Provides an alternative text depending on specified gender.
-        *
-        * Usage in message text: `{{gender:[gender|user object]|masculine|feminine|neutral}}`.
-        * If second or third parameter are not specified, masculine is used.
-        *
-        * These details may be overridden per language.
-        *
-        * @param {string} gender 'male', 'female', or anything else for neutral.
-        * @param {Array} forms List of gender forms
-        * @return {string}
-        */
-       gender: function ( gender, forms ) {
-               if ( !forms || forms.length === 0 ) {
-                       return '';
-               }
-               forms = mw.language.preConvertPlural( forms, 2 );
-               if ( gender === 'male' ) {
-                       return forms[ 0 ];
-               }
-               if ( gender === 'female' ) {
-                       return forms[ 1 ];
-               }
-               return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ];
-       },
+               /**
+                * Provides an alternative text depending on specified gender.
+                *
+                * Usage in message text: `{{gender:[gender|user object]|masculine|feminine|neutral}}`.
+                * If second or third parameter are not specified, masculine is used.
+                *
+                * These details may be overridden per language.
+                *
+                * @param {string} gender 'male', 'female', or anything else for neutral.
+                * @param {Array} forms List of gender forms
+                * @return {string}
+                */
+               gender: function ( gender, forms ) {
+                       if ( !forms || forms.length === 0 ) {
+                               return '';
+                       }
+                       forms = mw.language.preConvertPlural( forms, 2 );
+                       if ( gender === 'male' ) {
+                               return forms[ 0 ];
+                       }
+                       if ( gender === 'female' ) {
+                               return forms[ 1 ];
+                       }
+                       return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ];
+               },
 
-       /**
-        * Grammatical transformations, needed for inflected languages.
-        * Invoked by putting `{{grammar:form|word}}` in a message.
-        *
-        * The rules can be defined in $wgGrammarForms global or computed
-        * dynamically by overriding this method per language.
-        *
-        * @param {string} word
-        * @param {string} form
-        * @return {string}
-        */
-       convertGrammar: function ( word, form ) {
-               var grammarForms = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'grammarForms' );
-               if ( grammarForms && grammarForms[ form ] ) {
-                       return grammarForms[ form ][ word ] || word;
-               }
-               return word;
-       },
+               /**
+                * Grammatical transformations, needed for inflected languages.
+                * Invoked by putting `{{grammar:form|word}}` in a message.
+                *
+                * The rules can be defined in $wgGrammarForms global or computed
+                * dynamically by overriding this method per language.
+                *
+                * @param {string} word
+                * @param {string} form
+                * @return {string}
+                */
+               convertGrammar: function ( word, form ) {
+                       var grammarForms = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'grammarForms' );
+                       if ( grammarForms && grammarForms[ form ] ) {
+                               return grammarForms[ form ][ word ] || word;
+                       }
+                       return word;
+               },
 
-       /**
-        * Turn a list of string into a simple list using commas and 'and'.
-        *
-        * See Language::listToText in languages/Language.php
-        *
-        * @param {string[]} list
-        * @return {string}
-        */
-       listToText: function ( list ) {
-               var text = '',
-                       i = 0;
+               /**
+                * Turn a list of string into a simple list using commas and 'and'.
+                *
+                * See Language::listToText in languages/Language.php
+                *
+                * @param {string[]} list
+                * @return {string}
+                */
+               listToText: function ( list ) {
+                       var text = '',
+                               i = 0;
 
-               for ( ; i < list.length; i++ ) {
-                       text += list[ i ];
-                       if ( list.length - 2 === i ) {
-                               text += mw.msg( 'and' ) + mw.msg( 'word-separator' );
-                       } else if ( list.length - 1 !== i ) {
-                               text += mw.msg( 'comma-separator' );
+                       for ( ; i < list.length; i++ ) {
+                               text += list[ i ];
+                               if ( list.length - 2 === i ) {
+                                       text += mw.msg( 'and' ) + mw.msg( 'word-separator' );
+                               } else if ( list.length - 1 !== i ) {
+                                       text += mw.msg( 'comma-separator' );
+                               }
                        }
-               }
-               return text;
-       },
+                       return text;
+               },
 
-       setSpecialCharacters: function ( data ) {
-               this.specialCharacters = data;
-       }
-} );
+               setSpecialCharacters: function ( data ) {
+                       this.specialCharacters = data;
+               }
+       } );
 
 }( mediaWiki, jQuery ) );
index 268985f..1192650 100644 (file)
         * @return {string}
         */
        function replicate( str, num ) {
+               var buf = [];
+
                if ( num <= 0 || !str ) {
                        return '';
                }
 
-               var buf = [];
                while ( num-- ) {
                        buf.push( str );
                }
         * @return {string}
         */
        function pad( text, size, ch, end ) {
+               var out, padStr;
+
                if ( !ch ) {
                        ch = '0';
                }
 
-               var out = String( text ),
-                       padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
+               out = String( text );
+               padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
 
                return end ? out + padStr : padStr + out;
        }
         * @return {string}
         */
        function commafyNumber( value, pattern, options ) {
-               options = options || {
-                       group: ',',
-                       decimal: '.'
-               };
-
-               if ( isNaN( value ) ) {
-                       return value;
-               }
-
                var padLength,
                        patternDigits,
                        index,
                        groupSize2 = 0,
                        pieces = [];
 
+               options = options || {
+                       group: ',',
+                       decimal: '.'
+               };
+
+               if ( isNaN( value ) ) {
+                       return value;
+               }
+
                if ( patternParts[ 1 ] ) {
                        // Pad fractional with trailing zeros
                        padLength = ( patternParts[ 1 ] && patternParts[ 1 ].lastIndexOf( '0' ) + 1 );
index 6226c90..fd3fb00 100644 (file)
 ( function ( mw, $ ) {
 
-var ProtectionForm = window.ProtectionForm = {
-       /**
-        * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
-        * on the protection form
-        */
-       init: function () {
-               var $cell = $( '<td>' ),
-                       $row = $( '<tr>' ).append( $cell );
-
-               if ( !$( '#mwProtectSet' ).length ) {
-                       return false;
-               }
-
-               if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) {
-                       $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
-               }
-               this.getExpirySelectors().each( function () {
-                       $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
-               } );
-               this.getExpiryInputs().each( function () {
-                       $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
-               } );
-               this.getLevelSelectors().each( function () {
-                       $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
-               } );
-
-               $( '#mwProtectSet > tbody > tr:first' ).after( $row );
-
-               // If there is only one protection type, there is nothing to chain
-               if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
-                       $cell.append(
-                               $( '<input>' )
-                                       .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
-                                       .click( this.onChainClick.bind( this ) )
-                                       .prop( 'checked', !this.areAllTypesMatching() ),
-                               document.createTextNode( ' ' ),
-                               $( '<label>' )
-                                       .attr( 'for', 'mwProtectUnchained' )
-                                       .text( mw.msg( 'protect-unchain-permissions' ) )
-                       );
-
-                       this.toggleUnchainedInputs( !this.areAllTypesMatching() );
-               }
-
-               $( '#mwProtect-reason' ).byteLimit( 180 );
-
-               this.updateCascadeCheckbox();
-       },
-
-       /**
-        * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
-        */
-       updateCascadeCheckbox: function () {
-               this.getLevelSelectors().each( function () {
-                       if ( !ProtectionForm.isCascadeableLevel( $( this ).val() ) ) {
-                               $( '#mwProtect-cascade' ).prop( { checked: false, disabled: true } );
+       var ProtectionForm = window.ProtectionForm = {
+               /**
+                * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
+                * on the protection form
+                */
+               init: function () {
+                       var $cell = $( '<td>' ),
+                               $row = $( '<tr>' ).append( $cell );
+
+                       if ( !$( '#mwProtectSet' ).length ) {
                                return false;
-                       } else {
-                               $( '#mwProtect-cascade' ).prop( 'disabled', false );
                        }
-               } );
-       },
-
-       /**
-        * Checks if a certain protection level is cascadeable.
-        *
-        * @param {string} level
-        * @return {boolean}
-        */
-       isCascadeableLevel: function ( level ) {
-               return $.inArray( level, mw.config.get( 'wgCascadeableLevels' ) ) !== -1;
-       },
-
-       /**
-        * When protection levels are locked together, update the rest
-        * when one action's level changes
-        *
-        * @param {Element} source Level selector that changed
-        */
-       updateLevels: function ( source ) {
-               if ( !this.isUnchained() ) {
-                       this.setAllSelectors( source.selectedIndex );
-               }
-               this.updateCascadeCheckbox();
-       },
-
-       /**
-        * When protection levels are locked together, update the
-        * expiries when one changes
-        *
-        * @param {Element} source expiry input that changed
-        */
-
-       updateExpiry: function ( source ) {
-               if ( !this.isUnchained() ) {
+
+                       if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) {
+                               $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
+                       }
+                       this.getExpirySelectors().each( function () {
+                               $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
+                       } );
                        this.getExpiryInputs().each( function () {
-                               this.value = source.value;
+                               $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
                        } );
-               }
-               if ( this.isUnchained() ) {
-                       $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
-               } else {
-                       this.getExpirySelectors().each( function () {
-                               this.value = 'othertime';
+                       this.getLevelSelectors().each( function () {
+                               $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
                        } );
-               }
-       },
-
-       /**
-        * When protection levels are locked together, update the
-        * expiry lists when one changes and clear the custom inputs
-        *
-        * @param {Element} source Expiry selector that changed
-        */
-       updateExpiryList: function ( source ) {
-               if ( !this.isUnchained() ) {
-                       this.getExpirySelectors().each( function () {
-                               this.value = source.value;
+
+                       $( '#mwProtectSet > tbody > tr:first' ).after( $row );
+
+                       // If there is only one protection type, there is nothing to chain
+                       if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
+                               $cell.append(
+                                       $( '<input>' )
+                                               .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
+                                               .click( this.onChainClick.bind( this ) )
+                                               .prop( 'checked', !this.areAllTypesMatching() ),
+                                       document.createTextNode( ' ' ),
+                                       $( '<label>' )
+                                               .attr( 'for', 'mwProtectUnchained' )
+                                               .text( mw.msg( 'protect-unchain-permissions' ) )
+                               );
+
+                               this.toggleUnchainedInputs( !this.areAllTypesMatching() );
+                       }
+
+                       $( '#mwProtect-reason' ).byteLimit( 180 );
+
+                       this.updateCascadeCheckbox();
+               },
+
+               /**
+                * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
+                */
+               updateCascadeCheckbox: function () {
+                       this.getLevelSelectors().each( function () {
+                               if ( !ProtectionForm.isCascadeableLevel( $( this ).val() ) ) {
+                                       $( '#mwProtect-cascade' ).prop( { checked: false, disabled: true } );
+                                       return false;
+                               } else {
+                                       $( '#mwProtect-cascade' ).prop( 'disabled', false );
+                               }
                        } );
-                       this.getExpiryInputs().each( function () {
-                               this.value = '';
+               },
+
+               /**
+                * Checks if a certain protection level is cascadeable.
+                *
+                * @param {string} level
+                * @return {boolean}
+                */
+               isCascadeableLevel: function ( level ) {
+                       return $.inArray( level, mw.config.get( 'wgCascadeableLevels' ) ) !== -1;
+               },
+
+               /**
+                * When protection levels are locked together, update the rest
+                * when one action's level changes
+                *
+                * @param {Element} source Level selector that changed
+                */
+               updateLevels: function ( source ) {
+                       if ( !this.isUnchained() ) {
+                               this.setAllSelectors( source.selectedIndex );
+                       }
+                       this.updateCascadeCheckbox();
+               },
+
+               /**
+                * When protection levels are locked together, update the
+                * expiries when one changes
+                *
+                * @param {Element} source expiry input that changed
+                */
+
+               updateExpiry: function ( source ) {
+                       if ( !this.isUnchained() ) {
+                               this.getExpiryInputs().each( function () {
+                                       this.value = source.value;
+                               } );
+                       }
+                       if ( this.isUnchained() ) {
+                               $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
+                       } else {
+                               this.getExpirySelectors().each( function () {
+                                       this.value = 'othertime';
+                               } );
+                       }
+               },
+
+               /**
+                * When protection levels are locked together, update the
+                * expiry lists when one changes and clear the custom inputs
+                *
+                * @param {Element} source Expiry selector that changed
+                */
+               updateExpiryList: function ( source ) {
+                       if ( !this.isUnchained() ) {
+                               this.getExpirySelectors().each( function () {
+                                       this.value = source.value;
+                               } );
+                               this.getExpiryInputs().each( function () {
+                                       this.value = '';
+                               } );
+                       }
+               },
+
+               /**
+                * Update chain status and enable/disable various bits of the UI
+                * when the user changes the "unlock move permissions" checkbox
+                */
+               onChainClick: function () {
+                       this.toggleUnchainedInputs( this.isUnchained() );
+                       if ( !this.isUnchained() ) {
+                               this.setAllSelectors( this.getMaxLevel() );
+                       }
+                       this.updateCascadeCheckbox();
+               },
+
+               /**
+                * Returns true if the named attribute in all objects in the given array are matching
+                *
+                * @param {Object[]} objects
+                * @param {string} attrName
+                * @return {boolean}
+                */
+               matchAttribute: function ( objects, attrName ) {
+                       return $.map( objects, function ( object ) {
+                               return object[ attrName ];
+                       } ).filter( function ( item, index, a ) {
+                               return index === a.indexOf( item );
+                       } ).length === 1;
+               },
+
+               /**
+                * Are all actions protected at the same level, with the same expiry time?
+                *
+                * @return {boolean}
+                */
+               areAllTypesMatching: function () {
+                       return this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' ) &&
+                               this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' ) &&
+                               this.matchAttribute( this.getExpiryInputs(), 'value' );
+               },
+
+               /**
+                * Is protection chaining off?
+                *
+                * @return {boolean}
+                */
+               isUnchained: function () {
+                       var element = document.getElementById( 'mwProtectUnchained' );
+                       return element ?
+                               element.checked :
+                               true; // No control, so we need to let the user set both levels
+               },
+
+               /**
+                * Find the highest protection level in any selector
+                *
+                * @return {number}
+                */
+               getMaxLevel: function () {
+                       return Math.max.apply( Math, this.getLevelSelectors().map( function () {
+                               return this.selectedIndex;
+                       } ) );
+               },
+
+               /**
+                * Protect all actions at the specified level
+                *
+                * @param {number} index Protection level
+                */
+               setAllSelectors: function ( index ) {
+                       this.getLevelSelectors().each( function () {
+                               this.selectedIndex = index;
                        } );
+               },
+
+               /**
+                * Get a list of all protection selectors on the page
+                *
+                * @return {jQuery}
+                */
+               getLevelSelectors: function () {
+                       return $( 'select[id ^= mwProtect-level-]' );
+               },
+
+               /**
+                * Get a list of all expiry inputs on the page
+                *
+                * @return {jQuery}
+                */
+               getExpiryInputs: function () {
+                       return $( 'input[id ^= mwProtect-][id $= -expires]' );
+               },
+
+               /**
+                * Get a list of all expiry selector lists on the page
+                *
+                * @return {jQuery}
+                */
+               getExpirySelectors: function () {
+                       return $( 'select[id ^= mwProtectExpirySelection-]' );
+               },
+
+               /**
+                * Enable/disable protection selectors and expiry inputs
+                *
+                * @param {boolean} val Enable?
+                */
+               toggleUnchainedInputs: function ( val ) {
+                       var setDisabled = function () { this.disabled = !val; };
+                       this.getLevelSelectors().slice( 1 ).each( setDisabled );
+                       this.getExpiryInputs().slice( 1 ).each( setDisabled );
+                       this.getExpirySelectors().slice( 1 ).each( setDisabled );
                }
-       },
-
-       /**
-        * Update chain status and enable/disable various bits of the UI
-        * when the user changes the "unlock move permissions" checkbox
-        */
-       onChainClick: function () {
-               this.toggleUnchainedInputs( this.isUnchained() );
-               if ( !this.isUnchained() ) {
-                       this.setAllSelectors( this.getMaxLevel() );
-               }
-               this.updateCascadeCheckbox();
-       },
-
-       /**
-        * Returns true if the named attribute in all objects in the given array are matching
-        *
-        * @param {Object[]} objects
-        * @param {string} attrName
-        * @return {boolean}
-        */
-       matchAttribute: function ( objects, attrName ) {
-               return $.map( objects, function ( object ) {
-                       return object[ attrName ];
-               } ).filter( function ( item, index, a ) {
-                       return index === a.indexOf( item );
-               } ).length === 1;
-       },
-
-       /**
-        * Are all actions protected at the same level, with the same expiry time?
-        *
-        * @return {boolean}
-        */
-       areAllTypesMatching: function () {
-               return this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' )
-                       && this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' )
-                       && this.matchAttribute( this.getExpiryInputs(), 'value' );
-       },
-
-       /**
-        * Is protection chaining off?
-        *
-        * @return {boolean}
-        */
-       isUnchained: function () {
-               var element = document.getElementById( 'mwProtectUnchained' );
-               return element
-                       ? element.checked
-                       : true; // No control, so we need to let the user set both levels
-       },
-
-       /**
-        * Find the highest protection level in any selector
-        *
-        * @return {number}
-        */
-       getMaxLevel: function () {
-               return Math.max.apply( Math, this.getLevelSelectors().map( function () {
-                       return this.selectedIndex;
-               } ) );
-       },
-
-       /**
-        * Protect all actions at the specified level
-        *
-        * @param {number} index Protection level
-        */
-       setAllSelectors: function ( index ) {
-               this.getLevelSelectors().each( function () {
-                       this.selectedIndex = index;
-               } );
-       },
-
-       /**
-        * Get a list of all protection selectors on the page
-        *
-        * @return {jQuery}
-        */
-       getLevelSelectors: function () {
-               return $( 'select[id ^= mwProtect-level-]' );
-       },
-
-       /**
-        * Get a list of all expiry inputs on the page
-        *
-        * @return {jQuery}
-        */
-       getExpiryInputs: function () {
-               return $( 'input[id ^= mwProtect-][id $= -expires]' );
-       },
-
-       /**
-        * Get a list of all expiry selector lists on the page
-        *
-        * @return {jQuery}
-        */
-       getExpirySelectors: function () {
-               return $( 'select[id ^= mwProtectExpirySelection-]' );
-       },
-
-       /**
-        * Enable/disable protection selectors and expiry inputs
-        *
-        * @param {boolean} val Enable?
-        */
-       toggleUnchainedInputs: function ( val ) {
-               var setDisabled = function () { this.disabled = !val; };
-               this.getLevelSelectors().slice( 1 ).each( setDisabled );
-               this.getExpiryInputs().slice( 1 ).each( setDisabled );
-               this.getExpirySelectors().slice( 1 ).each( setDisabled );
-       }
-};
-
-$( ProtectionForm.init.bind( ProtectionForm ) );
+       };
+
+       $( ProtectionForm.init.bind( ProtectionForm ) );
 
 }( mediaWiki, jQuery ) );
index 38271a0..70a2ecd 100644 (file)
         * @deprecated since 1.17 Use mw.loader instead. Warnings added in 1.25.
         */
        function importScriptURI( url ) {
+               var s;
                if ( loadedScripts[ url ] ) {
                        return null;
                }
                loadedScripts[ url ] = true;
-               var s = document.createElement( 'script' );
+               s = document.createElement( 'script' );
                s.setAttribute( 'src', url );
                document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
                return s;
index 68fb2aa..558fd4c 100644 (file)
@@ -1,4 +1,3 @@
-/*global OO */
 ( function ( mw, $ ) {
        /**
         * Factory for MessagePoster objects. This provides a pluggable to way to script the action
index 89a314c..16c10fb 100644 (file)
@@ -1,4 +1,4 @@
-/*global OO */
+/* eslint-disable no-use-before-define */
 ( function ( $, mw, OO ) {
        'use strict';
        var ApiSandbox, Util, WidgetMethods, Validators,
                                                } else {
                                                        n = +value;
                                                        return !isNaN( n ) && isFinite( n ) &&
-                                                               /* jshint bitwise: false */
+                                                               // eslint-disable-next-line no-bitwise
                                                                ( n | 0 ) === n &&
-                                                               /* jshint bitwise: true */
                                                                n >= pi.min && n <= pi.apiSandboxMax;
                                                }
                                        } );
index 69a2a67..3e6e684 100644 (file)
@@ -6,9 +6,10 @@
                var $tagList = $( '#mw-edittags-tag-list' );
                if ( $tagList.length ) {
                        $tagList.chosen( {
-                               /*jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+                               /* eslint-disable camelcase */
                                placeholder_text_multiple: mw.msg( 'tags-edit-chosen-placeholder' ),
                                no_results_text: mw.msg( 'tags-edit-chosen-no-results' )
+                               /* eslint-enable camelcase */
                        } );
                }
 
index dd48367..9e81c4e 100644 (file)
@@ -5,8 +5,11 @@
  * @class mw.special.upload
  * @singleton
  */
+
+/* eslint-disable no-use-before-define */
+/* global Uint8Array */
+
 ( function ( mw, $ ) {
-       /*jshint latedef:false */
        var uploadWarning, uploadLicense,
                ajaxUploadDestCheck = mw.config.get( 'wgAjaxUploadDestCheck' ),
                $license = $( '#wpLicense' );
                        }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
                                try {
                                        meta = mw.libs.jpegmeta( data, file.fileName );
-                                       // jscs:disable requireCamelCaseOrUpperCaseIdentifiers, disallowDanglingUnderscores
+                                       // eslint-disable-next-line no-underscore-dangle, camelcase
                                        meta._binary_data = null;
-                                       // jscs:enable
                                } catch ( e ) {
                                        meta = null;
                                }
                if ( hasFileAPI() ) {
                        // Update thumbnail when the file selection control is updated.
                        $( '#wpUploadFile' ).change( function () {
+                               var file;
                                clearPreview();
                                if ( this.files && this.files.length ) {
                                        // Note: would need to be updated to handle multiple files.
-                                       var file = this.files[ 0 ];
+                                       file = this.files[ 0 ];
 
                                        if ( !checkMaxUploadSize( file ) ) {
                                                return;
index 90398d5..6e8dbf3 100644 (file)
         *  - 'clip': "Jan 32" => "Jan 31", "Feb 32" => "Feb 28" (or 29), "Feb 0" => "Feb 1", etc.
         * @return {Date} Adjusted date
         */
-       mw.widgets.datetime.DateTimeFormatter.prototype.adjustComponent = function ( date /*, component, delta, mode */ ) {
+       mw.widgets.datetime.DateTimeFormatter.prototype.adjustComponent = function ( date /* , component, delta, mode */ ) {
                // Should be overridden by subclass
                return date;
        };
index 3e0d91d..9fea5f8 100644 (file)
@@ -4,7 +4,7 @@
  * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
  * @license The MIT License (MIT); see LICENSE.txt
  */
-/*global moment */
+/* global moment */
 ( function ( $, mw ) {
 
        /**
                selected = moment( this.getDate(), this.getDateFormat() );
 
                switch ( this.displayLayer ) {
-               case 'month':
-                       this.labelButton.setLabel( this.moment.format( 'MMMM YYYY' ) );
-                       this.upButton.toggle( true );
-
-                       // First week displayed is the first week spanned by the month, unless it begins on Monday, in
-                       // which case first week displayed is the previous week. This makes the calendar "balanced"
-                       // and also neatly handles 28-day February sometimes spanning only 4 weeks.
-                       currentDay = moment( this.moment ).startOf( 'month' ).subtract( 1, 'day' ).startOf( 'week' );
-
-                       // Day-of-week labels. Localisation-independent: works with weeks starting on Saturday, Sunday
-                       // or Monday.
-                       for ( i = 0; i < 7; i++ ) {
-                               items.push(
-                                       $( '<div>' )
-                                               .addClass( 'mw-widget-calendarWidget-day-heading' )
-                                               .text( currentDay.format( 'dd' ) )
-                               );
-                               currentDay.add( 1, 'day' );
-                       }
-                       currentDay.subtract( 7, 'days' );
-
-                       // Actual calendar month. Always displays 6 weeks, for consistency (months can span 4 to 6
-                       // weeks).
-                       for ( i = 0; i < 42; i++ ) {
-                               items.push(
-                                       $( '<div>' )
-                                               .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-day' )
-                                               .toggleClass( 'mw-widget-calendarWidget-day-additional', !currentDay.isSame( this.moment, 'month' ) )
-                                               .toggleClass( 'mw-widget-calendarWidget-day-today', currentDay.isSame( today, 'day' ) )
-                                               .toggleClass( 'mw-widget-calendarWidget-item-selected', currentDay.isSame( selected, 'day' ) )
-                                               .text( currentDay.format( 'D' ) )
-                                               .data( 'date', currentDay.date() )
-                                               .data( 'month', currentDay.month() )
-                                               .data( 'year', currentDay.year() )
-                               );
-                               currentDay.add( 1, 'day' );
-                       }
-                       break;
-
-               case 'year':
-                       this.labelButton.setLabel( this.moment.format( 'YYYY' ) );
-                       this.upButton.toggle( true );
-
-                       currentMonth = moment( this.moment ).startOf( 'year' );
-                       for ( i = 0; i < 12; i++ ) {
-                               items.push(
-                                       $( '<div>' )
-                                               .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-month' )
-                                               .toggleClass( 'mw-widget-calendarWidget-item-selected', currentMonth.isSame( selected, 'month' ) )
-                                               .text( currentMonth.format( 'MMMM' ) )
-                                               .data( 'month', currentMonth.month() )
-                               );
-                               currentMonth.add( 1, 'month' );
-                       }
-                       // Shuffle the array to display months in columns rather than rows.
-                       items = [
-                               items[ 0 ], items[ 6 ],      //  | January  | July      |
-                               items[ 1 ], items[ 7 ],      //  | February | August    |
-                               items[ 2 ], items[ 8 ],      //  | March    | September |
-                               items[ 3 ], items[ 9 ],      //  | April    | October   |
-                               items[ 4 ], items[ 10 ],     //  | May      | November  |
-                               items[ 5 ], items[ 11 ]      //  | June     | December  |
-                       ];
-                       break;
-
-               case 'duodecade':
-                       this.labelButton.setLabel( null );
-                       this.upButton.toggle( false );
-
-                       currentYear = moment( { year: Math.floor( this.moment.year() / 20 ) * 20 } );
-                       for ( i = 0; i < 20; i++ ) {
-                               items.push(
-                                       $( '<div>' )
-                                               .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-year' )
-                                               .toggleClass( 'mw-widget-calendarWidget-item-selected', currentYear.isSame( selected, 'year' ) )
-                                               .text( currentYear.format( 'YYYY' ) )
-                                               .data( 'year', currentYear.year() )
-                               );
-                               currentYear.add( 1, 'year' );
-                       }
-                       break;
+                       case 'month':
+                               this.labelButton.setLabel( this.moment.format( 'MMMM YYYY' ) );
+                               this.upButton.toggle( true );
+
+                               // First week displayed is the first week spanned by the month, unless it begins on Monday, in
+                               // which case first week displayed is the previous week. This makes the calendar "balanced"
+                               // and also neatly handles 28-day February sometimes spanning only 4 weeks.
+                               currentDay = moment( this.moment ).startOf( 'month' ).subtract( 1, 'day' ).startOf( 'week' );
+
+                               // Day-of-week labels. Localisation-independent: works with weeks starting on Saturday, Sunday
+                               // or Monday.
+                               for ( i = 0; i < 7; i++ ) {
+                                       items.push(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-widget-calendarWidget-day-heading' )
+                                                       .text( currentDay.format( 'dd' ) )
+                                       );
+                                       currentDay.add( 1, 'day' );
+                               }
+                               currentDay.subtract( 7, 'days' );
+
+                               // Actual calendar month. Always displays 6 weeks, for consistency (months can span 4 to 6
+                               // weeks).
+                               for ( i = 0; i < 42; i++ ) {
+                                       items.push(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-day' )
+                                                       .toggleClass( 'mw-widget-calendarWidget-day-additional', !currentDay.isSame( this.moment, 'month' ) )
+                                                       .toggleClass( 'mw-widget-calendarWidget-day-today', currentDay.isSame( today, 'day' ) )
+                                                       .toggleClass( 'mw-widget-calendarWidget-item-selected', currentDay.isSame( selected, 'day' ) )
+                                                       .text( currentDay.format( 'D' ) )
+                                                       .data( 'date', currentDay.date() )
+                                                       .data( 'month', currentDay.month() )
+                                                       .data( 'year', currentDay.year() )
+                                       );
+                                       currentDay.add( 1, 'day' );
+                               }
+                               break;
+
+                       case 'year':
+                               this.labelButton.setLabel( this.moment.format( 'YYYY' ) );
+                               this.upButton.toggle( true );
+
+                               currentMonth = moment( this.moment ).startOf( 'year' );
+                               for ( i = 0; i < 12; i++ ) {
+                                       items.push(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-month' )
+                                                       .toggleClass( 'mw-widget-calendarWidget-item-selected', currentMonth.isSame( selected, 'month' ) )
+                                                       .text( currentMonth.format( 'MMMM' ) )
+                                                       .data( 'month', currentMonth.month() )
+                                       );
+                                       currentMonth.add( 1, 'month' );
+                               }
+                               // Shuffle the array to display months in columns rather than rows.
+                               items = [
+                                       items[ 0 ], items[ 6 ],      //  | January  | July      |
+                                       items[ 1 ], items[ 7 ],      //  | February | August    |
+                                       items[ 2 ], items[ 8 ],      //  | March    | September |
+                                       items[ 3 ], items[ 9 ],      //  | April    | October   |
+                                       items[ 4 ], items[ 10 ],     //  | May      | November  |
+                                       items[ 5 ], items[ 11 ]      //  | June     | December  |
+                               ];
+                               break;
+
+                       case 'duodecade':
+                               this.labelButton.setLabel( null );
+                               this.upButton.toggle( false );
+
+                               currentYear = moment( { year: Math.floor( this.moment.year() / 20 ) * 20 } );
+                               for ( i = 0; i < 20; i++ ) {
+                                       items.push(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-widget-calendarWidget-item mw-widget-calendarWidget-year' )
+                                                       .toggleClass( 'mw-widget-calendarWidget-item-selected', currentYear.isSame( selected, 'year' ) )
+                                                       .text( currentYear.format( 'YYYY' ) )
+                                                       .data( 'year', currentYear.year() )
+                                       );
+                                       currentYear.add( 1, 'year' );
+                               }
+                               break;
                }
 
                this.$body.append.apply( this.$body, items );
         */
        mw.widgets.CalendarWidget.prototype.onPrevButtonClick = function () {
                switch ( this.displayLayer ) {
-               case 'month':
-                       this.moment.subtract( 1, 'month' );
-                       break;
-               case 'year':
-                       this.moment.subtract( 1, 'year' );
-                       break;
-               case 'duodecade':
-                       this.moment.subtract( 20, 'years' );
-                       break;
+                       case 'month':
+                               this.moment.subtract( 1, 'month' );
+                               break;
+                       case 'year':
+                               this.moment.subtract( 1, 'year' );
+                               break;
+                       case 'duodecade':
+                               this.moment.subtract( 20, 'years' );
+                               break;
                }
                this.updateUI( 'previous' );
        };
         */
        mw.widgets.CalendarWidget.prototype.onNextButtonClick = function () {
                switch ( this.displayLayer ) {
-               case 'month':
-                       this.moment.add( 1, 'month' );
-                       break;
-               case 'year':
-                       this.moment.add( 1, 'year' );
-                       break;
-               case 'duodecade':
-                       this.moment.add( 20, 'years' );
-                       break;
+                       case 'month':
+                               this.moment.add( 1, 'month' );
+                               break;
+                       case 'year':
+                               this.moment.add( 1, 'year' );
+                               break;
+                       case 'duodecade':
+                               this.moment.add( 20, 'years' );
+                               break;
                }
                this.updateUI( 'next' );
        };
 
                if ( !this.isDisabled() ) {
                        switch ( e.which ) {
-                       case prevDirectionKey:
-                               this.moment.subtract( 1, this.precision === 'month' ? 'month' : 'day' );
-                               break;
-                       case nextDirectionKey:
-                               this.moment.add( 1, this.precision === 'month' ? 'month' : 'day' );
-                               break;
-                       case OO.ui.Keys.UP:
-                               this.moment.subtract( 1, this.precision === 'month' ? 'month' : 'week' );
-                               break;
-                       case OO.ui.Keys.DOWN:
-                               this.moment.add( 1, this.precision === 'month' ? 'month' : 'week' );
-                               break;
-                       case OO.ui.Keys.PAGEUP:
-                               this.moment.subtract( 1, this.precision === 'month' ? 'year' : 'month' );
-                               break;
-                       case OO.ui.Keys.PAGEDOWN:
-                               this.moment.add( 1, this.precision === 'month' ? 'year' : 'month' );
-                               break;
-                       default:
-                               changed = false;
-                               break;
+                               case prevDirectionKey:
+                                       this.moment.subtract( 1, this.precision === 'month' ? 'month' : 'day' );
+                                       break;
+                               case nextDirectionKey:
+                                       this.moment.add( 1, this.precision === 'month' ? 'month' : 'day' );
+                                       break;
+                               case OO.ui.Keys.UP:
+                                       this.moment.subtract( 1, this.precision === 'month' ? 'month' : 'week' );
+                                       break;
+                               case OO.ui.Keys.DOWN:
+                                       this.moment.add( 1, this.precision === 'month' ? 'month' : 'week' );
+                                       break;
+                               case OO.ui.Keys.PAGEUP:
+                                       this.moment.subtract( 1, this.precision === 'month' ? 'year' : 'month' );
+                                       break;
+                               case OO.ui.Keys.PAGEDOWN:
+                                       this.moment.add( 1, this.precision === 'month' ? 'year' : 'month' );
+                                       break;
+                               default:
+                                       changed = false;
+                                       break;
                        }
 
                        if ( changed ) {
index 267eebb..e0ec8e7 100644 (file)
                                        prop: 'categories',
                                        cllimit: this.limit,
                                        titles: 'Category:' + input
-                               } ).done( function ( res )  {
+                               } ).done( function ( res ) {
                                        var categories = [];
 
                                        $.each( res.query.pages, function ( index, page ) {
index ed251f2..167cb4f 100644 (file)
@@ -4,7 +4,7 @@
  * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
  * @license The MIT License (MIT); see LICENSE.txt
  */
-/*global moment */
+/* global moment */
 ( function ( $, mw ) {
 
        /**
@@ -87,6 +87,8 @@
         *     calendar uses relative positioning.
         */
        mw.widgets.DateInputWidget = function MWWDateInputWidget( config ) {
+               var placeholderDateFormat, mustBeAfter, mustBeBefore;
+
                // Config initialization
                config = $.extend( {
                        precision: 'day',
                        }
                }
 
-               var placeholderDateFormat, mustBeAfter, mustBeBefore;
                if ( config.placeholderDateFormat ) {
                        placeholderDateFormat = config.placeholderDateFormat;
                } else if ( config.inputFormat ) {
         * @return {string} Format string
         */
        mw.widgets.DateInputWidget.prototype.getDisplayFormat = function () {
+               var localeData, llll, lll, ll, format;
+
                if ( this.displayFormat !== undefined ) {
                        return this.displayFormat;
                }
                        // We try to construct it as 'llll - (lll - ll)' and hope for the best.
                        // This seems to work well for many languages (maybe even all?).
 
-                       var localeData = moment.localeData( moment.locale() ),
-                               llll = localeData.longDateFormat( 'llll' ),
-                               lll = localeData.longDateFormat( 'lll' ),
-                               ll = localeData.longDateFormat( 'll' ),
-                               format = llll.replace( lll.replace( ll, '' ), '' );
+                       localeData = moment.localeData( moment.locale() );
+                       llll = localeData.longDateFormat( 'llll' );
+                       lll = localeData.longDateFormat( 'lll' );
+                       ll = localeData.longDateFormat( 'll' );
+                       format = llll.replace( lll.replace( ll, '' ), '' );
 
                        return format;
                }
index 7ca19df..e1e50ea 100644 (file)
@@ -7,14 +7,14 @@
 ( function ( $, mw ) {
 
        var interwikiPrefixesPromise = new mw.Api().get( {
-                       action: 'query',
-                       meta: 'siteinfo',
-                       siprop: 'interwikimap'
-               } ).then( function ( data ) {
-                       return $.map( data.query.interwikimap, function ( interwiki ) {
-                               return interwiki.prefix;
-                       } );
+               action: 'query',
+               meta: 'siteinfo',
+               siprop: 'interwikimap'
+       } ).then( function ( data ) {
+               return $.map( data.query.interwikimap, function ( interwiki ) {
+                       return interwiki.prefix;
                } );
+       } );
 
        /**
         * Mixin for title widgets
                        description = mw.msg( 'mw-widgets-titleinput-description-new-page' );
                }
                return {
-                       data: this.namespace !== null && this.relative
-                               ? mwTitle.getRelativeText( this.namespace )
-                               title,
+                       data: this.namespace !== null && this.relative ?
+                               mwTitle.getRelativeText( this.namespace ) :
+                               title,
                        url: mwTitle.getUrl(),
                        imageUrl: this.showImages ? data.imageUrl : null,
                        description: this.showDescriptions ? description : null,
index f51403f..1a3cdd5 100644 (file)
                this.apiUrl = String( url );
                this.anonymous = options && options.anonymous;
 
-               options = $.extend( /*deep=*/ true,
+               options = $.extend( /* deep=*/ true,
                        {
                                ajax: {
                                        url: this.apiUrl,
                                        xhrFields: {
-                                               withCredentials: this.anonymous ? false : true
+                                               withCredentials: !this.anonymous
                                        }
                                },
                                parameters: {
index b7579ff..529bd9f 100644 (file)
                                } )
                                // AJAX success just means "200 OK" response, also check API error codes
                                .done( function ( result, textStatus, jqXHR ) {
+                                       var code;
                                        if ( result === undefined || result === null || result === '' ) {
                                                apiDeferred.reject( 'ok-but-empty',
                                                        'OK response but empty result (check HTTP headers?)',
                                                        jqXHR
                                                );
                                        } else if ( result.error ) {
-                                               var code = result.error.code === undefined ? 'unknown' : result.error.code;
+                                               code = result.error.code === undefined ? 'unknown' : result.error.code;
                                                apiDeferred.reject( code, result, result, jqXHR );
                                        } else {
                                                apiDeferred.resolve( result, jqXHR );
index bb3a913..21fad5e 100644 (file)
                        var basetimestamp, curtimestamp,
                                api = this;
                        return api.get( {
-                                       action: 'query',
-                                       prop: 'revisions',
-                                       rvprop: [ 'content', 'timestamp' ],
-                                       titles: String( title ),
-                                       formatversion: '2',
-                                       curtimestamp: true
-                               } )
+                               action: 'query',
+                               prop: 'revisions',
+                               rvprop: [ 'content', 'timestamp' ],
+                               titles: String( title ),
+                               formatversion: '2',
+                               curtimestamp: true
+                       } )
                                .then( function ( data ) {
                                        var page, revision;
                                        if ( !data.query || !data.query.pages ) {
index 418fd23..f38e88b 100644 (file)
                 * @return {string} return.done.data Parsed HTML of `wikitext`.
                 */
                parse: function ( content, additionalParams ) {
-                       var apiPromise, config = $.extend( {
-                               formatversion: 2,
-                               action: 'parse',
-                               contentmodel: 'wikitext'
-                       }, additionalParams );
+                       var apiPromise,
+                               config = $.extend( {
+                                       formatversion: 2,
+                                       action: 'parse',
+                                       contentmodel: 'wikitext'
+                               }, additionalParams );
 
                        if ( mw.Title && content instanceof mw.Title ) {
                                // Parse existing page
index 8169449..bf839ad 100644 (file)
@@ -34,7 +34,8 @@
         * @return {string}
         */
        function getFirstKey( obj ) {
-               for ( var key in obj ) {
+               var key;
+               for ( key in obj ) {
                        if ( obj.hasOwnProperty( key ) ) {
                                return key;
                        }
index c4513d5..5f60097 100644 (file)
 
        mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
                $root.find( '.mw-htmlform-hide-if' ).each( function () {
-                       var v, i, fields, test, func, spec, self, modules, data,extraModules,
+                       var v, i, fields, test, func, spec, self, modules, data, extraModules,
                                $el = $( this );
 
                        modules = [];
index 791b8ea..37c0554 100644 (file)
@@ -18,8 +18,8 @@
                } );
                $oldContainer.find( 'input' ).each( function () {
                        var $oldInput = $( this ),
-                       checked = $oldInput.prop( 'checked' ),
-                       $option = $( '<option>' );
+                               checked = $oldInput.prop( 'checked' ),
+                               $option = $( '<option>' );
                        $option.prop( 'value', $oldInput.prop( 'value' ) );
                        if ( checked ) {
                                $option.prop( 'selected', true );
index 741771a..2712ddb 100644 (file)
@@ -1,4 +1,4 @@
-/*global moment */
+/* global moment, Uint8Array */
 ( function ( $, mw ) {
 
        /**
                if ( file && file.type === 'image/jpeg' ) {
                        fileReader = new FileReader();
                        fileReader.onload = function () {
+                               // TODO: fileStr is never used.
                                var fileStr, arr, i, metadata;
 
                                if ( typeof fileReader.result === 'string' ) {
index e468768..cda389f 100644 (file)
@@ -3,9 +3,10 @@
  * @author Timo Tijhof, 2011-2013
  * @since 1.18
  */
-( function ( mw, $ ) {
-       /*jshint latedef:false */
 
+/* eslint-disable no-use-before-define */
+
+( function ( mw, $ ) {
        /**
         * Parse titles into an object structure. Note that when using the constructor
         * directly, passing invalid titles will result in an exception. Use #newFromText to use the
 
        /* Private members */
 
+       // eslint-disable-next-line vars-on-top
        var
+               namespaceIds = mw.config.get( 'wgNamespaceIds' ),
 
-       namespaceIds = mw.config.get( 'wgNamespaceIds' ),
+               /**
+                * @private
+                * @static
+                * @property NS_MAIN
+                */
+               NS_MAIN = namespaceIds[ '' ],
 
-       /**
-        * @private
-        * @static
-        * @property NS_MAIN
-        */
-       NS_MAIN = namespaceIds[ '' ],
+               /**
+                * @private
+                * @static
+                * @property NS_TALK
+                */
+               NS_TALK = namespaceIds.talk,
 
-       /**
-        * @private
-        * @static
-        * @property NS_TALK
-        */
-       NS_TALK = namespaceIds.talk,
+               /**
+                * @private
+                * @static
+                * @property NS_SPECIAL
+                */
+               NS_SPECIAL = namespaceIds.special,
 
-       /**
-        * @private
-        * @static
-        * @property NS_SPECIAL
-        */
-       NS_SPECIAL = namespaceIds.special,
+               /**
+                * @private
+                * @static
+                * @property NS_MEDIA
+                */
+               NS_MEDIA = namespaceIds.media,
 
-       /**
-        * @private
-        * @static
-        * @property NS_MEDIA
-        */
-       NS_MEDIA = namespaceIds.media,
+               /**
+                * @private
+                * @static
+                * @property NS_FILE
+                */
+               NS_FILE = namespaceIds.file,
 
-       /**
-        * @private
-        * @static
-        * @property NS_FILE
-        */
-       NS_FILE = namespaceIds.file,
+               /**
+                * @private
+                * @static
+                * @property FILENAME_MAX_BYTES
+                */
+               FILENAME_MAX_BYTES = 240,
 
-       /**
-        * @private
-        * @static
-        * @property FILENAME_MAX_BYTES
-        */
-       FILENAME_MAX_BYTES = 240,
+               /**
+                * @private
+                * @static
+                * @property TITLE_MAX_BYTES
+                */
+               TITLE_MAX_BYTES = 255,
 
-       /**
-        * @private
-        * @static
-        * @property TITLE_MAX_BYTES
-        */
-       TITLE_MAX_BYTES = 255,
+               /**
+                * Get the namespace id from a namespace name (either from the localized, canonical or alias
+                * name).
+                *
+                * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or
+                * even 'Bild'.
+                *
+                * @private
+                * @static
+                * @method getNsIdByName
+                * @param {string} ns Namespace name (case insensitive, leading/trailing space ignored)
+                * @return {number|boolean} Namespace id or boolean false
+                */
+               getNsIdByName = function ( ns ) {
+                       var id;
+
+                       // Don't cast non-strings to strings, because null or undefined should not result in
+                       // returning the id of a potential namespace called "Null:" (e.g. on null.example.org/wiki)
+                       // Also, toLowerCase throws exception on null/undefined, because it is a String method.
+                       if ( typeof ns !== 'string' ) {
+                               return false;
+                       }
+                       // TODO: Should just use local var namespaceIds here but it
+                       // breaks test which modify the config
+                       id = mw.config.get( 'wgNamespaceIds' )[ ns.toLowerCase() ];
+                       if ( id === undefined ) {
+                               return false;
+                       }
+                       return id;
+               },
 
-       /**
-        * Get the namespace id from a namespace name (either from the localized, canonical or alias
-        * name).
-        *
-        * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or
-        * even 'Bild'.
-        *
-        * @private
-        * @static
-        * @method getNsIdByName
-        * @param {string} ns Namespace name (case insensitive, leading/trailing space ignored)
-        * @return {number|boolean} Namespace id or boolean false
-        */
-       getNsIdByName = function ( ns ) {
-               var id;
-
-               // Don't cast non-strings to strings, because null or undefined should not result in
-               // returning the id of a potential namespace called "Null:" (e.g. on null.example.org/wiki)
-               // Also, toLowerCase throws exception on null/undefined, because it is a String method.
-               if ( typeof ns !== 'string' ) {
-                       return false;
-               }
-               // TODO: Should just use local var namespaceIds here but it
-               // breaks test which modify the config
-               id = mw.config.get( 'wgNamespaceIds' )[ ns.toLowerCase() ];
-               if ( id === undefined ) {
-                       return false;
-               }
-               return id;
-       },
+               /**
+                * @private
+                * @method getNamespacePrefix_
+                * @param {number} namespace
+                * @return {string}
+                */
+               getNamespacePrefix = function ( namespace ) {
+                       return namespace === NS_MAIN ?
+                               '' :
+                               ( mw.config.get( 'wgFormattedNamespaces' )[ namespace ].replace( / /g, '_' ) + ':' );
+               },
 
-       /**
-        * @private
-        * @method getNamespacePrefix_
-        * @param {number} namespace
-        * @return {string}
-        */
-       getNamespacePrefix = function ( namespace ) {
-               return namespace === NS_MAIN ?
-                       '' :
-                       ( mw.config.get( 'wgFormattedNamespaces' )[ namespace ].replace( / /g, '_' ) + ':' );
-       },
-
-       rUnderscoreTrim = /^_+|_+$/g,
-
-       rSplit = /^(.+?)_*:_*(.*)$/,
-
-       // See MediaWikiTitleCodec.php#getTitleInvalidRegex
-       rInvalid = new RegExp(
-               '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
-               // URL percent encoding sequences interfere with the ability
-               // to round-trip titles -- you can't link to them consistently.
-               '|%[0-9A-Fa-f]{2}' +
-               // XML/HTML character references produce similar issues.
-               '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
-               '|&#[0-9]+;' +
-               '|&#x[0-9A-Fa-f]+;'
-       ),
-
-       // From MediaWikiTitleCodec::splitTitleString() in PHP
-       // Note that this is not equivalent to /\s/, e.g. underscore is included, tab is not included.
-       rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
-
-       // From MediaWikiTitleCodec::splitTitleString() in PHP
-       rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+               rUnderscoreTrim = /^_+|_+$/g,
 
-       /**
-        * Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
-        * @private
-        * @static
-        * @property sanitationRules
-        */
-       sanitationRules = [
-               // "signature"
-               {
-                       pattern: /~{3}/g,
-                       replace: '',
-                       generalRule: true
-               },
-               // control characters
-               {
-                       pattern: /[\x00-\x1f\x7f]/g,
-                       replace: '',
-                       generalRule: true
-               },
-               // URL encoding (possibly)
-               {
-                       pattern: /%([0-9A-Fa-f]{2})/g,
-                       replace: '% $1',
-                       generalRule: true
-               },
-               // HTML-character-entities
-               {
-                       pattern: /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g,
-                       replace: '& $1',
-                       generalRule: true
-               },
-               // slash, colon (not supported by file systems like NTFS/Windows, Mac OS 9 [:], ext4 [/])
-               {
-                       pattern: new RegExp( '[' + mw.config.get( 'wgIllegalFileChars', '' ) + ']', 'g' ),
-                       replace: '-',
-                       fileRule: true
-               },
-               // brackets, greater than
-               {
-                       pattern: /[\]\}>]/g,
-                       replace: ')',
-                       generalRule: true
-               },
-               // brackets, lower than
-               {
-                       pattern: /[\[\{<]/g,
-                       replace: '(',
-                       generalRule: true
-               },
-               // everything that wasn't covered yet
-               {
-                       pattern: new RegExp( rInvalid.source, 'g' ),
-                       replace: '-',
-                       generalRule: true
-               },
-               // directory structures
-               {
-                       pattern: /^(\.|\.\.|\.\/.*|\.\.\/.*|.*\/\.\/.*|.*\/\.\.\/.*|.*\/\.|.*\/\.\.)$/g,
-                       replace: '',
-                       generalRule: true
-               }
-       ],
+               rSplit = /^(.+?)_*:_*(.*)$/,
 
-       /**
-        * Internal helper for #constructor and #newFromText.
-        *
-        * Based on Title.php#secureAndSplit
-        *
-        * @private
-        * @static
-        * @method parse
-        * @param {string} title
-        * @param {number} [defaultNamespace=NS_MAIN]
-        * @return {Object|boolean}
-        */
-       parse = function ( title, defaultNamespace ) {
-               var namespace, m, id, i, fragment, ext;
+               // See MediaWikiTitleCodec.php#getTitleInvalidRegex
+               rInvalid = new RegExp(
+                       '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
+                       // URL percent encoding sequences interfere with the ability
+                       // to round-trip titles -- you can't link to them consistently.
+                       '|%[0-9A-Fa-f]{2}' +
+                       // XML/HTML character references produce similar issues.
+                       '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
+                       '|&#[0-9]+;' +
+                       '|&#x[0-9A-Fa-f]+;'
+               ),
 
-               namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
+               // From MediaWikiTitleCodec::splitTitleString() in PHP
+               // Note that this is not equivalent to /\s/, e.g. underscore is included, tab is not included.
+               rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
 
-               title = title
-                       // Strip Unicode bidi override characters
-                       .replace( rUnicodeBidi, '' )
-                       // Normalise whitespace to underscores and remove duplicates
-                       .replace( rWhitespace, '_' )
-                       // Trim underscores
-                       .replace( rUnderscoreTrim, '' );
+               // From MediaWikiTitleCodec::splitTitleString() in PHP
+               rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+
+               /**
+                * Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
+                * @private
+                * @static
+                * @property sanitationRules
+                */
+               sanitationRules = [
+                       // "signature"
+                       {
+                               pattern: /~{3}/g,
+                               replace: '',
+                               generalRule: true
+                       },
+                       // control characters
+                       {
+                               // eslint-disable-next-line no-control-regex
+                               pattern: /[\x00-\x1f\x7f]/g,
+                               replace: '',
+                               generalRule: true
+                       },
+                       // URL encoding (possibly)
+                       {
+                               pattern: /%([0-9A-Fa-f]{2})/g,
+                               replace: '% $1',
+                               generalRule: true
+                       },
+                       // HTML-character-entities
+                       {
+                               pattern: /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g,
+                               replace: '& $1',
+                               generalRule: true
+                       },
+                       // slash, colon (not supported by file systems like NTFS/Windows, Mac OS 9 [:], ext4 [/])
+                       {
+                               pattern: new RegExp( '[' + mw.config.get( 'wgIllegalFileChars', '' ) + ']', 'g' ),
+                               replace: '-',
+                               fileRule: true
+                       },
+                       // brackets, greater than
+                       {
+                               pattern: /[\]\}>]/g,
+                               replace: ')',
+                               generalRule: true
+                       },
+                       // brackets, lower than
+                       {
+                               pattern: /[\[\{<]/g,
+                               replace: '(',
+                               generalRule: true
+                       },
+                       // everything that wasn't covered yet
+                       {
+                               pattern: new RegExp( rInvalid.source, 'g' ),
+                               replace: '-',
+                               generalRule: true
+                       },
+                       // directory structures
+                       {
+                               pattern: /^(\.|\.\.|\.\/.*|\.\.\/.*|.*\/\.\/.*|.*\/\.\.\/.*|.*\/\.|.*\/\.\.)$/g,
+                               replace: '',
+                               generalRule: true
+                       }
+               ],
+
+               /**
+                * Internal helper for #constructor and #newFromText.
+                *
+                * Based on Title.php#secureAndSplit
+                *
+                * @private
+                * @static
+                * @method parse
+                * @param {string} title
+                * @param {number} [defaultNamespace=NS_MAIN]
+                * @return {Object|boolean}
+                */
+               parse = function ( title, defaultNamespace ) {
+                       var namespace, m, id, i, fragment, ext;
+
+                       namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
 
-               // Process initial colon
-               if ( title !== '' && title[ 0 ] === ':' ) {
-                       // Initial colon means main namespace instead of specified default
-                       namespace = NS_MAIN;
                        title = title
-                               // Strip colon
-                               .slice( 1 )
+                               // Strip Unicode bidi override characters
+                               .replace( rUnicodeBidi, '' )
+                               // Normalise whitespace to underscores and remove duplicates
+                               .replace( rWhitespace, '_' )
                                // Trim underscores
                                .replace( rUnderscoreTrim, '' );
-               }
 
-               if ( title === '' ) {
-                       return false;
-               }
+                       // Process initial colon
+                       if ( title !== '' && title[ 0 ] === ':' ) {
+                               // Initial colon means main namespace instead of specified default
+                               namespace = NS_MAIN;
+                               title = title
+                                       // Strip colon
+                                       .slice( 1 )
+                                       // Trim underscores
+                                       .replace( rUnderscoreTrim, '' );
+                       }
 
-               // Process namespace prefix (if any)
-               m = title.match( rSplit );
-               if ( m ) {
-                       id = getNsIdByName( m[ 1 ] );
-                       if ( id !== false ) {
-                               // Ordinary namespace
-                               namespace = id;
-                               title = m[ 2 ];
+                       if ( title === '' ) {
+                               return false;
+                       }
 
-                               // For Talk:X pages, make sure X has no "namespace" prefix
-                               if ( namespace === NS_TALK && ( m = title.match( rSplit ) ) ) {
-                                       // Disallow titles like Talk:File:x (subject should roundtrip: talk:file:x -> file:x -> file_talk:x)
-                                       if ( getNsIdByName( m[ 1 ] ) !== false ) {
-                                               return false;
+                       // Process namespace prefix (if any)
+                       m = title.match( rSplit );
+                       if ( m ) {
+                               id = getNsIdByName( m[ 1 ] );
+                               if ( id !== false ) {
+                                       // Ordinary namespace
+                                       namespace = id;
+                                       title = m[ 2 ];
+
+                                       // For Talk:X pages, make sure X has no "namespace" prefix
+                                       if ( namespace === NS_TALK && ( m = title.match( rSplit ) ) ) {
+                                               // Disallow titles like Talk:File:x (subject should roundtrip: talk:file:x -> file:x -> file_talk:x)
+                                               if ( getNsIdByName( m[ 1 ] ) !== false ) {
+                                                       return false;
+                                               }
                                        }
                                }
                        }
-               }
-
-               // Process fragment
-               i = title.indexOf( '#' );
-               if ( i === -1 ) {
-                       fragment = null;
-               } else {
-                       fragment = title
-                               // Get segment starting after the hash
-                               .slice( i + 1 )
-                               // Convert to text
-                               // NB: Must not be trimmed ("Example#_foo" is not the same as "Example#foo")
-                               .replace( /_/g, ' ' );
 
-                       title = title
-                               // Strip hash
-                               .slice( 0, i )
-                               // Trim underscores, again (strips "_" from "bar" in "Foo_bar_#quux")
-                               .replace( rUnderscoreTrim, '' );
-               }
+                       // Process fragment
+                       i = title.indexOf( '#' );
+                       if ( i === -1 ) {
+                               fragment = null;
+                       } else {
+                               fragment = title
+                                       // Get segment starting after the hash
+                                       .slice( i + 1 )
+                                       // Convert to text
+                                       // NB: Must not be trimmed ("Example#_foo" is not the same as "Example#foo")
+                                       .replace( /_/g, ' ' );
+
+                               title = title
+                                       // Strip hash
+                                       .slice( 0, i )
+                                       // Trim underscores, again (strips "_" from "bar" in "Foo_bar_#quux")
+                                       .replace( rUnderscoreTrim, '' );
+                       }
 
-               // Reject illegal characters
-               if ( title.match( rInvalid ) ) {
-                       return false;
-               }
+                       // Reject illegal characters
+                       if ( title.match( rInvalid ) ) {
+                               return false;
+                       }
 
-               // Disallow titles that browsers or servers might resolve as directory navigation
-               if (
-                       title.indexOf( '.' ) !== -1 && (
-                               title === '.' || title === '..' ||
-                               title.indexOf( './' ) === 0 ||
-                               title.indexOf( '../' ) === 0 ||
-                               title.indexOf( '/./' ) !== -1 ||
-                               title.indexOf( '/../' ) !== -1 ||
-                               title.slice( -2 ) === '/.' ||
-                               title.slice( -3 ) === '/..'
-                       )
-               ) {
-                       return false;
-               }
+                       // Disallow titles that browsers or servers might resolve as directory navigation
+                       if (
+                               title.indexOf( '.' ) !== -1 && (
+                                       title === '.' || title === '..' ||
+                                       title.indexOf( './' ) === 0 ||
+                                       title.indexOf( '../' ) === 0 ||
+                                       title.indexOf( '/./' ) !== -1 ||
+                                       title.indexOf( '/../' ) !== -1 ||
+                                       title.slice( -2 ) === '/.' ||
+                                       title.slice( -3 ) === '/..'
+                               )
+                       ) {
+                               return false;
+                       }
 
-               // Disallow magic tilde sequence
-               if ( title.indexOf( '~~~' ) !== -1 ) {
-                       return false;
-               }
+                       // Disallow magic tilde sequence
+                       if ( title.indexOf( '~~~' ) !== -1 ) {
+                               return false;
+                       }
 
-               // Disallow titles exceeding the TITLE_MAX_BYTES byte size limit (size of underlying database field)
-               // Except for special pages, e.g. [[Special:Block/Long name]]
-               // Note: The PHP implementation also asserts that even in NS_SPECIAL, the title should
-               // be less than 512 bytes.
-               if ( namespace !== NS_SPECIAL && $.byteLength( title ) > TITLE_MAX_BYTES ) {
-                       return false;
-               }
+                       // Disallow titles exceeding the TITLE_MAX_BYTES byte size limit (size of underlying database field)
+                       // Except for special pages, e.g. [[Special:Block/Long name]]
+                       // Note: The PHP implementation also asserts that even in NS_SPECIAL, the title should
+                       // be less than 512 bytes.
+                       if ( namespace !== NS_SPECIAL && $.byteLength( title ) > TITLE_MAX_BYTES ) {
+                               return false;
+                       }
 
-               // Can't make a link to a namespace alone.
-               if ( title === '' && namespace !== NS_MAIN ) {
-                       return false;
-               }
+                       // Can't make a link to a namespace alone.
+                       if ( title === '' && namespace !== NS_MAIN ) {
+                               return false;
+                       }
 
-               // Any remaining initial :s are illegal.
-               if ( title[ 0 ] === ':' ) {
-                       return false;
-               }
+                       // Any remaining initial :s are illegal.
+                       if ( title[ 0 ] === ':' ) {
+                               return false;
+                       }
 
-               // For backwards-compatibility with old mw.Title, we separate the extension from the
-               // rest of the title.
-               i = title.lastIndexOf( '.' );
-               if ( i === -1 || title.length <= i + 1 ) {
-                       // Extensions are the non-empty segment after the last dot
-                       ext = null;
-               } else {
-                       ext = title.slice( i + 1 );
-                       title = title.slice( 0, i );
-               }
+                       // For backwards-compatibility with old mw.Title, we separate the extension from the
+                       // rest of the title.
+                       i = title.lastIndexOf( '.' );
+                       if ( i === -1 || title.length <= i + 1 ) {
+                               // Extensions are the non-empty segment after the last dot
+                               ext = null;
+                       } else {
+                               ext = title.slice( i + 1 );
+                               title = title.slice( 0, i );
+                       }
 
-               return {
-                       namespace: namespace,
-                       title: title,
-                       ext: ext,
-                       fragment: fragment
-               };
-       },
+                       return {
+                               namespace: namespace,
+                               title: title,
+                               ext: ext,
+                               fragment: fragment
+                       };
+               },
 
-       /**
-        * Convert db-key to readable text.
-        *
-        * @private
-        * @static
-        * @method text
-        * @param {string} s
-        * @return {string}
-        */
-       text = function ( s ) {
-               if ( s !== null && s !== undefined ) {
-                       return s.replace( /_/g, ' ' );
-               } else {
-                       return '';
-               }
-       },
+               /**
+                * Convert db-key to readable text.
+                *
+                * @private
+                * @static
+                * @method text
+                * @param {string} s
+                * @return {string}
+                */
+               text = function ( s ) {
+                       if ( s !== null && s !== undefined ) {
+                               return s.replace( /_/g, ' ' );
+                       } else {
+                               return '';
+                       }
+               },
 
-       /**
-        * Sanitizes a string based on a rule set and a filter
-        *
-        * @private
-        * @static
-        * @method sanitize
-        * @param {string} s
-        * @param {Array} filter
-        * @return {string}
-        */
-       sanitize = function ( s, filter ) {
-               var i, ruleLength, rule, m, filterLength,
-                       rules = sanitationRules;
-
-               for ( i = 0, ruleLength = rules.length; i < ruleLength; ++i ) {
-                       rule = rules[ i ];
-                       for ( m = 0, filterLength = filter.length; m < filterLength; ++m ) {
-                               if ( rule[ filter[ m ] ] ) {
-                                       s = s.replace( rule.pattern, rule.replace );
+               /**
+                * Sanitizes a string based on a rule set and a filter
+                *
+                * @private
+                * @static
+                * @method sanitize
+                * @param {string} s
+                * @param {Array} filter
+                * @return {string}
+                */
+               sanitize = function ( s, filter ) {
+                       var i, ruleLength, rule, m, filterLength,
+                               rules = sanitationRules;
+
+                       for ( i = 0, ruleLength = rules.length; i < ruleLength; ++i ) {
+                               rule = rules[ i ];
+                               for ( m = 0, filterLength = filter.length; m < filterLength; ++m ) {
+                                       if ( rule[ filter[ m ] ] ) {
+                                               s = s.replace( rule.pattern, rule.replace );
+                                       }
                                }
                        }
-               }
-               return s;
-       },
-
-       /**
-        * Cuts a string to a specific byte length, assuming UTF-8
-        * or less, if the last character is a multi-byte one
-        *
-        * @private
-        * @static
-        * @method trimToByteLength
-        * @param {string} s
-        * @param {number} length
-        * @return {string}
-        */
-       trimToByteLength = function ( s, length ) {
-               var byteLength, chopOffChars, chopOffBytes;
-
-               // bytelength is always greater or equal to the length in characters
-               s = s.substr( 0, length );
-               while ( ( byteLength = $.byteLength( s ) ) > length ) {
-                       // Calculate how many characters can be safely removed
-                       // First, we need to know how many bytes the string exceeds the threshold
-                       chopOffBytes = byteLength - length;
-                       // A character in UTF-8 is at most 4 bytes
-                       // One character must be removed in any case because the
-                       // string is too long
-                       chopOffChars = Math.max( 1, Math.floor( chopOffBytes / 4 ) );
-                       s = s.substr( 0, s.length - chopOffChars );
-               }
-               return s;
-       },
+                       return s;
+               },
 
-       /**
-        * Cuts a file name to a specific byte length
-        *
-        * @private
-        * @static
-        * @method trimFileNameToByteLength
-        * @param {string} name without extension
-        * @param {string} extension file extension
-        * @return {string} The full name, including extension
-        */
-       trimFileNameToByteLength = function ( name, extension ) {
-               // There is a special byte limit for file names and ... remember the dot
-               return trimToByteLength( name, FILENAME_MAX_BYTES - extension.length - 1 ) + '.' + extension;
-       },
-
-       // Polyfill for ES5 Object.create
-       createObject = Object.create || ( function () {
-               return function ( o ) {
-                       function Title() {}
-                       if ( o !== Object( o ) ) {
-                               throw new Error( 'Cannot inherit from a non-object' );
+               /**
+                * Cuts a string to a specific byte length, assuming UTF-8
+                * or less, if the last character is a multi-byte one
+                *
+                * @private
+                * @static
+                * @method trimToByteLength
+                * @param {string} s
+                * @param {number} length
+                * @return {string}
+                */
+               trimToByteLength = function ( s, length ) {
+                       var byteLength, chopOffChars, chopOffBytes;
+
+                       // bytelength is always greater or equal to the length in characters
+                       s = s.substr( 0, length );
+                       while ( ( byteLength = $.byteLength( s ) ) > length ) {
+                               // Calculate how many characters can be safely removed
+                               // First, we need to know how many bytes the string exceeds the threshold
+                               chopOffBytes = byteLength - length;
+                               // A character in UTF-8 is at most 4 bytes
+                               // One character must be removed in any case because the
+                               // string is too long
+                               chopOffChars = Math.max( 1, Math.floor( chopOffBytes / 4 ) );
+                               s = s.substr( 0, s.length - chopOffChars );
                        }
-                       Title.prototype = o;
-                       return new Title();
-               };
-       }() );
+                       return s;
+               },
+
+               /**
+                * Cuts a file name to a specific byte length
+                *
+                * @private
+                * @static
+                * @method trimFileNameToByteLength
+                * @param {string} name without extension
+                * @param {string} extension file extension
+                * @return {string} The full name, including extension
+                */
+               trimFileNameToByteLength = function ( name, extension ) {
+                       // There is a special byte limit for file names and ... remember the dot
+                       return trimToByteLength( name, FILENAME_MAX_BYTES - extension.length - 1 ) + '.' + extension;
+               },
+
+               // Polyfill for ES5 Object.create
+               createObject = Object.create || ( function () {
+                       return function ( o ) {
+                               function Title() {}
+                               if ( o !== Object( o ) ) {
+                                       throw new Error( 'Cannot inherit from a non-object' );
+                               }
+                               Title.prototype = o;
+                               return new Title();
+                       };
+               }() );
 
        /* Static members */
 
                        }
                }
 
-               if ( namespace === NS_MEDIA
-                       || ( options.forUploading && ( namespace === NS_FILE ) )
+               if (
+                       namespace === NS_MEDIA ||
+                       ( options.forUploading && ( namespace === NS_FILE ) )
                ) {
 
                        title = sanitize( title, [ 'generalRule', 'fileRule' ] );
                pages: {},
 
                set: function ( titles, state ) {
+                       var i, len,
+                               pages = this.pages;
+
                        titles = $.isArray( titles ) ? titles : [ titles ];
                        state = state === undefined ? true : !!state;
-                       var i,
-                               pages = this.pages,
-                               len = titles.length;
 
-                       for ( i = 0; i < len; i++ ) {
+                       for ( i = 0, len = titles.length; i < len; i++ ) {
                                pages[ titles[ i ] ] = state;
                        }
                        return true;
index 8bda601..3873e9b 100644 (file)
         * @return {mw.Upload.BookletLayout} An upload booklet
         */
        mw.Upload.Dialog.prototype.createUploadBooklet = function () {
+               // eslint-disable-next-line new-cap
                return new this.bookletClass( $.extend( {
                        $overlay: this.$overlay
                }, this.bookletConfig ) );
index 835b423..d0ed659 100644 (file)
  * @class mw.Uri
  */
 
+/* eslint-disable no-use-before-define */
+
 ( function ( mw, $ ) {
+       var parser, properties;
+
        /**
         * Function that's useful when constructing the URI string -- we frequently encounter the pattern
         * of having to add something to the URI as we go, but only if it's present, and to include a
         * @static
         * @property {Object} parser
         */
-       var parser = {
+       parser = {
                strict: mw.template.get( 'mediawiki.Uri', 'strict.regexp' ).render(),
                loose: mw.template.get( 'mediawiki.Uri', 'loose.regexp' ).render()
-       },
+       };
 
        /**
         * The order here matches the order of captured matches in the `parser` property regexes.
index 4d0c135..c005804 100644 (file)
@@ -1,4 +1,3 @@
-/* jshint devel: true */
 ( function ( mw, $ ) {
        /**
         * @method confirmCloseWindow
                        }
                };
        };
-} )( mediaWiki, jQuery );
+}( mediaWiki, jQuery ) );
index 26c74a1..cc5958d 100644 (file)
                        }
 
                        bitDiv( 'phpversion' )
-                               .append( $( this.data.phpEngine === 'HHVM'
-                                       ? '<a href="http://hhvm.com/">HHVM</a>'
-                                       '<a href="https://php.net/">PHP</a>'
+                               .append( $( this.data.phpEngine === 'HHVM' ?
+                                       '<a href="http://hhvm.com/">HHVM</a>' :
+                                       '<a href="https://php.net/">PHP</a>'
                                ) )
                                .append( ': ' + this.data.phpVersion );
 
                        $table = $( '<table id="mw-debug-querylist"></table>' );
 
                        $( '<tr>' )
-                               .append( $( '<th>#</th>' ).css( 'width', '4em' )    )
+                               .append( $( '<th>#</th>' ).css( 'width', '4em' ) )
                                .append( $( '<th>SQL</th>' ) )
-                               .append( $( '<th>Time</th>' ).css( 'width', '8em'  ) )
+                               .append( $( '<th>Time</th>' ).css( 'width', '8em' ) )
                                .append( $( '<th>Call</th>' ).css( 'width', '18em' ) )
                        .appendTo( $table );
 
index 49ff411..0c9ea97 100644 (file)
@@ -1,4 +1,3 @@
-/* jshint bitwise:false */
 ( function ( mw, $ ) {
 
        var CONTROL_BUCKET = 'control',
@@ -17,6 +16,7 @@
         * @see https://jsbin.com/kejewi/4/watch?js,console
         */
        function hashString( string ) {
+               /* eslint-disable no-bitwise */
                var hash = 0,
                        i = string.length;
 
@@ -30,6 +30,7 @@
                hash += ( hash << 15 );
 
                return hash >>> 0;
+               /* eslint-enable no-bitwise */
        }
 
        /**
index e0785b6..6abdf83 100644 (file)
@@ -6,7 +6,6 @@
  * @author Moriel Schottlender, 2015
  * @since 1.19
  */
-/*global OO*/
 ( function ( mw, $ ) {
        /**
         * This is a way of getting simple feedback from users. It's useful
                                this.feedbackSubjectInput.getValue()
                        );
 
-               this.actions.setAbilities( { submit:  isValid } );
+               this.actions.setAbilities( { submit: isValid } );
        };
 
        /**
                                if ( secondaryCode === 'http' ) {
                                        fb.status = 'error3';
                                        // ajax request failed
-                                       mw.log.warn( 'Feedback report failed with HTTP error: ' +  details.textStatus );
+                                       mw.log.warn( 'Feedback report failed with HTTP error: ' + details.textStatus );
                                } else {
                                        fb.status = 'error2';
-                                       mw.log.warn( 'Feedback report failed with API error: ' +  secondaryCode );
+                                       mw.log.warn( 'Feedback report failed with API error: ' + secondaryCode );
                                }
                        } else {
                                fb.status = 'error1';
index 882affe..72bf3d7 100644 (file)
@@ -4,7 +4,6 @@
  * @author Mark Holmquist, 2015
  * @since 1.25
  */
-/*global OO*/
 ( function ( mw, $, oo ) {
        var warningConfig = mw.config.get( 'wgFileWarning' ),
                warningMessages = warningConfig.messages,
index a74aef3..2494305 100644 (file)
@@ -4,7 +4,9 @@
  * @author Ori Livneh
  * @since 1.22
  */
-/*jshint devel:true */
+
+/* eslint-disable no-console */
+
 ( function ( mw, $ ) {
 
        var inspect,
        }
 
        function humanSize( bytes ) {
-               if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; }
-               var i = 0,
+               var i,
                        units = [ '', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB' ];
 
-               for ( ; bytes >= 1024; bytes /= 1024 ) { i++; }
+               if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; }
+
+               for ( i = 0; bytes >= 1024; bytes /= 1024 ) { i++; }
                // Maintain one decimal for kB and above, but don't
                // add ".0" for bytes.
                return bytes.toFixed( i > 0 ? 1 : 0 ) + units[ i ];
                                                allSelectors: stats.total,
                                                matchedSelectors: stats.matched,
                                                percentMatched: stats.total !== 0 ?
-                                                       ( stats.matched / stats.total * 100 ).toFixed( 2 )  + '%' : null
+                                                       ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null
                                        } );
                                } );
                                sortByProperty( modules, 'allSelectors', true );
                                        $.extend( stats, mw.loader.store.stats );
                                        try {
                                                raw = localStorage.getItem( mw.loader.store.getStoreKey() );
-                                               stats.totalSizeInBytes =  $.byteLength( raw );
+                                               stats.totalSizeInBytes = $.byteLength( raw );
                                                stats.totalSize = humanSize( $.byteLength( raw ) );
                                        } catch ( e ) {}
                                }
 
                                // Grep module's CSS
                                if (
-                                       $.isPlainObject( module.style ) && $.isArray( module.style.css )
-                                       && pattern.test( module.style.css.join( '' ) )
+                                       $.isPlainObject( module.style ) && $.isArray( module.style.css ) &&
+                                       pattern.test( module.style.css.join( '' ) )
                                ) {
                                        // Module's CSS source matches
                                        return true;
index cad59b8..8504964 100644 (file)
        function getFailableParserFn( options ) {
                return function ( args ) {
                        var fallback,
+                               // eslint-disable-next-line new-cap
                                parser = new mw.jqueryMsg.parser( options ),
                                key = args[ 0 ],
                                argsArray = $.isArray( args[ 1 ] ) ? args[ 1 ] : slice.call( args, 1 );
                }
 
                return function () {
+                       var failableResult;
                        if ( !failableParserFn ) {
                                failableParserFn = getFailableParserFn( options );
                        }
-                       var failableResult = failableParserFn( arguments );
+                       failableResult = failableParserFn( arguments );
                        if ( format === 'text' || format === 'escaped' ) {
                                return failableResult.text();
                        } else {
                var failableParserFn;
 
                return function () {
+                       var $target;
                        if ( !failableParserFn ) {
                                failableParserFn = getFailableParserFn( options );
                        }
-                       var $target = this.empty();
+                       $target = this.empty();
                        appendWithoutParsing( $target, failableParserFn( arguments ) );
                        return $target;
                };
                this.settings.onlyCurlyBraceTransform = ( this.settings.format === 'text' || this.settings.format === 'escaped' );
                this.astCache = {};
 
+               // eslint-disable-next-line new-cap
                this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic );
        };
 
         * htmlEmitter - object which primarily exists to emit HTML from parser ASTs
         */
        mw.jqueryMsg.htmlEmitter = function ( language, magic ) {
-               this.language = language;
                var jmsg = this;
+               this.language = language;
                $.each( magic, function ( key, val ) {
                        jmsg[ key.toLowerCase() ] = function () {
                                return val;
                 * @return {number|string} Formatted number
                 */
                formatnum: function ( nodes ) {
-                       var isInteger = ( nodes[ 1 ] && nodes[ 1 ] === 'R' ) ? true : false,
+                       var isInteger = !!nodes[ 1 ] && nodes[ 1 ] === 'R',
                                number = nodes[ 0 ];
 
                        return this.language.convertNumber( number, isInteger );
                return function () {
                        return reusableParent.msg( this.key, this.parameters ).contents().detach();
                };
-       } )();
+       }() );
 
 }( mediaWiki, jQuery ) );
index d525813..4dce192 100644 (file)
@@ -7,7 +7,9 @@
  * @alternateClassName mediaWiki
  * @singleton
  */
-/*jshint latedef:false */
+
+/* eslint-disable no-use-before-define */
+
 ( function ( $ ) {
        'use strict';
 
@@ -31,7 +33,7 @@
         * @return {string} hash as an seven-character base 36 string
         */
        function fnv132( str ) {
-               /*jshint bitwise:false */
+               /* eslint-disable no-bitwise */
                var hash = 0x811C9DC5,
                        i;
 
@@ -46,6 +48,7 @@
                }
 
                return hash;
+               /* eslint-enable no-bitwise */
        }
 
        StringSet = window.Set || ( function () {
                log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
                        obj[ key ] = val;
                } : function ( obj, key, val, msg, logName ) {
+                       var logged = new StringSet();
                        logName = logName || key;
                        msg = 'Use of "' + logName + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' );
-                       var logged = new StringSet();
                        function uniqueTrace() {
                                var trace = new Error().stack;
                                if ( logged.has( trace ) ) {
                                }
 
                                if ( registry[ module ].skip !== null ) {
-                                       /*jshint evil:true */
+                                       // eslint-disable-next-line no-new-func
                                        skip = new Function( registry[ module ].skip );
                                        registry[ module ].skip = null;
                                        if ( skip() ) {
                                                        ) );
                                                }
 
-                                               unresolved.add(  module );
+                                               unresolved.add( module );
                                                sortDependencies( deps[ i ], resolved, unresolved );
                                        }
                                }
                                                } );
                                        };
 
-                                       implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 )
-                                               ? []
-                                               legacyModules;
+                                       implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 ) ?
+                                               [] :
+                                               legacyModules;
 
                                        if ( module === 'user' ) {
                                                // Implicit dependency on the site module. Not real dependency because
                                                implicitDependencies.push( 'site' );
                                        }
 
-                                       legacyWait = implicitDependencies.length
-                                               ? mw.loader.using( implicitDependencies )
-                                               $.Deferred().resolve();
+                                       legacyWait = implicitDependencies.length ?
+                                               mw.loader.using( implicitDependencies ) :
+                                               $.Deferred().resolve();
 
                                        legacyWait.always( function () {
                                                try {
                                                        prefix = modules[ i ].substr( 0, lastDotIndex );
                                                        suffix = modules[ i ].slice( lastDotIndex + 1 );
 
-                                                       bytesAdded = hasOwn.call( moduleMap, prefix )
-                                                               ? suffix.length + 3 // '%2C'.length == 3
-                                                               modules[ i ].length + 3; // '%7C'.length == 3
+                                                       bytesAdded = hasOwn.call( moduleMap, prefix ) ?
+                                                               suffix.length + 3 : // '%2C'.length == 3
+                                                               modules[ i ].length + 3; // '%7C'.length == 3
 
                                                        // If the url would become too long, create a new one,
                                                        // but don't create empty requests
                                                        return true;
                                                } );
                                                asyncEval( implementations, function ( err ) {
+                                                       var failed;
                                                        // Not good, the cached mw.loader.implement calls failed! This should
                                                        // never happen, barring ResourceLoader bugs, browser bugs and PEBKACs.
                                                        // Depending on how corrupt the string is, it is likely that some
 
                                                        mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } );
                                                        // Re-add the failed ones that are still pending back to the batch
-                                                       var failed = $.grep( sourceModules, function ( module ) {
+                                                       failed = $.grep( sourceModules, function ( module ) {
                                                                return registry[ module ].state === 'loading';
                                                        } );
                                                        batchRequest( failed );
                                                        // Partial descriptor
                                                        // (e.g. skipped module, or style module with state=ready)
                                                        $.inArray( undefined, [ descriptor.script, descriptor.style,
-                                                                       descriptor.messages, descriptor.templates ] ) !== -1
+                                                               descriptor.messages, descriptor.templates ] ) !== -1
                                                ) {
                                                        // Decline to store
                                                        return false;
index c886817..4d23604 100644 (file)
 
                        if ( !$log.length ) {
                                $log = $( '<div id="mw-log-console"></div>' ).css( {
-                                               overflow: 'auto',
-                                               height: '150px',
-                                               backgroundColor: 'white',
-                                               borderTop: 'solid 2px #ADADAD'
-                                       } );
+                                       overflow: 'auto',
+                                       height: '150px',
+                                       backgroundColor: 'white',
+                                       borderTop: 'solid 2px #ADADAD'
+                               } );
                                hovzer = $.getFootHovzer();
                                hovzer.$.append( $log );
                                hovzer.update();
index 36b45f1..6c6f560 100644 (file)
@@ -25,7 +25,7 @@
         * @private
         */
        function Notification( message, options ) {
-               var $notification, $notificationTitle, $notificationContent;
+               var $notification, $notificationContent;
 
                $notification = $( '<div class="mw-notification"></div>' )
                        .data( 'mw.notification', this )
@@ -48,7 +48,7 @@
                }
 
                if ( options.title ) {
-                       $notificationTitle = $( '<div class="mw-notification-title"></div>' )
+                       $( '<div class="mw-notification-title"></div>' )
                                .text( options.title )
                                .appendTo( $notification );
                }
                                        $area.hide();
                                        notif.$notification.remove();
                                } else {
-                                       notif.$notification.slideUp( 'fast',  function () {
+                                       notif.$notification.slideUp( 'fast', function () {
                                                $( this ).remove();
                                        } );
                                }
index 7f62256..c5e96eb 100644 (file)
@@ -1,4 +1,4 @@
-/*global Mustache */
+/* global Mustache */
 ( function ( mw, $ ) {
        // Register mustache compiler
        mw.template.registerCompiler( 'mustache', {
index 63e7de8..240d1bd 100644 (file)
@@ -2,6 +2,7 @@
  * @class mw.user
  * @singleton
  */
+/* global Uint8Array */
 ( function ( mw, $ ) {
        var i,
                userInfoPromise,
@@ -50,7 +51,7 @@
                 * @return {string} 64 bit integer in hex format, padded
                 */
                generateRandomSessionId: function () {
-                       /*jshint bitwise:false */
+                       /* eslint-disable no-bitwise */
                        var rnds, i, r,
                                hexRnds = new Array( 8 ),
                                // Support: IE 11
@@ -79,6 +80,7 @@
                        // Concatenation of two random integers with entropy n and m
                        // returns a string with entropy n+m if those strings are independent
                        return hexRnds.join( '' );
+                       /* eslint-enable no-bitwise */
                },
 
                /**
                 *  unavailable, or Date for when the user registered.
                 */
                getRegistration: function () {
+                       var registration;
                        if ( mw.user.isAnon() ) {
                                return false;
                        }
-                       var registration = mw.config.get( 'wgUserRegistration' );
+                       registration = mw.config.get( 'wgUserRegistration' );
                        // Registration may be unavailable if the user signed up before MediaWiki
                        // began tracking this.
                        return !registration ? null : new Date( registration );
index 654f232..bee22c7 100644 (file)
                                query = $.param( params );
                        }
                        if ( query ) {
-                               url = title
-                                       ? util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query
-                                       util.wikiScript() + '?' + query;
+                               url = title ?
+                                       util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query :
+                                       util.wikiScript() + '?' + query;
                        } else {
                                url = mw.config.get( 'wgArticlePath' )
                                        .replace( '$1', util.wikiUrlencode( title ).replace( /\$/g, '$$$$' ) );
                 * @return {Mixed} Parameter value or null.
                 */
                getParamValue: function ( param, url ) {
-                       if ( url === undefined ) {
-                               url = location.href;
-                       }
                        // Get last match, stop at hash
                        var     re = new RegExp( '^[^#]*[&?]' + mw.RegExp.escape( param ) + '=([^&#]*)' ),
-                               m = re.exec( url );
+                               m = re.exec( url !== undefined ? url : location.href );
+
                        if ( m ) {
                                // Beware that decodeURIComponent is not required to understand '+'
                                // by spec, as encodeURIComponent does not produce it.
 
                        html5EmailRegexp = new RegExp(
                                // start of string
-                               '^'
-                               +
+                               '^' +
                                // User part which is liberal :p
-                               '[' + rfc5322Atext + '\\.]+'
-                               +
+                               '[' + rfc5322Atext + '\\.]+' +
                                // 'at'
-                               '@'
-                               +
+                               '@' +
                                // Domain first part
-                               '[' + rfc1034LdhStr + ']+'
-                               +
+                               '[' + rfc1034LdhStr + ']+' +
                                // Optional second part and following are separated by a dot
-                               '(?:\\.[' + rfc1034LdhStr + ']+)*'
-                               +
+                               '(?:\\.[' + rfc1034LdhStr + ']+)*' +
                                // End of string
                                '$',
                                // RegExp is case insensitive
                 * @return {boolean}
                 */
                isIPv4Address: function ( address, allowBlock ) {
+                       var block, RE_IP_BYTE, RE_IP_ADD;
+
                        if ( typeof address !== 'string' ) {
                                return false;
                        }
 
-                       var     block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '',
-                               RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])',
-                               RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
+                       block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '';
+                       RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])';
+                       RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
 
                        return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
                },
                 * @return {boolean}
                 */
                isIPv6Address: function ( address, allowBlock ) {
+                       var block, RE_IPV6_ADD;
+
                        if ( typeof address !== 'string' ) {
                                return false;
                        }
 
-                       var     block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '',
-                               RE_IPV6_ADD =
-                       '(?:' + // starts with "::" (including "::")
-                       ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' +
-                       '|' + // ends with "::" (except "::")
-                       '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' +
-                       '|' + // contains no "::"
-                       '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' +
-                       ')';
+                       block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '';
+                       RE_IPV6_ADD =
+                               '(?:' + // starts with "::" (including "::")
+                               ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' +
+                               '|' + // ends with "::" (except "::")
+                               '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' +
+                               '|' + // contains no "::"
+                               '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' +
+                               ')';
 
                        if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) {
                                return true;
                        RE_IPV6_ADD = '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}';
 
                        return (
-                               new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address )
-                               && /::/.test( address )
-                               && !/::.*::/.test( address )
+                               new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) &&
+                               /::/.test( address ) &&
+                               !/::.*::/.test( address )
                        );
                },
 
index 6396331..cb1e73f 100644 (file)
@@ -85,7 +85,7 @@
                 */
                isElementCloseToViewport: function ( el, threshold, rectangle ) {
                        var viewport = rectangle ? $.extend( {}, rectangle ) : this.makeViewportFromWindow();
-                       threshold = threshold || 50 ;
+                       threshold = threshold || 50;
 
                        viewport.top -= threshold;
                        viewport.left -= threshold;
index 26f568b..be75a2e 100644 (file)
                if ( this.imageInfoCache[ imageSrc ] === undefined ) {
                        api = new mw.Api();
                        // TODO: This supports only gallery of images
-                       title = new mw.Title.newFromImg( $img );
+                       title = mw.Title.newFromImg( $img );
                        params = {
                                action: 'query',
                                formatversion: 2,
        // Bootstrap all slideshow galleries
        mw.hook( 'wikipage.content' ).add( function ( $content ) {
                $content.find( '.mw-gallery-slideshow' ).each( function () {
-                       /*jshint -W031 */
+                       // eslint-disable-next-line no-new
                        new mw.GallerySlideshow( this );
-                       /*jshint +W031 */
                } );
        } );
 }( mediaWiki, jQuery, OO ) );
index 02bc1de..6038a57 100644 (file)
@@ -1,8 +1,10 @@
 /*!
  * Implement AJAX navigation for multi-page images so the user may browse without a full page reload.
  */
+
+/* eslint-disable no-use-before-define */
+
 ( function ( mw, $ ) {
-       /*jshint latedef:false */
        var jqXhr, $multipageimage, $spinner,
                cache = {},
                cacheOrder = [];
index 89bbbe7..6d6d46d 100644 (file)
                                rcid: rcid
                        } )
                        .done( function ( data ) {
+                               var title;
                                // Remove all patrollinks from the page (including any spinners inside).
                                $patrolLinks.closest( '.patrollink' ).remove();
                                if ( data.patrol !== undefined ) {
                                        // Success
-                                       var title = new mw.Title( data.patrol.title );
+                                       title = new mw.Title( data.patrol.title );
                                        mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) );
                                } else {
                                        // This should never happen as errors should trigger fail
index 83d14b3..cb46b11 100644 (file)
@@ -43,9 +43,9 @@
                                        }
                                        $( e.delegateTarget ).remove();
                                }, function ( errorCode, data ) {
-                                       var message = data && data.error && data.error.messageHtml
-                                               ? $.parseHTML( data.error.messageHtml )
-                                               mw.msg( 'rollbackfailed' ),
+                                       var message = data && data.error && data.error.messageHtml ?
+                                               $.parseHTML( data.error.messageHtml ) :
+                                               mw.msg( 'rollbackfailed' ),
                                                type = errorCode === 'alreadyrolled' ? 'warn' : 'error';
 
                                        mw.notify( message, {
index 282799a..076357a 100644 (file)
@@ -3,6 +3,7 @@
        mw.page = {};
 
        $( function () {
+               var $diff;
                mw.util.init();
 
                /**
@@ -23,7 +24,7 @@
                 */
                mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) );
 
-               var $diff = $( 'table.diff[data-mw="interface"]' );
+               $diff = $( 'table.diff[data-mw="interface"]' );
                if ( $diff.length ) {
                        /**
                         * Fired when the diff is added to a page containing a diff
index c67b93e..0abb957 100644 (file)
@@ -1,7 +1,7 @@
 // Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
 // This affects English only (and languages without localisations, that fall back to English).
 // http://momentjs.com/docs/#/customization/long-date-formats/
-/*global moment */
+/* global moment */
 moment.locale( 'en', {
        longDateFormat: {
                // Unchanged, but have to be repeated here:
index dd33b00..13f26f0 100644 (file)
@@ -1,4 +1,4 @@
-/*global moment, mw */
+/* global moment, mw */
 
 // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
 // wgTranslateNumerals is respected.
index 5f52d1e..c8acc86 100644 (file)
@@ -4,16 +4,16 @@
  */
 
 window.Node = window.Node || {
-       ELEMENT_NODE:                1,
-       ATTRIBUTE_NODE:              2,
-       TEXT_NODE:                   3,
-       CDATA_SECTION_NODE:          4,
-       ENTITY_REFERENCE_NODE:       5,
-       ENTITY_NODE:                 6,
+       ELEMENT_NODE: 1,
+       ATTRIBUTE_NODE: 2,
+       TEXT_NODE: 3,
+       CDATA_SECTION_NODE: 4,
+       ENTITY_REFERENCE_NODE: 5,
+       ENTITY_NODE: 6,
        PROCESSING_INSTRUCTION_NODE: 7,
-       COMMENT_NODE:                8,
-       DOCUMENT_NODE:               9,
-       DOCUMENT_TYPE_NODE:          10,
-       DOCUMENT_FRAGMENT_NODE:      11,
-       NOTATION_NODE:               12
+       COMMENT_NODE: 8,
+       DOCUMENT_NODE: 9,
+       DOCUMENT_TYPE_NODE: 10,
+       DOCUMENT_FRAGMENT_NODE: 11,
+       NOTATION_NODE: 12
 };
index 5e05590..e3094d2 100644 (file)
@@ -3,11 +3,11 @@
  *
  * This file is where we decide whether to initialise the modern run-time.
  */
-/*jshint unused: false */
-/*globals mw, RLQ: true, NORLQ: true, $VARS, $CODE, performance */
 
-var mediaWikiLoadStart = ( new Date() ).getTime(),
+/* global mw, $VARS, $CODE */
 
+// eslint-disable-next-line no-unused-vars
+var mediaWikiLoadStart = ( new Date() ).getTime(),
        mwPerformance = ( window.performance && performance.mark ) ? performance : {
                mark: function () {}
        };
@@ -51,19 +51,19 @@ function isCompatible( str ) {
        var ua = str || navigator.userAgent;
        return !!(
                // http://caniuse.com/#feat=queryselector
-               'querySelector' in document
+               'querySelector' in document &&
 
                // http://caniuse.com/#feat=namevalue-storage
                // https://developer.blackberry.com/html5/apis/v1_0/localstorage.html
                // https://blog.whatwg.org/this-week-in-html-5-episode-30
-               && 'localStorage' in window
+               'localStorage' in window &&
 
                // http://caniuse.com/#feat=addeventlistener
-               && 'addEventListener' in window
+               'addEventListener' in window &&
 
                // Hardcoded exceptions for browsers that pass the requirement but we don't want to
                // support in the modern run-time.
-               && !(
+               !(
                        ua.match( /webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass/ ) ||
                        ua.match( /PlayStation/i ) ||
                        // UC Mini (speed mode on)
@@ -112,6 +112,7 @@ function isCompatible( str ) {
 
                // Must be after mw.config.set because these callbacks may use mw.loader which
                // needs to have values 'skin', 'debug' etc. from mw.config.
+               // eslint-disable-next-line vars-on-top
                var RLQ = window.RLQ || [];
                while ( RLQ.length ) {
                        RLQ.shift()();