diff options
Diffstat (limited to 'resources/src/jquery')
19 files changed, 679 insertions, 525 deletions
diff --git a/resources/src/jquery/jquery.accessKeyLabel.js b/resources/src/jquery/jquery.accessKeyLabel.js index 867c25e7..92f8eb9c 100644 --- a/resources/src/jquery/jquery.accessKeyLabel.js +++ b/resources/src/jquery/jquery.accessKeyLabel.js @@ -112,7 +112,7 @@ function getAccessKeyLabel( element ) { */ function updateTooltipOnElement( element, titleElement ) { var array = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ), - regexp = new RegExp( $.map( array, $.escapeRE ).join( '.*?' ) + '$' ), + regexp = new RegExp( $.map( array, mw.RegExp.escape ).join( '.*?' ) + '$' ), oldTitle = titleElement.title, rawTitle = oldTitle.replace( regexp, '' ), newTitle = rawTitle, @@ -150,14 +150,14 @@ function updateTooltip( element ) { if ( id ) { $label = $( 'label[for="' + id + '"]' ); if ( $label.length === 1 ) { - updateTooltipOnElement( element, $label[0] ); + 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] ); + updateTooltipOnElement( element, $labelParent[ 0 ] ); } } } diff --git a/resources/src/jquery/jquery.autoEllipsis.js b/resources/src/jquery/jquery.autoEllipsis.js index 9a196b5d..e1115d65 100644 --- a/resources/src/jquery/jquery.autoEllipsis.js +++ b/resources/src/jquery/jquery.autoEllipsis.js @@ -69,16 +69,16 @@ $.fn.autoEllipsis = function ( options ) { // Try cache if ( options.matchText ) { if ( !( text in matchTextCache ) ) { - matchTextCache[text] = {}; + matchTextCache[ text ] = {}; } - if ( !( options.matchText in matchTextCache[text] ) ) { - matchTextCache[text][options.matchText] = {}; + if ( !( options.matchText in matchTextCache[ text ] ) ) { + matchTextCache[ text ][ options.matchText ] = {}; } - if ( !( w in matchTextCache[text][options.matchText] ) ) { - matchTextCache[text][options.matchText][w] = {}; + 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.position in matchTextCache[ text ][ options.matchText ][ w ] ) { + $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] ); if ( options.tooltip ) { $container.attr( 'title', text ); } @@ -86,13 +86,13 @@ $.fn.autoEllipsis = function ( options ) { } } else { if ( !( text in cache ) ) { - cache[text] = {}; + cache[ text ] = {}; } - if ( !( w in cache[text] ) ) { - cache[text][w] = {}; + if ( !( w in cache[ text ] ) ) { + cache[ text ][ w ] = {}; } - if ( options.position in cache[text][w] ) { - $container.html( cache[text][w][options.position] ); + if ( options.position in cache[ text ][ w ] ) { + $container.html( cache[ text ][ w ][ options.position ] ); if ( options.tooltip ) { $container.attr( 'title', text ); } @@ -120,19 +120,19 @@ $.fn.autoEllipsis = function ( options ) { break; case 'center': // TODO: Use binary search like for 'right' - i = [Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 )]; + 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] ) ); + 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]--; + i[ 0 ]--; side = 1; } else { // Make the end shorter - i[1]++; + i[ 1 ]++; side = 0; } } @@ -152,9 +152,9 @@ $.fn.autoEllipsis = function ( options ) { } if ( options.matchText ) { $container.highlightText( options.matchText ); - matchTextCache[text][options.matchText][w][options.position] = $container.html(); + matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html(); } else { - cache[text][w][options.position] = $container.html(); + cache[ text ][ w ][ options.position ] = $container.html(); } } ); diff --git a/resources/src/jquery/jquery.byteLimit.js b/resources/src/jquery/jquery.byteLimit.js index 5551232a..dd71a2bc 100644 --- a/resources/src/jquery/jquery.byteLimit.js +++ b/resources/src/jquery/jquery.byteLimit.js @@ -10,17 +10,17 @@ * "fobo", not "foba". Basically emulating the native maxlength by * reconstructing where the insertion occurred. * - * @private + * @static * @param {string} safeVal Known value that was previously returned by this * function, if none, pass empty string. * @param {string} newVal New value that may have to be trimmed down. * @param {number} byteLimit Number of bytes the value may be in size. - * @param {Function} [fn] See jQuery.byteLimit. + * @param {Function} [fn] See jQuery#byteLimit. * @return {Object} * @return {string} return.newVal * @return {boolean} return.trimmed */ - function trimValForByteLength( safeVal, newVal, byteLimit, fn ) { + $.trimByteLength = function ( safeVal, newVal, byteLimit, fn ) { var startMatches, endMatches, matchesLen, inpParts, oldVal = safeVal; @@ -77,22 +77,22 @@ // until the limit is statisfied. if ( fn ) { // stop, when there is nothing to slice - bug 41450 - while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[1].length > 0 ) { - inpParts[1] = inpParts[1].slice( 0, -1 ); + while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[ 1 ].length > 0 ) { + inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -1 ); } } else { while ( $.byteLength( inpParts.join( '' ) ) > byteLimit ) { - inpParts[1] = inpParts[1].slice( 0, -1 ); + inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -1 ); } } - newVal = inpParts.join( '' ); - return { - newVal: newVal, - trimmed: true + newVal: inpParts.join( '' ), + // For pathological fn() that always returns a value longer than the limit, we might have + // ended up not trimming - check for this case to avoid infinite loops + trimmed: newVal !== inpParts.join( '' ) }; - } + }; var eventKeys = [ 'keyup.byteLimit', @@ -206,7 +206,7 @@ // See http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for // the order and characteristics of the key events. $el.on( eventKeys, function () { - var res = trimValForByteLength( + var res = $.trimByteLength( prevSafeVal, this.value, elLimit, @@ -219,9 +219,12 @@ // This is a side-effect of limiting after the fact. if ( res.trimmed === true ) { this.value = res.newVal; + // Trigger a 'change' event to let other scripts attached to this node know that the value + // was changed. This will also call ourselves again, but that's okay, it'll be a no-op. + $el.trigger( 'change' ); } // Always adjust prevSafeVal to reflect the input value. Not doing this could cause - // trimValForByteLength to compare the new value to an empty string instead of the + // trimByteLength to compare the new value to an empty string instead of the // old value, resulting in trimming always from the end (bug 40850). prevSafeVal = res.newVal; } ); diff --git a/resources/src/jquery/jquery.color.js b/resources/src/jquery/jquery.color.js index 04f8047b..a3cc8fc3 100644 --- a/resources/src/jquery/jquery.color.js +++ b/resources/src/jquery/jquery.color.js @@ -28,7 +28,7 @@ } // We override the animation for all of these color styles - $.each([ + $.each( [ 'backgroundColor', 'borderBottomColor', 'borderLeftColor', @@ -37,17 +37,17 @@ 'color', 'outlineColor' ], function ( i, attr ) { - $.fx.step[attr] = function ( fx ) { + $.fx.step[ attr ] = function ( fx ) { if ( !fx.colorInit ) { fx.start = getColor( fx.elem, attr ); fx.end = $.colorUtil.getRGB( fx.end ); fx.colorInit = true; } - fx.elem.style[attr] = 'rgb(' + [ - Math.max( Math.min( parseInt( (fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10 ), 255 ), 0 ), - Math.max( Math.min( parseInt( (fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10 ), 255 ), 0 ), - Math.max( Math.min( parseInt( (fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10 ), 255 ), 0 ) + fx.elem.style[ attr ] = 'rgb(' + [ + Math.max( Math.min( parseInt( ( fx.pos * ( fx.end[ 0 ] - fx.start[ 0 ] ) ) + fx.start[ 0 ], 10 ), 255 ), 0 ), + Math.max( Math.min( parseInt( ( fx.pos * ( fx.end[ 1 ] - fx.start[ 1 ] ) ) + fx.start[ 1 ], 10 ), 255 ), 0 ), + Math.max( Math.min( parseInt( ( fx.pos * ( fx.end[ 2 ] - fx.start[ 2 ] ) ) + fx.start[ 2 ], 10 ), 255 ), 0 ) ].join( ',' ) + ')'; }; } ); diff --git a/resources/src/jquery/jquery.colorUtil.js b/resources/src/jquery/jquery.colorUtil.js index a6ff8bc8..c14f2c86 100644 --- a/resources/src/jquery/jquery.colorUtil.js +++ b/resources/src/jquery/jquery.colorUtil.js @@ -32,48 +32,48 @@ } // Look for rgb(num,num,num) - if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) { + 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 ), - parseInt( result[2], 10 ), - parseInt( result[3], 10 ) + parseInt( result[ 1 ], 10 ), + parseInt( result[ 2 ], 10 ), + parseInt( result[ 3 ], 10 ) ]; } // Look for rgb(num%,num%,num%) - if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) { + 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, - parseFloat( result[2] ) * 2.55, - parseFloat( result[3] ) * 2.55 + parseFloat( result[ 1 ] ) * 2.55, + parseFloat( result[ 2 ] ) * 2.55, + parseFloat( result[ 3 ] ) * 2.55 ]; } // Look for #a0b1c2 - if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) { + if ( result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec( color ) ) { return [ - parseInt( result[1], 16 ), - parseInt( result[2], 16 ), - parseInt( result[3], 16 ) + parseInt( result[ 1 ], 16 ), + parseInt( result[ 2 ], 16 ), + parseInt( result[ 3 ], 16 ) ]; } // Look for #fff - if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) { + if ( result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec( color ) ) { return [ - parseInt( result[1] + result[1], 16 ), - parseInt( result[2] + result[2], 16 ), - parseInt( result[3] + result[3], 16) + parseInt( result[ 1 ] + result[ 1 ], 16 ), + parseInt( result[ 2 ] + result[ 2 ], 16 ), + parseInt( result[ 3 ] + result[ 3 ], 16 ) ]; } // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 - if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) { + if ( result = /rgba\(0, 0, 0, 0\)/.exec( color ) ) { return $.colorUtil.colors.transparent; } // Otherwise, we're most likely dealing with a named color - return $.colorUtil.colors[$.trim(color).toLowerCase()]; + return $.colorUtil.colors[ $.trim( color ).toLowerCase() ]; }, /** @@ -85,50 +85,50 @@ * @property {Object} */ colors: { - aqua: [0, 255, 255], - azure: [240, 255, 255], - beige: [245, 245, 220], - black: [0, 0, 0], - blue: [0, 0, 255], - brown: [165, 42, 42], - cyan: [0, 255, 255], - darkblue: [0, 0, 139], - darkcyan: [0, 139, 139], - darkgrey: [169, 169, 169], - darkgreen: [0, 100, 0], - darkkhaki: [189, 183, 107], - darkmagenta: [139, 0, 139], - darkolivegreen: [85, 107, 47], - darkorange: [255, 140, 0], - darkorchid: [153, 50, 204], - darkred: [139, 0, 0], - darksalmon: [233, 150, 122], - darkviolet: [148, 0, 211], - fuchsia: [255, 0, 255], - gold: [255, 215, 0], - green: [0, 128, 0], - indigo: [75, 0, 130], - khaki: [240, 230, 140], - lightblue: [173, 216, 230], - lightcyan: [224, 255, 255], - lightgreen: [144, 238, 144], - lightgrey: [211, 211, 211], - lightpink: [255, 182, 193], - lightyellow: [255, 255, 224], - lime: [0, 255, 0], - magenta: [255, 0, 255], - maroon: [128, 0, 0], - navy: [0, 0, 128], - olive: [128, 128, 0], - orange: [255, 165, 0], - pink: [255, 192, 203], - purple: [128, 0, 128], - violet: [128, 0, 128], - red: [255, 0, 0], - silver: [192, 192, 192], - white: [255, 255, 255], - yellow: [255, 255, 0], - transparent: [255, 255, 255] + aqua: [ 0, 255, 255 ], + azure: [ 240, 255, 255 ], + beige: [ 245, 245, 220 ], + black: [ 0, 0, 0 ], + blue: [ 0, 0, 255 ], + brown: [ 165, 42, 42 ], + cyan: [ 0, 255, 255 ], + darkblue: [ 0, 0, 139 ], + darkcyan: [ 0, 139, 139 ], + darkgrey: [ 169, 169, 169 ], + darkgreen: [ 0, 100, 0 ], + darkkhaki: [ 189, 183, 107 ], + darkmagenta: [ 139, 0, 139 ], + darkolivegreen: [ 85, 107, 47 ], + darkorange: [ 255, 140, 0 ], + darkorchid: [ 153, 50, 204 ], + darkred: [ 139, 0, 0 ], + darksalmon: [ 233, 150, 122 ], + darkviolet: [ 148, 0, 211 ], + fuchsia: [ 255, 0, 255 ], + gold: [ 255, 215, 0 ], + green: [ 0, 128, 0 ], + indigo: [ 75, 0, 130 ], + khaki: [ 240, 230, 140 ], + lightblue: [ 173, 216, 230 ], + lightcyan: [ 224, 255, 255 ], + lightgreen: [ 144, 238, 144 ], + lightgrey: [ 211, 211, 211 ], + lightpink: [ 255, 182, 193 ], + lightyellow: [ 255, 255, 224 ], + lime: [ 0, 255, 0 ], + magenta: [ 255, 0, 255 ], + maroon: [ 128, 0, 0 ], + navy: [ 0, 0, 128 ], + olive: [ 128, 128, 0 ], + orange: [ 255, 165, 0 ], + pink: [ 255, 192, 203 ], + purple: [ 128, 0, 128 ], + violet: [ 128, 0, 128 ], + red: [ 255, 0, 0 ], + silver: [ 192, 192, 192 ], + white: [ 255, 255, 255 ], + yellow: [ 255, 255, 0 ], + transparent: [ 255, 255, 255 ] }, /** @@ -157,29 +157,29 @@ min = Math.min( r, g, b ), h, s, - l = (max + min) / 2; + l = ( max + min ) / 2; if ( max === min ) { // achromatic h = s = 0; } else { d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); switch ( max ) { case r: - h = (g - b) / d + (g < b ? 6 : 0); + h = ( g - b ) / d + ( g < b ? 6 : 0 ); break; case g: - h = (b - r) / d + 2; + h = ( b - r ) / d + 2; break; case b: - h = (r - g) / d + 4; + h = ( r - g ) / d + 4; break; } h /= 6; } - return [h, s, l]; + return [ h, s, l ]; }, /** @@ -212,25 +212,25 @@ t -= 1; } if ( t < 1 / 6 ) { - return p + (q - p) * 6 * t; + return p + ( q - p ) * 6 * t; } if ( t < 1 / 2 ) { return q; } if ( t < 2 / 3 ) { - return p + (q - p) * (2 / 3 - t) * 6; + return p + ( q - p ) * ( 2 / 3 - t ) * 6; } return p; }; - q = l < 0.5 ? l * (1 + s) : l + s - l * s; + q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; p = 2 * l - q; r = hue2rgb( p, q, h + 1 / 3 ); g = hue2rgb( p, q, h ); b = hue2rgb( p, q, h - 1 / 3 ); } - return [r * 255, g * 255, b * 255]; + return [ r * 255, g * 255, b * 255 ]; }, /** @@ -249,11 +249,11 @@ */ getColorBrightness: function ( currentColor, mod ) { var rgbArr = $.colorUtil.getRGB( currentColor ), - hslArr = $.colorUtil.rgbToHsl(rgbArr[0], rgbArr[1], rgbArr[2] ); - rgbArr = $.colorUtil.hslToRgb(hslArr[0], hslArr[1], hslArr[2] + mod); + hslArr = $.colorUtil.rgbToHsl( rgbArr[ 0 ], rgbArr[ 1 ], rgbArr[ 2 ] ); + rgbArr = $.colorUtil.hslToRgb( hslArr[ 0 ], hslArr[ 1 ], hslArr[ 2 ] + mod ); return 'rgb(' + - [parseInt( rgbArr[0], 10), parseInt( rgbArr[1], 10 ), parseInt( rgbArr[2], 10 )].join( ',' ) + + [ parseInt( rgbArr[ 0 ], 10 ), parseInt( rgbArr[ 1 ], 10 ), parseInt( rgbArr[ 2 ], 10 ) ].join( ',' ) + ')'; } diff --git a/resources/src/jquery/jquery.expandableField.js b/resources/src/jquery/jquery.expandableField.js index 48341bc5..221e6bbe 100644 --- a/resources/src/jquery/jquery.expandableField.js +++ b/resources/src/jquery/jquery.expandableField.js @@ -23,7 +23,7 @@ expandField: function ( e, context ) { context.config.beforeExpand.call( context.data.$field, context ); context.data.$field - .animate( { 'width': context.data.expandedWidth }, 'fast', function () { + .animate( { width: context.data.expandedWidth }, 'fast', function () { context.config.afterExpand.call( this, context ); } ); }, @@ -33,18 +33,19 @@ condenseField: function ( e, context ) { context.config.beforeCondense.call( context.data.$field, context ); context.data.$field - .animate( { 'width': context.data.condensedWidth }, 'fast', function () { + .animate( { width: context.data.condensedWidth }, 'fast', function () { context.config.afterCondense.call( this, context ); } ); }, /** * Sets the value of a property, and updates the widget accordingly - * @param property String Name of property - * @param value Mixed Value to set property with + * + * @param {String} property Name of property + * @param {Mixed} value Value to set property with */ configure: function ( context, property, value ) { // TODO: Validate creation using fallback values - context.config[property] = value; + context.config[ property ] = value; } }; @@ -87,20 +88,20 @@ /* API */ // Handle various calling styles if ( args.length > 0 ) { - if ( typeof args[0] === 'object' ) { + if ( typeof args[ 0 ] === 'object' ) { // Apply set of properties - for ( key in args[0] ) { - $.expandableField.configure( context, key, args[0][key] ); + for ( key in args[ 0 ] ) { + $.expandableField.configure( context, key, args[ 0 ][ key ] ); } - } else if ( typeof args[0] === 'string' ) { + } else if ( typeof args[ 0 ] === 'string' ) { if ( args.length > 1 ) { // Set property values - $.expandableField.configure( context, args[0], args[1] ); + $.expandableField.configure( context, args[ 0 ], args[ 1 ] ); // TODO: Do we need to check both null and undefined? } else if ( returnValue === null || returnValue === undefined ) { // Get property values, but don't give access to internal data - returns only the first - returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] ); + returnValue = ( args[ 0 ] in context.config ? undefined : context.config[ args[ 0 ] ] ); } } } diff --git a/resources/src/jquery/jquery.farbtastic.js b/resources/src/jquery/jquery.farbtastic.js index d7024cc8..f70913f9 100644 --- a/resources/src/jquery/jquery.farbtastic.js +++ b/resources/src/jquery/jquery.farbtastic.js @@ -52,10 +52,10 @@ jQuery._farbtastic = function (container, callback) { if (this.currentStyle.backgroundImage != 'none') { var image = this.currentStyle.backgroundImage; image = this.currentStyle.backgroundImage.slice(5, image.length - 2); - $(this).css({ - 'backgroundImage': 'none', - 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" - }); + $(this).css( { + backgroundImage: 'none', + filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" + } ); } }); } diff --git a/resources/src/jquery/jquery.getAttrs.js b/resources/src/jquery/jquery.getAttrs.js index 64827fb7..3064b423 100644 --- a/resources/src/jquery/jquery.getAttrs.js +++ b/resources/src/jquery/jquery.getAttrs.js @@ -8,7 +8,7 @@ function serializeControls( controls ) { len = controls.length; for ( i = 0; i < len; i++ ) { - data[ controls[i].name ] = controls[i].value; + data[ controls[ i ].name ] = controls[ i ].value; } return data; @@ -23,7 +23,7 @@ function serializeControls( controls ) { * @return {Object} */ jQuery.fn.getAttrs = function () { - return serializeControls( this[0].attributes ); + return serializeControls( this[ 0 ].attributes ); }; /** diff --git a/resources/src/jquery/jquery.hidpi.js b/resources/src/jquery/jquery.hidpi.js index 8fca0567..aa6590bf 100644 --- a/resources/src/jquery/jquery.hidpi.js +++ b/resources/src/jquery/jquery.hidpi.js @@ -27,14 +27,15 @@ $.devicePixelRatio = function () { if ( window.devicePixelRatio !== undefined ) { // Most web browsers: - // * WebKit (Safari, Chrome, Android browser, etc) + // * 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 doesn't report pixel ratio directly, but we can get the + // 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. @@ -53,6 +54,52 @@ $.devicePixelRatio = function () { }; /** + * 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() ); +}; + +/** * Implement responsive images based on srcset attributes, if browser has no * native srcset support. * @@ -106,11 +153,11 @@ $.matchSrcSet = function ( devicePixelRatio, srcset ) { selectedSrc = null; candidates = srcset.split( / *, */ ); for ( i = 0; i < candidates.length; i++ ) { - candidate = candidates[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 ); + 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; diff --git a/resources/src/jquery/jquery.highlightText.js b/resources/src/jquery/jquery.highlightText.js index 13382182..e37f19b0 100644 --- a/resources/src/jquery/jquery.highlightText.js +++ b/resources/src/jquery/jquery.highlightText.js @@ -3,7 +3,7 @@ * TODO: Add a function for restoring the previous text. * TODO: Accept mappings for converting shortcuts like WP: to Wikipedia:. */ -( function ( $ ) { +( function ( $, mw ) { $.highlightText = { @@ -12,10 +12,10 @@ var i, patArray = pat.split( ' ' ); for ( i = 0; i < patArray.length; i++ ) { - if ( patArray[i].length === 0 ) { + if ( patArray[ i ].length === 0 ) { continue; } - $.highlightText.innerHighlight( node, patArray[i] ); + $.highlightText.innerHighlight( node, patArray[ i ] ); } return node; }, @@ -23,15 +23,14 @@ // scans a node looking for the pattern and wraps a span around each match innerHighlight: function ( node, pat ) { var i, match, pos, spannode, middlebit, middleclone; - // if this is a text node - if ( node.nodeType === 3 ) { + if ( node.nodeType === Node.TEXT_NODE ) { // TODO - need to be smarter about the character matching here. // non latin characters can make regex think a new word has begun: do not use \b // http://stackoverflow.com/questions/3787072/regex-wordwrap-with-utf8-characters-in-js // look for an occurrence of our pattern and store the starting position - match = node.data.match( new RegExp( '(^|\\s)' + $.escapeRE( pat ), 'i' ) ); + match = node.data.match( new RegExp( '(^|\\s)' + mw.RegExp.escape( pat ), 'i' ) ); if ( match ) { - pos = match.index + match[1].length; // include length of any matched spaces + pos = match.index + match[ 1 ].length; // include length of any matched spaces // create the span wrapper for the matched text spannode = document.createElement( 'span' ); spannode.className = 'highlight'; @@ -46,8 +45,8 @@ // replace the matched node, with our span-wrapped clone of the matched node middlebit.parentNode.replaceChild( spannode, middlebit ); } - // if this is an element with childnodes, and not a script, style or an element we created - } else if ( node.nodeType === 1 + } 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' @@ -56,7 +55,7 @@ ) { for ( i = 0; i < node.childNodes.length; ++i ) { // call the highlight function for each child node - $.highlightText.innerHighlight( node.childNodes[i], pat ); + $.highlightText.innerHighlight( node.childNodes[ i ], pat ); } } } @@ -70,4 +69,4 @@ } ); }; -}( jQuery ) ); +}( jQuery, mediaWiki ) ); diff --git a/resources/src/jquery/jquery.localize.js b/resources/src/jquery/jquery.localize.js index 0b423545..f5932b24 100644 --- a/resources/src/jquery/jquery.localize.js +++ b/resources/src/jquery/jquery.localize.js @@ -5,16 +5,16 @@ /** * Gets a localized message, using parameters from options if present. - * @ignore * + * @ignore * @param {Object} options * @param {string} key * @return {string} Localized message */ function msg( options, key ) { - var args = options.params[key] || []; + var args = options.params[ key ] || []; // Format: mw.msg( key [, p1, p2, ...] ) - args.unshift( options.prefix + ( options.keys[key] || key ) ); + args.unshift( options.prefix + ( options.keys[ key ] || key ) ); return mw.msg.apply( mw, args ); } @@ -108,7 +108,7 @@ function msg( options, key ) { */ $.fn.localize = function ( options ) { var $target = this, - attributes = ['title', 'alt', 'placeholder']; + attributes = [ 'title', 'alt', 'placeholder' ]; // Extend options options = $.extend( { diff --git a/resources/src/jquery/jquery.makeCollapsible.js b/resources/src/jquery/jquery.makeCollapsible.js index f7c42177..19fdb263 100644 --- a/resources/src/jquery/jquery.makeCollapsible.js +++ b/resources/src/jquery/jquery.makeCollapsible.js @@ -159,8 +159,13 @@ } if ( e ) { - if ( e.type === 'click' && options.linksPassthru && $.nodeName( e.target, 'a' ) ) { - // Don't fire if a link was clicked, if requested (for premade togglers by default) + if ( + e.type === 'click' && + options.linksPassthru && + $.nodeName( e.target, 'a' ) && + $( e.target ).attr( 'href' ) !== '#' + ) { + // Don't fire if a link with href !== '#' was clicked, if requested (for premade togglers by default) return; } else if ( e.type === 'keypress' && e.which !== 13 && e.which !== 32 ) { // Only handle keypresses on the "Enter" or "Space" keys diff --git a/resources/src/jquery/jquery.mwExtension.js b/resources/src/jquery/jquery.mwExtension.js index e6e33ade..27ceb2bc 100644 --- a/resources/src/jquery/jquery.mwExtension.js +++ b/resources/src/jquery/jquery.mwExtension.js @@ -1,9 +1,11 @@ /* * JavaScript backwards-compatibility alternatives and other convenience functions + * + * @deprecated since 1.26 Dated collection of miscellaneous utilities. Methods are + * either trivially inline, obsolete, or have a better place elsewhere. */ -( function ( $ ) { - - $.extend( { +( function ( $, mw ) { + $.each( { trimLeft: function ( str ) { return str === null ? '' : str.toString().replace( /^\s+/, '' ); }, @@ -14,9 +16,6 @@ ucFirst: function ( str ) { return str.charAt( 0 ).toUpperCase() + str.slice( 1 ); }, - escapeRE: function ( str ) { - return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ); - }, isDomElement: function ( el ) { return !!el && !!el.nodeType; }, @@ -28,7 +27,7 @@ return true; } // the for-loop could potentially contain prototypes - // to avoid that we check it's length first + // to avoid that we check its length first if ( v.length === 0 ) { return true; } @@ -45,11 +44,11 @@ return false; } for ( var i = 0; i < arrThis.length; i++ ) { - if ( $.isArray( arrThis[i] ) ) { - if ( !$.compareArray( arrThis[i], arrAgainst[i] ) ) { + if ( $.isArray( arrThis[ i ] ) ) { + if ( !$.compareArray( arrThis[ i ], arrAgainst[ i ] ) ) { return false; } - } else if ( arrThis[i] !== arrAgainst[i] ) { + } else if ( arrThis[ i ] !== arrAgainst[ i ] ) { return false; } } @@ -72,24 +71,24 @@ // Check if this property is also present in the other object if ( prop in objectB ) { // Compare the types of the properties - type = typeof objectA[prop]; - if ( type === typeof objectB[prop] ) { + type = typeof objectA[ prop ]; + if ( type === typeof objectB[ prop ] ) { // Recursively check objects inside this one switch ( type ) { case 'object' : - if ( !$.compareObject( objectA[prop], objectB[prop] ) ) { + if ( !$.compareObject( objectA[ prop ], objectB[ prop ] ) ) { return false; } break; case 'function' : // Functions need to be strings to compare them properly - if ( objectA[prop].toString() !== objectB[prop].toString() ) { + if ( objectA[ prop ].toString() !== objectB[ prop ].toString() ) { return false; } break; default: // Strings, numbers - if ( objectA[prop] !== objectB[prop] ) { + if ( objectA[ prop ] !== objectB[ prop ] ) { return false; } break; @@ -117,6 +116,12 @@ } return true; } + }, function ( key, value ) { + mw.log.deprecate( $, key, value ); } ); -}( jQuery ) ); + mw.log.deprecate( $, 'escapeRE', function ( str ) { + return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ); + }, 'Use mediawiki.RegExp instead.' ); + +} )( jQuery, mediaWiki ); diff --git a/resources/src/jquery/jquery.placeholder.js b/resources/src/jquery/jquery.placeholder.js index d50422e2..9c18a919 100644 --- a/resources/src/jquery/jquery.placeholder.js +++ b/resources/src/jquery/jquery.placeholder.js @@ -13,23 +13,115 @@ * @version 2.1.0 * @license MIT */ -( function ($) { +( function ( $ ) { - var isInputSupported = 'placeholder' in document.createElement('input'), - isTextareaSupported = 'placeholder' in document.createElement('textarea'), + var isInputSupported = 'placeholder' in document.createElement( 'input' ), + isTextareaSupported = 'placeholder' in document.createElement( 'textarea' ), prototype = $.fn, valHooks = $.valHooks, propHooks = $.propHooks, hooks, placeholder; - if (isInputSupported && isTextareaSupported) { + function safeActiveElement() { + // Avoid IE9 `document.activeElement` of death + // https://github.com/mathiasbynens/jquery-placeholder/pull/99 + try { + return document.activeElement; + } catch ( err ) {} + } + + function args( elem ) { + // Return an object of element attributes + var newAttrs = {}, + rinlinejQuery = /^jQuery\d+$/; + $.each( elem.attributes, function ( i, attr ) { + if ( attr.specified && !rinlinejQuery.test( attr.name ) ) { + newAttrs[ attr.name ] = attr.value; + } + } ); + return newAttrs; + } + + function clearPlaceholder( event, value ) { + var input = this, + $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' ) ); + // If `clearPlaceholder` was called from `$.valHooks.input.set` + if ( event === true ) { + $input[ 0 ].value = value; + return value; + } + $input.focus(); + } else { + input.value = ''; + $input.removeClass( 'placeholder' ); + if ( input === safeActiveElement() ) { + input.select(); + } + } + } + } - placeholder = prototype.placeholder = function (text) { + function setPlaceholder() { + var $replacement, + input = this, + $input = $( input ), + id = this.id; + if ( !input.value ) { + if ( input.type === 'password' ) { + if ( !$input.data( 'placeholder-textinput' ) ) { + try { + $replacement = $input.clone().attr( { type: 'text' } ); + } catch ( e ) { + $replacement = $( '<input>' ).attr( $.extend( args( this ), { type: 'text' } ) ); + } + $replacement + .removeAttr( 'name' ) + .data( { + 'placeholder-password': $input, + 'placeholder-id': id + } ) + .bind( 'focus.placeholder drop.placeholder', clearPlaceholder ); + $input + .data( { + 'placeholder-textinput': $replacement, + 'placeholder-id': id + } ) + .before( $replacement ); + } + $input = $input.removeAttr( 'id' ).hide().prev().attr( 'id', id ).show(); + // Note: `$input[0] != input` now! + } + $input.addClass( 'placeholder' ); + $input[ 0 ].value = $input.attr( 'placeholder' ); + } else { + $input.removeClass( 'placeholder' ); + } + } + + function changePlaceholder( text ) { + var hasArgs = arguments.length, + $input = this; + if ( hasArgs ) { + if ( $input.attr( 'placeholder' ) !== text ) { + $input.prop( 'placeholder', text ); + if ( $input.hasClass( 'placeholder' ) ) { + $input[ 0 ].value = text; + } + } + } + } + + if ( isInputSupported && isTextareaSupported ) { + + placeholder = prototype.placeholder = function ( text ) { var hasArgs = arguments.length; - if (hasArgs) { - changePlaceholder.call(this, text); + if ( hasArgs ) { + changePlaceholder.call( this, text ); } return this; @@ -39,25 +131,25 @@ } else { - placeholder = prototype.placeholder = function (text) { + placeholder = prototype.placeholder = function ( text ) { var $this = this, hasArgs = arguments.length; - if (hasArgs) { - changePlaceholder.call(this, text); + if ( hasArgs ) { + changePlaceholder.call( this, text ); } $this - .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]') + .filter( ( isInputSupported ? 'textarea' : ':input' ) + '[placeholder]' ) .filter( function () { - return !$(this).data('placeholder-enabled'); - }) - .bind({ + return !$( this ).data( 'placeholder-enabled' ); + } ) + .bind( { 'focus.placeholder drop.placeholder': clearPlaceholder, 'blur.placeholder': setPlaceholder - }) - .data('placeholder-enabled', true) - .trigger('blur.placeholder'); + } ) + .data( 'placeholder-enabled', true ) + .trigger( 'blur.placeholder' ); return $this; }; @@ -65,36 +157,36 @@ placeholder.textarea = isTextareaSupported; hooks = { - 'get': function (element) { - var $element = $(element), - $passwordInput = $element.data('placeholder-password'); - if ($passwordInput) { - return $passwordInput[0].value; + get: function ( element ) { + var $element = $( element ), + $passwordInput = $element.data( 'placeholder-password' ); + if ( $passwordInput ) { + return $passwordInput[ 0 ].value; } - return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value; + return $element.data( 'placeholder-enabled' ) && $element.hasClass( 'placeholder' ) ? '' : element.value; }, - 'set': function (element, value) { - var $element = $(element), - $passwordInput = $element.data('placeholder-password'); - if ($passwordInput) { - $passwordInput[0].value = value; + set: function ( element, value ) { + var $element = $( element ), + $passwordInput = $element.data( 'placeholder-password' ); + if ( $passwordInput ) { + $passwordInput[ 0 ].value = value; return value; } - if (!$element.data('placeholder-enabled')) { + if ( !$element.data( 'placeholder-enabled' ) ) { element.value = value; return value; } - if (!value) { + if ( !value ) { element.value = value; // Issue #56: Setting the placeholder causes problems if the element continues to have focus. - if (element !== safeActiveElement()) { + if ( element !== safeActiveElement() ) { // We can't use `triggerHandler` here because of dummy text/password inputs :( - setPlaceholder.call(element); + setPlaceholder.call( element ); } - } else if ($element.hasClass('placeholder')) { - if (!clearPlaceholder.call(element, true, value)) { + } else if ( $element.hasClass( 'placeholder' ) ) { + if ( !clearPlaceholder.call( element, true, value ) ) { element.value = value; } } else { @@ -105,125 +197,32 @@ } }; - if (!isInputSupported) { + if ( !isInputSupported ) { valHooks.input = hooks; propHooks.value = hooks; } - if (!isTextareaSupported) { + if ( !isTextareaSupported ) { valHooks.textarea = hooks; propHooks.value = hooks; } $( function () { // Look for forms - $(document).delegate('form', 'submit.placeholder', function () { + $( document ).delegate( 'form', 'submit.placeholder', function () { // Clear the placeholder values so they don't get submitted - var $inputs = $('.placeholder', this).each(clearPlaceholder); + var $inputs = $( '.placeholder', this ).each( clearPlaceholder ); setTimeout( function () { - $inputs.each(setPlaceholder); - }, 10); - }); - }); + $inputs.each( setPlaceholder ); + }, 10 ); + } ); + } ); // Clear placeholder values upon page reload - $(window).bind('beforeunload.placeholder', function () { - $('.placeholder').each( function () { + $( window ).bind( 'beforeunload.placeholder', function () { + $( '.placeholder' ).each( function () { this.value = ''; - }); - }); + } ); + } ); } - - function args(elem) { - // Return an object of element attributes - var newAttrs = {}, - rinlinejQuery = /^jQuery\d+$/; - $.each(elem.attributes, function (i, attr) { - if (attr.specified && !rinlinejQuery.test(attr.name)) { - newAttrs[attr.name] = attr.value; - } - }); - return newAttrs; - } - - function clearPlaceholder(event, value) { - var input = this, - $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')); - // If `clearPlaceholder` was called from `$.valHooks.input.set` - if (event === true) { - $input[0].value = value; - return value; - } - $input.focus(); - } else { - input.value = ''; - $input.removeClass('placeholder'); - if (input === safeActiveElement()) { - input.select(); - } - } - } - } - - function setPlaceholder() { - var $replacement, - input = this, - $input = $(input), - id = this.id; - if (!input.value) { - if (input.type === 'password') { - if (!$input.data('placeholder-textinput')) { - try { - $replacement = $input.clone().attr({ 'type': 'text' }); - } catch (e) { - $replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' })); - } - $replacement - .removeAttr('name') - .data({ - 'placeholder-password': $input, - 'placeholder-id': id - }) - .bind('focus.placeholder drop.placeholder', clearPlaceholder); - $input - .data({ - 'placeholder-textinput': $replacement, - 'placeholder-id': id - }) - .before($replacement); - } - $input = $input.removeAttr('id').hide().prev().attr('id', id).show(); - // Note: `$input[0] != input` now! - } - $input.addClass('placeholder'); - $input[0].value = $input.attr('placeholder'); - } else { - $input.removeClass('placeholder'); - } - } - - function safeActiveElement() { - // Avoid IE9 `document.activeElement` of death - // https://github.com/mathiasbynens/jquery-placeholder/pull/99 - try { - return document.activeElement; - } catch (err) {} - } - - function changePlaceholder(text) { - var hasArgs = arguments.length, - $input = this; - if (hasArgs) { - if ($input.attr('placeholder') !== text) { - $input.prop('placeholder', text); - if ($input.hasClass('placeholder')) { - $input[0].value = text; - } - } - } - } - -}(jQuery)); +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.qunit.completenessTest.js b/resources/src/jquery/jquery.qunit.completenessTest.js index 556bf8c7..785b2738 100644 --- a/resources/src/jquery/jquery.qunit.completenessTest.js +++ b/resources/src/jquery/jquery.qunit.completenessTest.js @@ -51,14 +51,14 @@ /** * CompletenessTest - * @constructor * + * @constructor * @example * var myTester = new CompletenessTest( myLib ); - * @param masterVariable {Object} The root variable that contains all object + * @param {Object} masterVariable The root variable that contains all object * members. CompletenessTest will recursively traverse objects and keep track * of all methods. - * @param ignoreFn {Function} Optionally pass a function to filter out certain + * @param {Function} [ignoreFn] Optionally pass a function to filter out certain * methods. Example: You may want to filter out instances of jQuery or some * other constructor. Otherwise "missingTests" will include all methods that * were not called from that instance. @@ -132,7 +132,7 @@ elOutputWrapper.appendChild( elContainer ); util.each( style, function ( key, value ) { - elOutputWrapper.style[key] = value; + elOutputWrapper.style[ key ] = value; } ); return elOutputWrapper; } @@ -186,12 +186,12 @@ * Depending on the action it either injects our listener into the methods, or * reads from our tracker and records which methods have not been called by the test suite. * - * @param currName {String|Null} Name of the given object member (Initially this is null). - * @param currVar {mixed} The variable to check (initially an object, + * @param {String|Null} currName Name of the given object member (Initially this is null). + * @param {mixed} currVar The variable to check (initially an object, * further down it could be anything). - * @param masterVariable {Object} Throughout our interation, always keep track of the master/root. + * @param {Object} masterVariable Throughout our interation, always keep track of the master/root. * Initially this is the same as currVar. - * @param parentPathArray {Array} Array of names that indicate our breadcrumb path starting at + * @param {Array} parentPathArray Array of names that indicate our breadcrumb path starting at * masterVariable. Not including currName. */ walkTheObject: function ( currObj, currName, masterVariable, parentPathArray ) { @@ -201,7 +201,7 @@ if ( currName ) { currPathArray.push( currName ); - currVal = currObj[currName]; + currVal = currObj[ currName ]; } else { currName = '(root)'; currVal = currObj; @@ -258,12 +258,12 @@ * was called during the test suite (as far as the tracker knows). * If not it adds it to missingTests. * - * @param fnName {String} + * @param {String} fnName * @return {Boolean} */ hasTest: function ( fnName ) { if ( !( fnName in this.methodCallTracker ) ) { - this.missingTests[fnName] = true; + this.missingTests[ fnName ] = true; return false; } return true; @@ -275,9 +275,9 @@ * Injects a function (such as a spy that updates methodCallTracker when * it's called) inside another function. * - * @param masterVariable {Object} - * @param objectPathArray {Array} - * @param injectFn {Function} + * @param {Object} masterVariable + * @param {Array} objectPathArray + * @param {Function} injectFn */ injectCheck: function ( obj, key, injectFn ) { var spy, @@ -291,8 +291,11 @@ // 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; diff --git a/resources/src/jquery/jquery.spinner.js b/resources/src/jquery/jquery.spinner.js index 361d3e08..41c555b7 100644 --- a/resources/src/jquery/jquery.spinner.js +++ b/resources/src/jquery/jquery.spinner.js @@ -67,7 +67,7 @@ opts = $.extend( {}, defaults, opts ); - var $spinner = $( '<div>', { 'class': 'mw-spinner', 'title': '...' } ); + var $spinner = $( '<div>', { 'class': 'mw-spinner', title: '...' } ); if ( opts.id !== undefined ) { $spinner.attr( 'id', 'mw-spinner-' + opts.id ); } diff --git a/resources/src/jquery/jquery.suggestions.js b/resources/src/jquery/jquery.suggestions.js index 813c37ce..dc1c7794 100644 --- a/resources/src/jquery/jquery.suggestions.js +++ b/resources/src/jquery/jquery.suggestions.js @@ -53,6 +53,12 @@ * @param {Function} options.result.select Called in context of the suggestions-result-current element. * @param {jQuery} options.result.select.$textbox * + * @param {Object} [options.update] Set of callbacks for listening to a change in the text input. + * + * @param {Function} options.update.before Called right after the user changes the textbox text. + * @param {Function} options.update.after Called after results are updated either from the cache or + * the API as a result of the user input. + * * @param {jQuery} [options.$region=this] The element to place the suggestions below and match width of. * * @param {string[]} [options.suggestions] Array of suggestions to display. @@ -83,7 +89,7 @@ * @param {boolean} [options.positionFromLeft] Sets `expandFrom=left`, for backwards * compatibility. * - * @param {boolean} [options.highlightInput=false] Whether to hightlight matched portions of the + * @param {boolean} [options.highlightInput=false] Whether to highlight matched portions of the * input or not. */ ( function ( $ ) { @@ -136,6 +142,7 @@ $.suggestions = { * call to this function still pending will be canceled. If the value in the * textbox is empty or hasn't changed since the last time suggestions were fetched, * this function does nothing. + * * @param {boolean} delayed Whether or not to delay this by the currently configured amount of time */ update: function ( context, delayed ) { @@ -144,6 +151,10 @@ $.suggestions = { cache = context.data.cache, cacheHit; + if ( typeof context.config.update.before === 'function' ) { + context.config.update.before.call( context.data.$textbox ); + } + // Only fetch if the value in the textbox changed and is not empty, or if the results were hidden // if the textbox is empty then clear the result div, but leave other settings intouched if ( val.length === 0 ) { @@ -158,6 +169,9 @@ $.suggestions = { if ( context.config.cache && hasOwn.call( cache, val ) ) { if ( +new Date() - cache[ val ].timestamp < context.config.cacheMaxAge ) { context.data.$textbox.suggestions( 'suggestions', cache[ val ].suggestions ); + if ( typeof context.config.update.after === 'function' ) { + context.config.update.after.call( context.data.$textbox ); + } cacheHit = true; } else { // Cache expired @@ -171,6 +185,9 @@ $.suggestions = { function ( suggestions ) { suggestions = suggestions.slice( 0, context.config.maxRows ); context.data.$textbox.suggestions( 'suggestions', suggestions ); + if ( typeof context.config.update.after === 'function' ) { + context.config.update.after.call( context.data.$textbox ); + } if ( context.config.cache ) { cache[ val ] = { suggestions: suggestions, @@ -213,6 +230,7 @@ $.suggestions = { /** * Sets the value of a property, and updates the widget accordingly + * * @param {string} property Name of property * @param {Mixed} value Value to set property with */ @@ -227,12 +245,13 @@ $.suggestions = { case 'cancel': case 'special': case 'result': + case 'update': case '$region': case 'expandFrom': - context.config[property] = value; + context.config[ property ] = value; break; case 'suggestions': - context.config[property] = value; + context.config[ property ] = value; // Update suggestions if ( context.data !== undefined ) { if ( context.data.$textbox.val().length === 0 ) { @@ -260,7 +279,7 @@ $.suggestions = { expandFrom = 'left'; // Catch invalid values, default to 'auto' - } else if ( $.inArray( expandFrom, ['left', 'right', 'start', 'end', 'auto'] ) === -1 ) { + } else if ( $.inArray( expandFrom, [ 'left', 'right', 'start', 'end', 'auto' ] ) === -1 ) { expandFrom = 'auto'; } @@ -319,11 +338,11 @@ $.suggestions = { expWidth = -1; for ( i = 0; i < context.config.suggestions.length; i++ ) { /*jshint loopfunc:true */ - text = context.config.suggestions[i]; + text = context.config.suggestions[ i ]; $result = $( '<div>' ) .addClass( 'suggestions-result' ) .attr( 'rel', i ) - .data( 'text', context.config.suggestions[i] ) + .data( 'text', context.config.suggestions[ i ] ) .mousemove( function () { context.data.selectedWithMouse = true; $.suggestions.highlight( @@ -335,7 +354,7 @@ $.suggestions = { .appendTo( $results ); // Allow custom rendering if ( typeof context.config.result.render === 'function' ) { - context.config.result.render.call( $result, context.config.suggestions[i], context ); + context.config.result.render.call( $result, context.config.suggestions[ i ], context ); } else { $result.text( text ); } @@ -376,28 +395,29 @@ $.suggestions = { } break; case 'maxRows': - context.config[property] = Math.max( 1, Math.min( 100, value ) ); + context.config[ property ] = Math.max( 1, Math.min( 100, value ) ); break; case 'delay': - context.config[property] = Math.max( 0, Math.min( 1200, value ) ); + context.config[ property ] = Math.max( 0, Math.min( 1200, value ) ); break; case 'cacheMaxAge': - context.config[property] = Math.max( 1, value ); + context.config[ property ] = Math.max( 1, value ); break; case 'maxExpandFactor': - context.config[property] = Math.max( 1, value ); + context.config[ property ] = Math.max( 1, value ); break; case 'cache': case 'submitOnClick': case 'positionFromLeft': case 'highlightInput': - context.config[property] = !!value; + context.config[ property ] = !!value; break; } }, /** * Highlight a result in the results table + * * @param {jQuery|string} result `<tr>` to highlight, or 'prev' or 'next' * @param {boolean} updateTextbox If true, put the suggestion in the textbox */ @@ -467,6 +487,7 @@ $.suggestions = { /** * Respond to keypress event + * * @param {number} key Code of key pressed */ keypress: function ( e, context, key ) { @@ -559,6 +580,7 @@ $.fn.suggestions = function () { cancel: function () {}, special: {}, result: {}, + update: {}, $region: $( this ), suggestions: [], maxRows: 10, @@ -577,18 +599,18 @@ $.fn.suggestions = function () { // Handle various calling styles if ( args.length > 0 ) { - if ( typeof args[0] === 'object' ) { + if ( typeof args[ 0 ] === 'object' ) { // Apply set of properties - for ( key in args[0] ) { - $.suggestions.configure( context, key, args[0][key] ); + for ( key in args[ 0 ] ) { + $.suggestions.configure( context, key, args[ 0 ][ key ] ); } - } else if ( typeof args[0] === 'string' ) { + } else if ( typeof args[ 0 ] === 'string' ) { if ( args.length > 1 ) { // Set property values - $.suggestions.configure( context, args[0], args[1] ); + $.suggestions.configure( context, args[ 0 ], args[ 1 ] ); } else if ( returnValue === null || returnValue === undefined ) { // Get property values, but don't give access to internal data - returns only the first - returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] ); + returnValue = ( args[ 0 ] in context.config ? undefined : context.config[ args[ 0 ] ] ); } } } diff --git a/resources/src/jquery/jquery.tablesorter.js b/resources/src/jquery/jquery.tablesorter.js index ff5ff0a9..eaa138b9 100644 --- a/resources/src/jquery/jquery.tablesorter.js +++ b/resources/src/jquery/jquery.tablesorter.js @@ -8,7 +8,7 @@ * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * - * Depends on mw.config (wgDigitTransformTable, wgDefaultDateFormat, wgContentLanguage) + * Depends on mw.config (wgDigitTransformTable, wgDefaultDateFormat, wgPageContentLanguage) * and mw.language.months. * * Uses 'tableSorterCollation' in mw.config (if available) @@ -70,8 +70,8 @@ var i, len = parsers.length; for ( i = 0; i < len; i++ ) { - if ( parsers[i].id.toLowerCase() === name.toLowerCase() ) { - return parsers[i]; + if ( parsers[ i ].id.toLowerCase() === name.toLowerCase() ) { + return parsers[ i ]; } } return false; @@ -95,8 +95,7 @@ return $node.attr( 'alt' ) || ''; // handle undefined alt } else { return $.map( $.makeArray( node.childNodes ), function ( elem ) { - // 1 is for document.ELEMENT_NODE (the constant is undefined on old browsers) - if ( elem.nodeType === 1 ) { + if ( elem.nodeType === Node.ELEMENT_NODE ) { return getElementSortKey( elem ); } else { return $.text( elem ); @@ -106,69 +105,83 @@ } } - function detectParserForColumn( table, rows, cellIndex ) { + function detectParserForColumn( table, rows, column ) { var l = parsers.length, + cellIndex, nodeValue, // Start with 1 because 0 is the fallback parser i = 1, + lastRowIndex = -1, rowIndex = 0, concurrent = 0, + empty = 0, needed = ( rows.length > 4 ) ? 5 : rows.length; while ( i < l ) { - if ( rows[rowIndex] && rows[rowIndex].cells[cellIndex] ) { - nodeValue = $.trim( getElementSortKey( rows[rowIndex].cells[cellIndex] ) ); + if ( rows[ rowIndex ] ) { + if ( rowIndex !== lastRowIndex ) { + lastRowIndex = rowIndex; + cellIndex = $( rows[ rowIndex ] ).data( 'columnToCell' )[ column ]; + nodeValue = $.trim( getElementSortKey( rows[ rowIndex ].cells[ cellIndex ] ) ); + } } else { nodeValue = ''; } if ( nodeValue !== '' ) { - if ( parsers[i].is( nodeValue, table ) ) { + if ( parsers[ i ].is( nodeValue, table ) ) { concurrent++; rowIndex++; if ( concurrent >= needed ) { // Confirmed the parser for multiple cells, let's return it - return parsers[i]; + return parsers[ i ]; } } else { // Check next parser, reset rows i++; rowIndex = 0; concurrent = 0; + empty = 0; } } else { // Empty cell + empty++; rowIndex++; - if ( rowIndex > rows.length ) { - rowIndex = 0; + if ( rowIndex >= rows.length ) { + if ( concurrent >= rows.length - empty ) { + // Confirmed the parser for all filled cells + return parsers[ i ]; + } + // Check next parser, reset rows i++; + rowIndex = 0; + concurrent = 0; + empty = 0; } } } // 0 is always the generic parser (text) - return parsers[0]; + return parsers[ 0 ]; } function buildParserCache( table, $headers ) { - var sortType, cells, len, i, parser, - rows = table.tBodies[0].rows, + var sortType, len, j, parser, + rows = table.tBodies[ 0 ].rows, + config = $( table ).data( 'tablesorter' ).config, parsers = []; - if ( rows[0] ) { - - cells = rows[0].cells; - len = cells.length; - - for ( i = 0; i < len; i++ ) { + if ( rows[ 0 ] ) { + len = config.columns; + for ( j = 0; j < len; j++ ) { parser = false; - sortType = $headers.eq( i ).data( 'sortType' ); + sortType = $headers.eq( config.columnToHeader[ j ] ).data( 'sortType' ); if ( sortType !== undefined ) { parser = getParserById( sortType ); } if ( parser === false ) { - parser = detectParserForColumn( table, rows, i ); + parser = detectParserForColumn( table, rows, j ); } parsers.push( parser ); @@ -181,33 +194,35 @@ function buildCache( table ) { var i, j, $row, cols, - totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0, - totalCells = ( table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length ) || 0, + totalRows = ( table.tBodies[ 0 ] && table.tBodies[ 0 ].rows.length ) || 0, config = $( table ).data( 'tablesorter' ).config, parsers = config.parsers, + len = parsers.length, + cellIndex, cache = { row: [], normalized: [] }; - for ( i = 0; i < totalRows; ++i ) { + for ( i = 0; i < totalRows; i++ ) { // Add the table data to main data array - $row = $( table.tBodies[0].rows[i] ); + $row = $( table.tBodies[ 0 ].rows[ i ] ); cols = []; // if this is a child row, add it to the last row's children and // continue to the next row if ( $row.hasClass( config.cssChildRow ) ) { - cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add( $row ); + cache.row[ cache.row.length - 1 ] = cache.row[ cache.row.length - 1 ].add( $row ); // go to the next for loop continue; } cache.row.push( $row ); - for ( j = 0; j < totalCells; ++j ) { - cols.push( parsers[j].format( getElementSortKey( $row[0].cells[j] ), table, $row[0].cells[j] ) ); + for ( j = 0; j < len; j++ ) { + cellIndex = $row.data( 'columnToCell' )[ j ]; + cols.push( parsers[ j ].format( getElementSortKey( $row[ 0 ].cells[ cellIndex ] ) ) ); } cols.push( cache.normalized.length ); // add position for rowCache @@ -223,20 +238,20 @@ row = cache.row, normalized = cache.normalized, totalRows = normalized.length, - checkCell = ( normalized[0].length - 1 ), + checkCell = ( normalized[ 0 ].length - 1 ), fragment = document.createDocumentFragment(); for ( i = 0; i < totalRows; i++ ) { - pos = normalized[i][checkCell]; + pos = normalized[ i ][ checkCell ]; - l = row[pos].length; + l = row[ pos ].length; for ( j = 0; j < l; j++ ) { - fragment.appendChild( row[pos][j] ); + fragment.appendChild( row[ pos ][ j ] ); } } - table.tBodies[0].appendChild( fragment ); + table.tBodies[ 0 ].appendChild( fragment ); $( table ).trigger( 'sortEnd.tablesorter' ); } @@ -249,7 +264,8 @@ * * After this, it will look at all rows at the bottom for footer rows * And place these in a tfoot using similar rules. - * @param $table jQuery object for a <table> + * + * @param {jQuery} $table object for a <table> */ function emulateTHeadAndFoot( $table ) { var $thead, $tfoot, i, len, @@ -270,26 +286,37 @@ $tfoot = $( '<tfoot>' ); len = $rows.length; for ( i = len - 1; i >= 0; i-- ) { - if ( $( $rows[i] ).children( 'td' ).length ) { + if ( $( $rows[ i ] ).children( 'td' ).length ) { break; } - $tfoot.prepend( $( $rows[i] ) ); + $tfoot.prepend( $( $rows[ i ] ) ); } $table.append( $tfoot ); } } + function uniqueElements( array ) { + var uniques = []; + $.each( array, function ( index, elem ) { + if ( elem !== undefined && $.inArray( elem, uniques ) === -1 ) { + uniques.push( elem ); + } + } ); + return uniques; + } + function buildHeaders( table, msg ) { var config = $( table ).data( 'tablesorter' ).config, maxSeen = 0, colspanOffset = 0, columns, - i, + k, $cell, rowspan, colspan, headerCount, longestTR, + headerIndex, exploded, $tableHeaders = $( [] ), $tableRows = $( 'thead:eq(0) > tr', table ); @@ -308,7 +335,7 @@ colspan = Number( cell.colSpan ); // Skip the spots in the exploded matrix that are already filled - while ( exploded[rowIndex] && exploded[rowIndex][columnIndex] !== undefined ) { + while ( exploded[ rowIndex ] && exploded[ rowIndex ][ columnIndex ] !== undefined ) { ++columnIndex; } @@ -316,10 +343,10 @@ // in the exploded matrix rowspan times colspan times, with the proper offsets for ( matrixColumnIndex = columnIndex; matrixColumnIndex < columnIndex + colspan; ++matrixColumnIndex ) { for ( matrixRowIndex = rowIndex; matrixRowIndex < rowIndex + rowspan; ++matrixRowIndex ) { - if ( !exploded[matrixRowIndex] ) { - exploded[matrixRowIndex] = []; + if ( !exploded[ matrixRowIndex ] ) { + exploded[ matrixRowIndex ] = []; } - exploded[matrixRowIndex][matrixColumnIndex] = cell; + exploded[ matrixRowIndex ][ matrixColumnIndex ] = cell; } } } ); @@ -333,49 +360,65 @@ } } ); // We cannot use $.unique() here because it sorts into dom order, which is undesirable - $tableHeaders = $( uniqueElements( exploded[longestTR] ) ).filter( 'th' ); + $tableHeaders = $( uniqueElements( exploded[ longestTR ] ) ).filter( 'th' ); } // as each header can span over multiple columns (using colspan=N), // we have to bidirectionally map headers to their columns and columns to their headers - $tableHeaders.each( function ( headerIndex ) { + config.columnToHeader = []; + config.headerToColumns = []; + config.headerList = []; + headerIndex = 0; + $tableHeaders.each( function () { $cell = $( this ); columns = []; - for ( i = 0; i < this.colSpan; i++ ) { - config.columnToHeader[ colspanOffset + i ] = headerIndex; - columns.push( colspanOffset + i ); - } - - config.headerToColumns[ headerIndex ] = columns; - colspanOffset += this.colSpan; - - $cell.data( { - headerIndex: headerIndex, - order: 0, - count: 0 - } ); - - if ( $cell.hasClass( config.unsortableClass ) ) { - $cell.data( 'sortDisabled', true ); - } - - if ( !$cell.data( 'sortDisabled' ) ) { + if ( !$cell.hasClass( config.unsortableClass ) ) { $cell .addClass( config.cssHeader ) .prop( 'tabIndex', 0 ) .attr( { role: 'columnheader button', - title: msg[1] + title: msg[ 1 ] } ); + + for ( k = 0; k < this.colSpan; k++ ) { + config.columnToHeader[ colspanOffset + k ] = headerIndex; + columns.push( colspanOffset + k ); + } + + config.headerToColumns[ headerIndex ] = columns; + + $cell.data( { + headerIndex: headerIndex, + order: 0, + count: 0 + } ); + + // add only sortable cells to headerList + config.headerList[ headerIndex ] = this; + headerIndex++; } - // add cell to headerList - config.headerList[headerIndex] = this; + colspanOffset += this.colSpan; } ); - return $tableHeaders; + // number of columns with extended colspan, inclusive unsortable + // parsers[j], cache[][j], columnToHeader[j], columnToCell[j] have so many elements + config.columns = colspanOffset; + + return $tableHeaders.not( '.' + config.unsortableClass ); + } + function isValueInArray( v, a ) { + var i, + len = a.length; + for ( i = 0; i < len; i++ ) { + if ( a[ i ][ 0 ] === v ) { + return true; + } + } + return false; } /** @@ -391,7 +434,7 @@ $.each( headerToColumns, function ( headerIndex, columns ) { $.each( columns, function ( i, columnIndex ) { - var header = $headers[headerIndex], + var header = $headers[ headerIndex ], $header = $( header ); if ( !isValueInArray( columnIndex, sortList ) ) { @@ -403,10 +446,10 @@ } else { // Column shall be sorted: Apply designated count and order. $.each( sortList, function ( j, sortColumn ) { - if ( sortColumn[0] === i ) { + if ( sortColumn[ 0 ] === i ) { $header.data( { - order: sortColumn[1], - count: sortColumn[1] + 1 + order: sortColumn[ 1 ], + count: sortColumn[ 1 ] + 1 } ); return false; } @@ -417,35 +460,14 @@ } ); } - function isValueInArray( v, a ) { - var i, - len = a.length; - for ( i = 0; i < len; i++ ) { - if ( a[i][0] === v ) { - return true; - } - } - return false; - } - - function uniqueElements( array ) { - var uniques = []; - $.each( array, function ( index, elem ) { - if ( elem !== undefined && $.inArray( elem, uniques ) === -1 ) { - uniques.push( elem ); - } - } ); - return uniques; - } - function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) { // Remove all header information and reset titles to default message - $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] ); + $headers.removeClass( css[ 0 ] ).removeClass( css[ 1 ] ).attr( 'title', msg[ 1 ] ); for ( var i = 0; i < list.length; i++ ) { - $headers.eq( columnToHeader[ list[i][0] ] ) - .addClass( css[ list[i][1] ] ) - .attr( 'title', msg[ list[i][1] ] ); + $headers.eq( columnToHeader[ list[ i ][ 0 ] ] ) + .addClass( css[ list[ i ][ 1 ] ] ) + .attr( 'title', msg[ list[ i ][ 1 ] ] ); } } @@ -462,19 +484,19 @@ sortFn = [], len = sortList.length; for ( i = 0; i < len; i++ ) { - sortFn[i] = ( sortList[i][1] ) ? sortTextDesc : sortText; + sortFn[ i ] = ( sortList[ i ][ 1 ] ) ? sortTextDesc : sortText; } cache.normalized.sort( function ( array1, array2 ) { var i, col, ret; for ( i = 0; i < len; i++ ) { - col = sortList[i][0]; - ret = sortFn[i].call( this, array1[col], array2[col] ); + col = sortList[ i ][ 0 ]; + ret = sortFn[ i ].call( this, array1[ col ], array2[ col ] ); if ( ret !== 0 ) { return ret; } } // Fall back to index number column to ensure stable sort - return sortText.call( this, array1[array1.length - 1], array2[array2.length - 1] ); + return sortText.call( this, array1[ array1.length - 1 ], array2[ array2.length - 1 ] ); } ); return cache; } @@ -485,19 +507,19 @@ separatorTransformTable = mw.config.get( 'wgSeparatorTransformTable' ), digitTransformTable = mw.config.get( 'wgDigitTransformTable' ); - if ( separatorTransformTable === null || ( separatorTransformTable[0] === '' && digitTransformTable[2] === '' ) ) { + if ( separatorTransformTable === null || ( separatorTransformTable[ 0 ] === '' && digitTransformTable[ 2 ] === '' ) ) { ts.transformTable = false; } else { ts.transformTable = {}; // Unpack the transform table - ascii = separatorTransformTable[0].split( '\t' ).concat( digitTransformTable[0].split( '\t' ) ); - localised = separatorTransformTable[1].split( '\t' ).concat( digitTransformTable[1].split( '\t' ) ); + ascii = separatorTransformTable[ 0 ].split( '\t' ).concat( digitTransformTable[ 0 ].split( '\t' ) ); + localised = separatorTransformTable[ 1 ].split( '\t' ).concat( digitTransformTable[ 1 ].split( '\t' ) ); // Construct regex for number identification for ( i = 0; i < ascii.length; i++ ) { - ts.transformTable[localised[i]] = ascii[i]; - digits.push( $.escapeRE( localised[i] ) ); + ts.transformTable[ localised[ i ] ] = ascii[ i ]; + digits.push( mw.RegExp.escape( localised[ i ] ) ); } } digitClass = '[' + digits.join( '', digits ) + ']'; @@ -516,15 +538,15 @@ ts.monthNames = {}; for ( i = 0; i < 12; i++ ) { - name = mw.language.months.names[i].toLowerCase(); - ts.monthNames[name] = i + 1; - regex.push( $.escapeRE( name ) ); - name = mw.language.months.genitive[i].toLowerCase(); - ts.monthNames[name] = i + 1; - regex.push( $.escapeRE( name ) ); - name = mw.language.months.abbrev[i].toLowerCase().replace( '.', '' ); - ts.monthNames[name] = i + 1; - regex.push( $.escapeRE( name ) ); + name = mw.language.months.names[ i ].toLowerCase(); + ts.monthNames[ name ] = i + 1; + regex.push( mw.RegExp.escape( name ) ); + name = mw.language.months.genitive[ i ].toLowerCase(); + ts.monthNames[ name ] = i + 1; + regex.push( mw.RegExp.escape( name ) ); + name = mw.language.months.abbrev[ i ].toLowerCase().replace( '.', '' ); + ts.monthNames[ name ] = i + 1; + regex.push( mw.RegExp.escape( name ) ); } // Build piped string @@ -532,13 +554,13 @@ // Build RegEx // Any date formated with . , ' - or / - ts.dateRegex[0] = new RegExp( /^\s*(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{2,4})\s*?/i ); + ts.dateRegex[ 0 ] = new RegExp( /^\s*(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{2,4})\s*?/i ); // Written Month name, dmy - ts.dateRegex[1] = new RegExp( '^\\s*(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); + ts.dateRegex[ 1 ] = new RegExp( '^\\s*(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); // Written Month name, mdy - ts.dateRegex[2] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); + ts.dateRegex[ 2 ] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); } @@ -546,7 +568,7 @@ * Replace all rowspanned cells in the body with clones in each row, so sorting * need not worry about them. * - * @param $table jQuery object for a <table> + * @param {jQuery} $table jQuery object for a <table> */ function explodeRowspans( $table ) { var spanningRealCellIndex, rowSpan, colSpan, @@ -566,11 +588,11 @@ col = 0, l = this.cells.length; for ( i = 0; i < l; i++ ) { - $( this.cells[i] ).data( 'tablesorter', { + $( this.cells[ i ] ).data( 'tablesorter', { realCellIndex: col, realRowIndex: this.rowIndex } ); - col += this.cells[i].colSpan; + col += this.cells[ i ].colSpan; } } ); @@ -609,7 +631,7 @@ } while ( rowspanCells.length ) { - if ( $.data( rowspanCells[0], 'tablesorter' ).needResort ) { + if ( $.data( rowspanCells[ 0 ], 'tablesorter' ).needResort ) { resortCells(); } @@ -621,7 +643,7 @@ cell.rowSpan = 1; $nextRows = $( cell ).parent().nextAll(); for ( i = 0; i < rowSpan - 1; i++ ) { - $tds = $( $nextRows[i].cells ).filter( filterfunc ); + $tds = $( $nextRows[ i ].cells ).filter( filterfunc ); $clone = $( cell ).clone(); $clone.data( 'tablesorter', { realCellIndex: spanningRealCellIndex, @@ -638,6 +660,49 @@ } } + /** + * Build index to handle colspanned cells in the body. + * Set the cell index for each column in an array, + * so that colspaned cells set multiple in this array. + * columnToCell[collumnIndex] point at the real cell in this row. + * + * @param {jQuery} $table object for a <table> + */ + function manageColspans( $table ) { + var i, j, k, $row, + $rows = $table.find( '> tbody > tr' ), + totalRows = $rows.length || 0, + config = $table.data( 'tablesorter' ).config, + columns = config.columns, + columnToCell, cellsInRow, index; + + for ( i = 0; i < totalRows; i++ ) { + + $row = $rows.eq( i ); + // if this is a child row, continue to the next row (as buildCache()) + if ( $row.hasClass( config.cssChildRow ) ) { + // go to the next for loop + continue; + } + + columnToCell = []; + cellsInRow = ( $row[ 0 ].cells.length ) || 0; // all cells in this row + index = 0; // real cell index in this row + for ( j = 0; j < columns; index++ ) { + if ( index === cellsInRow ) { + // Row with cells less than columns: add empty cell + $row.append( '<td>' ); + cellsInRow++; + } + for ( k = 0; k < $row[ 0 ].cells[ index ].colSpan; k++ ) { + columnToCell[ j++ ] = index; + } + } + // Store it in $row + $row.data( 'columnToCell', columnToCell ); + } + } + function buildCollationTable() { ts.collationTable = mw.config.get( 'tableSorterCollation' ); ts.collationRegex = null; @@ -699,7 +764,7 @@ $.each( sortObjects, function ( i, sortObject ) { $.each( sortObject, function ( columnIndex, order ) { var orderIndex = ( order === 'desc' ) ? 1 : 0; - sortList.push( [parseInt( columnIndex, 10 ), orderIndex] ); + sortList.push( [ parseInt( columnIndex, 10 ), orderIndex ] ); } ); } ); return sortList; @@ -708,7 +773,6 @@ /* Public scope */ $.tablesorter = { - defaultOptions: { cssHeader: 'headerSort', cssAsc: 'headerSortUp', @@ -716,20 +780,21 @@ cssChildRow: 'expand-child', sortMultiSortKey: 'shiftKey', unsortableClass: 'unsortable', - parsers: {}, + parsers: [], cancelSelection: true, sortList: [], headerList: [], headerToColumns: [], - columnToHeader: [] + columnToHeader: [], + columns: 0 }, dateRegex: [], monthNames: {}, /** - * @param $tables {jQuery} - * @param settings {Object} (optional) + * @param {jQuery} $tables + * @param {Object} [settings] */ construct: function ( $tables, settings ) { return $tables.each( function ( i, table ) { @@ -799,6 +864,7 @@ } explodeRowspans( $table ); + manageColspans( $table ); // Try to auto detect column type, and store in tables config config.parsers = buildParserCache( table, $headers ); @@ -806,7 +872,7 @@ // Apply event handling to headers // this is too big, perhaps break it out? - $headers.not( '.' + config.unsortableClass ).on( 'keypress click', function ( e ) { + $headers.on( 'keypress click', function ( e ) { var cell, $cell, columns, newSortList, i, totalRows, j, s, o; @@ -834,8 +900,8 @@ // 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 ( !table.sortDisabled && totalRows > 0 ) { + totalRows = ( $table[ 0 ].tBodies[ 0 ] && $table[ 0 ].tBodies[ 0 ].rows.length ) || 0; + if ( totalRows > 0 ) { cell = this; $cell = $( cell ); @@ -850,12 +916,12 @@ columns = config.headerToColumns[ $cell.data( 'headerIndex' ) ]; newSortList = $.map( columns, function ( c ) { // jQuery "helpfully" flattens the arrays... - return [[c, $cell.data( 'order' )]]; + return [ [ c, $cell.data( 'order' ) ] ]; } ); // Index of first column belonging to this header - i = columns[0]; + i = columns[ 0 ]; - if ( !e[config.sortMultiSortKey] ) { + if ( !e[ config.sortMultiSortKey ] ) { // User only wants to sort on one column set // Flush the sort list and add new columns config.sortList = newSortList; @@ -867,11 +933,11 @@ // 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[s[0]]; - if ( isValueInArray( s[0], newSortList ) ) { - $( o ).data( 'count', s[1] + 1 ); - s[1] = $( o ).data( 'count' ) % 2; + 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 { @@ -884,9 +950,9 @@ setHeadersOrder( $headers, config.sortList, config.headerToColumns ); // Set CSS for headers - setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader ); + setHeadersCss( $table[ 0 ], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader ); appendToTable( - $table[0], multisort( $table[0], config.sortList, cache ) + $table[ 0 ], multisort( $table[ 0 ], config.sortList, cache ) ); // Stop normal event by returning false @@ -909,7 +975,7 @@ * Passing an empty array will reset sorting (basically just reset the headers * making the table appear unsorted). * - * @param sortList {Array} (optional) List of sort objects. + * @param {Array} [sortList] List of sort objects. */ $table.data( 'tablesorter' ).sort = function ( sortList ) { @@ -939,7 +1005,6 @@ // sort initially if ( config.sortList.length > 0 ) { - setupForFirstSort(); config.sortList = convertSortList( config.sortList ); $table.data( 'tablesorter' ).sort(); } @@ -952,7 +1017,7 @@ len = parsers.length, a = true; for ( i = 0; i < len; i++ ) { - if ( parsers[i].id.toLowerCase() === parser.id.toLowerCase() ) { + if ( parsers[ i ].id.toLowerCase() === parser.id.toLowerCase() ) { a = false; } } @@ -968,7 +1033,7 @@ for ( p = 0; p < s.length; p++ ) { c = s.charAt( p ); if ( c in ts.transformTable ) { - out += ts.transformTable[c]; + out += ts.transformTable[ c ]; } else { out += c; } @@ -990,7 +1055,7 @@ }, clearTableBody: function ( table ) { - $( table.tBodies[0] ).empty(); + $( table.tBodies[ 0 ] ).empty(); }, getParser: function ( id ) { @@ -1000,6 +1065,10 @@ buildCollationTable(); return getParserById( id ); + }, + + getParsers: function () { // for table diagnosis + return parsers; } }; @@ -1022,7 +1091,7 @@ if ( ts.collationRegex ) { var tsc = ts.collationTable; s = s.replace( ts.collationRegex, function ( match ) { - var r = tsc[match] ? tsc[match] : tsc[match.toUpperCase()]; + var r = tsc[ match ] ? tsc[ match ] : tsc[ match.toUpperCase() ]; return r.toLowerCase(); } ); } @@ -1034,7 +1103,7 @@ ts.addParser( { id: 'IPAddress', is: function ( s ) { - return ts.rgx.IPAddress[0].test( s ); + return ts.rgx.IPAddress[ 0 ].test( s ); }, format: function ( s ) { var i, item, @@ -1042,7 +1111,7 @@ r = '', len = a.length; for ( i = 0; i < len; i++ ) { - item = a[i]; + item = a[ i ]; if ( item.length === 1 ) { r += '00' + item; } else if ( item.length === 2 ) { @@ -1059,10 +1128,10 @@ ts.addParser( { id: 'currency', is: function ( s ) { - return ts.rgx.currency[0].test( s ); + return ts.rgx.currency[ 0 ].test( s ); }, format: function ( s ) { - return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[1], '' ) ); + return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[ 1 ], '' ) ); }, type: 'numeric' } ); @@ -1070,10 +1139,10 @@ ts.addParser( { id: 'url', is: function ( s ) { - return ts.rgx.url[0].test( s ); + return ts.rgx.url[ 0 ].test( s ); }, format: function ( s ) { - return $.trim( s.replace( ts.rgx.url[1], '' ) ); + return $.trim( s.replace( ts.rgx.url[ 1 ], '' ) ); }, type: 'text' } ); @@ -1081,7 +1150,7 @@ ts.addParser( { id: 'isoDate', is: function ( s ) { - return ts.rgx.isoDate[0].test( s ); + return ts.rgx.isoDate[ 0 ].test( s ); }, format: function ( s ) { return $.tablesorter.formatFloat( ( s !== '' ) ? new Date( s.replace( @@ -1093,7 +1162,7 @@ ts.addParser( { id: 'usLongDate', is: function ( s ) { - return ts.rgx.usLongDate[0].test( s ); + return ts.rgx.usLongDate[ 0 ].test( s ); }, format: function ( s ) { return $.tablesorter.formatFloat( new Date( s ).getTime() ); @@ -1104,49 +1173,49 @@ ts.addParser( { id: 'date', is: function ( s ) { - return ( ts.dateRegex[0].test( s ) || ts.dateRegex[1].test( s ) || ts.dateRegex[2].test( s ) ); + return ( ts.dateRegex[ 0 ].test( s ) || ts.dateRegex[ 1 ].test( s ) || ts.dateRegex[ 2 ].test( s ) ); }, format: function ( s ) { var match, y; s = $.trim( s.toLowerCase() ); - if ( ( match = s.match( ts.dateRegex[0] ) ) !== null ) { - if ( mw.config.get( 'wgDefaultDateFormat' ) === 'mdy' || mw.config.get( 'wgContentLanguage' ) === 'en' ) { - s = [ match[3], match[1], match[2] ]; + if ( ( match = s.match( ts.dateRegex[ 0 ] ) ) !== null ) { + if ( mw.config.get( 'wgDefaultDateFormat' ) === 'mdy' || mw.config.get( 'wgPageContentLanguage' ) === 'en' ) { + s = [ match[ 3 ], match[ 1 ], match[ 2 ] ]; } else if ( mw.config.get( 'wgDefaultDateFormat' ) === 'dmy' ) { - s = [ match[3], match[2], match[1] ]; + s = [ match[ 3 ], match[ 2 ], match[ 1 ] ]; } else { // If we get here, we don't know which order the dd-dd-dddd // date is in. So return something not entirely invalid. return '99999999'; } - } else if ( ( match = s.match( ts.dateRegex[1] ) ) !== null ) { - s = [ match[3], String( ts.monthNames[match[2]] ), match[1] ]; - } else if ( ( match = s.match( ts.dateRegex[2] ) ) !== null ) { - s = [ match[3], String( ts.monthNames[match[1]] ), match[2] ]; + } else if ( ( match = s.match( ts.dateRegex[ 1 ] ) ) !== null ) { + s = [ match[ 3 ], String( ts.monthNames[ match[ 2 ] ] ), match[ 1 ] ]; + } else if ( ( match = s.match( ts.dateRegex[ 2 ] ) ) !== null ) { + s = [ match[ 3 ], String( ts.monthNames[ match[ 1 ] ] ), match[ 2 ] ]; } else { // Should never get here return '99999999'; } // Pad Month and Day - if ( s[1].length === 1 ) { - s[1] = '0' + s[1]; + if ( s[ 1 ].length === 1 ) { + s[ 1 ] = '0' + s[ 1 ]; } - if ( s[2].length === 1 ) { - s[2] = '0' + s[2]; + if ( s[ 2 ].length === 1 ) { + s[ 2 ] = '0' + s[ 2 ]; } - if ( ( y = parseInt( s[0], 10 ) ) < 100 ) { + if ( ( y = parseInt( s[ 0 ], 10 ) ) < 100 ) { // Guestimate years without centuries if ( y < 30 ) { - s[0] = 2000 + y; + s[ 0 ] = 2000 + y; } else { - s[0] = 1900 + y; + s[ 0 ] = 1900 + y; } } - while ( s[0].length < 4 ) { - s[0] = '0' + s[0]; + while ( s[ 0 ].length < 4 ) { + s[ 0 ] = '0' + s[ 0 ]; } return parseInt( s.join( '' ), 10 ); }, @@ -1156,7 +1225,7 @@ ts.addParser( { id: 'time', is: function ( s ) { - return ts.rgx.time[0].test( s ); + return ts.rgx.time[ 0 ].test( s ); }, format: function ( s ) { return $.tablesorter.formatFloat( new Date( '2000/01/01 ' + s ).getTime() ); diff --git a/resources/src/jquery/jquery.textSelection.js b/resources/src/jquery/jquery.textSelection.js index 51119305..b9016424 100644 --- a/resources/src/jquery/jquery.textSelection.js +++ b/resources/src/jquery/jquery.textSelection.js @@ -138,7 +138,7 @@ insertText = '', selTextArr = selText.split( '\n' ); for ( i = 0; i < selTextArr.length; i++ ) { - insertText += pre + selTextArr[i] + post; + insertText += pre + selTextArr[ i ] + post; if ( i !== selTextArr.length - 1 ) { insertText += '\n'; } @@ -160,7 +160,7 @@ context.fn.restoreCursorAndScrollTop(); } if ( options.selectionStart !== undefined ) { - $( this ).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); + $( this ).textSelection( 'setSelection', { start: options.selectionStart, end: options.selectionEnd } ); } selText = $( this ).textSelection( 'getSelection' ); @@ -203,7 +203,7 @@ $( this ).focus(); if ( options.selectionStart !== undefined ) { - $( this ).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); + $( this ).textSelection( 'setSelection', { start: options.selectionStart, end: options.selectionEnd } ); } selText = $( this ).textSelection( 'getSelection' ); @@ -411,7 +411,8 @@ * * Scroll a textarea to the current cursor position. You can set the cursor * position with setSelection() - * @param options boolean Whether to force a scroll even if the caret position + * + * @param {boolean} options Whether to force a scroll even if the caret position * is already visible. Defaults to false * * @fixme document the options parameters (function body suggests options.force is a boolean, not options itself) @@ -576,7 +577,7 @@ context.fn.restoreSelection(); needSave = true; } - retval = ( alternateFn && alternateFn[command] || fn[command] ).call( this, options ); + retval = ( alternateFn && alternateFn[ command ] || fn[ command ] ).call( this, options ); if ( hasWikiEditor && needSave ) { context.fn.saveSelection(); } |