--- /dev/null
+{
+ "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
+ }
+}
+++ /dev/null
-{
- "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/**"
- ]
-}
+++ /dev/null
-# 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/**
+++ /dev/null
-{
- // 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
- }
-}
-/*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}/**'
]
},
watch: {
files: [
- '.{stylelintrc,jscsrc,jshintignore,jshintrc}',
+ '.{stylelintrc,eslintrc.json}',
'**/*',
'!{docs,extensions,node_modules,skins,vendor}/**'
],
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' ] );
"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",
*/
( 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 ) );
*/
( 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 ) );
*/
( 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.
( function ( $ ) {
function getColor( elem, attr ) {
- /*jshint boss:true */
var color;
do {
}
attr = 'backgroundColor';
+ // eslint-disable-next-line no-cond-assign
} while ( elem = elem.parentNode );
return $.colorUtil.getRGB( color );
* @return {Array}
*/
getRGB: function ( color ) {
- /*jshint boss:true */
var result;
// Check if we're already dealing with an array of colors
}
// 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 ),
}
// 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,
}
// 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 ),
}
// 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 ),
}
// 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
*/
( 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 ) );
// 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 ) {
*/
( 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: --><b><i>Hello</i> world!</b>
- * <!-- 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: --><b><i>Hello</i> world!</b>
+ * <!-- 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 ) );
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 ) );
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;
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' ) );
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 );
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;
* @return {jQuery}
*/
createSpinner: function ( opts ) {
+ var $spinner;
+
if ( opts !== undefined && $.type( opts ) !== 'object' ) {
opts = {
id: opts
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 );
}
$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 );
}
}
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 ) {
* 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;
}
}
/*!
* 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();
}
( 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();
}
// 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.
*/
mediaWiki.language.convertGrammar = function ( word, form ) {
- /*jshint onecase:true */
var grammarForms = mediaWiki.language.getData( 'ga', 'grammarForms' );
if ( grammarForms && grammarForms[ form ] ) {
return grammarForms[ form ][ word ];
}
// 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;
}
}
*/
mediaWiki.language.convertGrammar = function ( word, form ) {
- /*jshint onecase:true */
var grammarForms = mediaWiki.language.getData( 'hy', 'grammarForms' );
if ( grammarForms && grammarForms[ form ] ) {
return grammarForms[ form ][ word ];
*/
( 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 ) );
* @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 );
( 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 ) );
* @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;
-/*global OO*/
( function ( mw ) {
/**
* This is the abstract base class for MessagePoster implementations.
-/*global OO*/
( function ( mw, $ ) {
/**
* This is an implementation of MessagePoster for wikitext talk pages.
-/*global OO */
( function ( mw, $ ) {
/**
* Factory for MessagePoster objects. This provides a pluggable to way to script the action
-/*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;
}
} );
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 */
} );
}
* @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;
* - '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;
};
* @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 ) {
prop: 'categories',
cllimit: this.limit,
titles: 'Category:' + input
- } ).done( function ( res ) {
+ } ).done( function ( res ) {
var categories = [];
$.each( res.query.pages, function ( index, page ) {
* @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 ) {
/**
* 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;
}
( 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,
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: {
} )
// 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 );
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 ) {
* @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
* @return {string}
*/
function getFirstKey( obj ) {
- for ( var key in obj ) {
+ var key;
+ for ( key in obj ) {
if ( obj.hasOwnProperty( key ) ) {
return key;
}
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 = [];
} );
$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 );
-/*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' ) {
* @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;
-/*global moment*/
+/* global moment*/
( function ( $, mw, moment ) {
/**
* @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 ) );
* @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.
} );
}( mediaWiki, jQuery ) );
-
-/* jshint devel: true */
( function ( mw, $ ) {
/**
* @method confirmCloseWindow
}
};
};
-} )( mediaWiki, jQuery );
+}( mediaWiki, jQuery ) );
}
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 );
-/* jshint bitwise:false */
( function ( mw, $ ) {
var CONTROL_BUCKET = 'control',
* @see https://jsbin.com/kejewi/4/watch?js,console
*/
function hashString( string ) {
+ /* eslint-disable no-bitwise */
var hash = 0,
i = string.length;
hash += ( hash << 15 );
return hash >>> 0;
+ /* eslint-enable no-bitwise */
}
/**
* @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';
* @author Mark Holmquist, 2015
* @since 1.25
*/
-/*global OO*/
( function ( mw, $, oo ) {
var warningConfig = mw.config.get( 'wgFileWarning' ),
warningMessages = warningConfig.messages,
* @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;
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 ) );
* @alternateClassName mediaWiki
* @singleton
*/
-/*jshint latedef:false */
+
+/* eslint-disable no-use-before-define */
+
( function ( $ ) {
'use strict';
* @return {string} hash as an seven-character base 36 string
*/
function fnv132( str ) {
- /*jshint bitwise:false */
+ /* eslint-disable no-bitwise */
var hash = 0x811C9DC5,
i;
}
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;
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();
* @private
*/
function Notification( message, options ) {
- var $notification, $notificationTitle, $notificationContent;
+ var $notification, $notificationContent;
$notification = $( '<div class="mw-notification"></div>' )
.data( 'mw.notification', this )
}
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();
} );
}
-/*global Mustache */
+/* global Mustache */
( function ( mw, $ ) {
// Register mustache compiler
mw.template.registerCompiler( 'mustache', {
* @class mw.user
* @singleton
*/
+/* global Uint8Array */
( function ( mw, $ ) {
var i,
userInfoPromise,
* @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
// 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 );
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 )
);
},
*/
isElementCloseToViewport: function ( el, threshold, rectangle ) {
var viewport = rectangle ? $.extend( {}, rectangle ) : this.makeViewportFromWindow();
- threshold = threshold || 50 ;
+ threshold = threshold || 50;
viewport.top -= threshold;
viewport.left -= threshold;
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 ) );
/*!
* 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 = [];
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
}
$( 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, {
mw.page = {};
$( function () {
+ var $diff;
mw.util.init();
/**
*/
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
// 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:
-/*global moment, mw */
+/* global moment, mw */
// HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
// wgTranslateNumerals is respected.
*/
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
};
*
* 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 () {}
};
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)
// 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()();