From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- .../images/jquery.arrowSteps.divider-ltr.png | Bin 0 -> 126 bytes .../images/jquery.arrowSteps.divider-rtl.png | Bin 0 -> 127 bytes .../jquery/images/jquery.arrowSteps.head-ltr.png | Bin 0 -> 303 bytes .../jquery/images/jquery.arrowSteps.head-rtl.png | Bin 0 -> 311 bytes .../jquery/images/jquery.arrowSteps.tail-ltr.png | Bin 0 -> 222 bytes .../jquery/images/jquery.arrowSteps.tail-rtl.png | Bin 0 -> 219 bytes resources/src/jquery/images/marker.png | Bin 0 -> 472 bytes resources/src/jquery/images/mask.png | Bin 0 -> 1795 bytes resources/src/jquery/images/sort_both.gif | Bin 0 -> 1184 bytes resources/src/jquery/images/sort_down.gif | Bin 0 -> 1174 bytes resources/src/jquery/images/sort_none.gif | Bin 0 -> 462 bytes resources/src/jquery/images/sort_up.gif | Bin 0 -> 1174 bytes resources/src/jquery/images/spinner-large.gif | Bin 0 -> 1788 bytes resources/src/jquery/images/spinner.gif | Bin 0 -> 1819 bytes resources/src/jquery/images/wheel.png | Bin 0 -> 11505 bytes resources/src/jquery/jquery.accessKeyLabel.js | 200 ++++ resources/src/jquery/jquery.arrowSteps.css | 45 + resources/src/jquery/jquery.arrowSteps.js | 98 ++ resources/src/jquery/jquery.autoEllipsis.js | 168 +++ resources/src/jquery/jquery.badge.css | 36 + resources/src/jquery/jquery.badge.js | 88 ++ resources/src/jquery/jquery.byteLength.js | 40 + resources/src/jquery/jquery.byteLimit.js | 235 ++++ resources/src/jquery/jquery.checkboxShiftClick.js | 43 + resources/src/jquery/jquery.client.js | 301 +++++ resources/src/jquery/jquery.color.js | 55 + resources/src/jquery/jquery.colorUtil.js | 262 +++++ resources/src/jquery/jquery.confirmable.css | 28 + resources/src/jquery/jquery.confirmable.js | 170 +++ .../src/jquery/jquery.confirmable.mediawiki.js | 14 + resources/src/jquery/jquery.expandableField.js | 140 +++ resources/src/jquery/jquery.farbtastic.css | 54 + resources/src/jquery/jquery.farbtastic.js | 286 +++++ resources/src/jquery/jquery.footHovzer.css | 6 + resources/src/jquery/jquery.footHovzer.js | 66 ++ resources/src/jquery/jquery.getAttrs.js | 42 + resources/src/jquery/jquery.hidpi.js | 129 +++ resources/src/jquery/jquery.highlightText.js | 73 ++ resources/src/jquery/jquery.localize.js | 170 +++ resources/src/jquery/jquery.makeCollapsible.css | 27 + resources/src/jquery/jquery.makeCollapsible.js | 404 +++++++ resources/src/jquery/jquery.mw-jump.js | 15 + resources/src/jquery/jquery.mwExtension.js | 122 ++ resources/src/jquery/jquery.placeholder.js | 229 ++++ .../src/jquery/jquery.qunit.completenessTest.js | 305 +++++ resources/src/jquery/jquery.spinner.css | 40 + resources/src/jquery/jquery.spinner.js | 112 ++ resources/src/jquery/jquery.suggestions.css | 76 ++ resources/src/jquery/jquery.suggestions.js | 684 ++++++++++++ resources/src/jquery/jquery.tabIndex.js | 57 + resources/src/jquery/jquery.tablesorter.css | 17 + resources/src/jquery/jquery.tablesorter.js | 1161 ++++++++++++++++++++ resources/src/jquery/jquery.textSelection.js | 572 ++++++++++ 53 files changed, 6570 insertions(+) create mode 100644 resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png create mode 100644 resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png create mode 100644 resources/src/jquery/images/jquery.arrowSteps.head-ltr.png create mode 100644 resources/src/jquery/images/jquery.arrowSteps.head-rtl.png create mode 100644 resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png create mode 100644 resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png create mode 100644 resources/src/jquery/images/marker.png create mode 100644 resources/src/jquery/images/mask.png create mode 100644 resources/src/jquery/images/sort_both.gif create mode 100644 resources/src/jquery/images/sort_down.gif create mode 100644 resources/src/jquery/images/sort_none.gif create mode 100644 resources/src/jquery/images/sort_up.gif create mode 100644 resources/src/jquery/images/spinner-large.gif create mode 100644 resources/src/jquery/images/spinner.gif create mode 100644 resources/src/jquery/images/wheel.png create mode 100644 resources/src/jquery/jquery.accessKeyLabel.js create mode 100644 resources/src/jquery/jquery.arrowSteps.css create mode 100644 resources/src/jquery/jquery.arrowSteps.js create mode 100644 resources/src/jquery/jquery.autoEllipsis.js create mode 100644 resources/src/jquery/jquery.badge.css create mode 100644 resources/src/jquery/jquery.badge.js create mode 100644 resources/src/jquery/jquery.byteLength.js create mode 100644 resources/src/jquery/jquery.byteLimit.js create mode 100644 resources/src/jquery/jquery.checkboxShiftClick.js create mode 100644 resources/src/jquery/jquery.client.js create mode 100644 resources/src/jquery/jquery.color.js create mode 100644 resources/src/jquery/jquery.colorUtil.js create mode 100644 resources/src/jquery/jquery.confirmable.css create mode 100644 resources/src/jquery/jquery.confirmable.js create mode 100644 resources/src/jquery/jquery.confirmable.mediawiki.js create mode 100644 resources/src/jquery/jquery.expandableField.js create mode 100644 resources/src/jquery/jquery.farbtastic.css create mode 100644 resources/src/jquery/jquery.farbtastic.js create mode 100644 resources/src/jquery/jquery.footHovzer.css create mode 100644 resources/src/jquery/jquery.footHovzer.js create mode 100644 resources/src/jquery/jquery.getAttrs.js create mode 100644 resources/src/jquery/jquery.hidpi.js create mode 100644 resources/src/jquery/jquery.highlightText.js create mode 100644 resources/src/jquery/jquery.localize.js create mode 100644 resources/src/jquery/jquery.makeCollapsible.css create mode 100644 resources/src/jquery/jquery.makeCollapsible.js create mode 100644 resources/src/jquery/jquery.mw-jump.js create mode 100644 resources/src/jquery/jquery.mwExtension.js create mode 100644 resources/src/jquery/jquery.placeholder.js create mode 100644 resources/src/jquery/jquery.qunit.completenessTest.js create mode 100644 resources/src/jquery/jquery.spinner.css create mode 100644 resources/src/jquery/jquery.spinner.js create mode 100644 resources/src/jquery/jquery.suggestions.css create mode 100644 resources/src/jquery/jquery.suggestions.js create mode 100644 resources/src/jquery/jquery.tabIndex.js create mode 100644 resources/src/jquery/jquery.tablesorter.css create mode 100644 resources/src/jquery/jquery.tablesorter.js create mode 100644 resources/src/jquery/jquery.textSelection.js (limited to 'resources/src/jquery') diff --git a/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png new file mode 100644 index 00000000..84ed2a2d Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png differ diff --git a/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png new file mode 100644 index 00000000..7cfbfeba Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png differ diff --git a/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png new file mode 100644 index 00000000..eb070280 Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png differ diff --git a/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png new file mode 100644 index 00000000..7ea2fdb5 Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png differ diff --git a/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png new file mode 100644 index 00000000..3ad990b6 Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png differ diff --git a/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png new file mode 100644 index 00000000..1d3048ef Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png differ diff --git a/resources/src/jquery/images/marker.png b/resources/src/jquery/images/marker.png new file mode 100644 index 00000000..19efb6ce Binary files /dev/null and b/resources/src/jquery/images/marker.png differ diff --git a/resources/src/jquery/images/mask.png b/resources/src/jquery/images/mask.png new file mode 100644 index 00000000..fe08de0e Binary files /dev/null and b/resources/src/jquery/images/mask.png differ diff --git a/resources/src/jquery/images/sort_both.gif b/resources/src/jquery/images/sort_both.gif new file mode 100644 index 00000000..50ad15a0 Binary files /dev/null and b/resources/src/jquery/images/sort_both.gif differ diff --git a/resources/src/jquery/images/sort_down.gif b/resources/src/jquery/images/sort_down.gif new file mode 100644 index 00000000..ec4f41b0 Binary files /dev/null and b/resources/src/jquery/images/sort_down.gif differ diff --git a/resources/src/jquery/images/sort_none.gif b/resources/src/jquery/images/sort_none.gif new file mode 100644 index 00000000..edd07e58 Binary files /dev/null and b/resources/src/jquery/images/sort_none.gif differ diff --git a/resources/src/jquery/images/sort_up.gif b/resources/src/jquery/images/sort_up.gif new file mode 100644 index 00000000..80189185 Binary files /dev/null and b/resources/src/jquery/images/sort_up.gif differ diff --git a/resources/src/jquery/images/spinner-large.gif b/resources/src/jquery/images/spinner-large.gif new file mode 100644 index 00000000..72203fdd Binary files /dev/null and b/resources/src/jquery/images/spinner-large.gif differ diff --git a/resources/src/jquery/images/spinner.gif b/resources/src/jquery/images/spinner.gif new file mode 100644 index 00000000..6146be4e Binary files /dev/null and b/resources/src/jquery/images/spinner.gif differ diff --git a/resources/src/jquery/images/wheel.png b/resources/src/jquery/images/wheel.png new file mode 100644 index 00000000..7e53103e Binary files /dev/null and b/resources/src/jquery/images/wheel.png differ diff --git a/resources/src/jquery/jquery.accessKeyLabel.js b/resources/src/jquery/jquery.accessKeyLabel.js new file mode 100644 index 00000000..7b49cb2d --- /dev/null +++ b/resources/src/jquery/jquery.accessKeyLabel.js @@ -0,0 +1,200 @@ +/** + * jQuery plugin to update the tooltip to show the correct access key + * + * @class jQuery.plugin.accessKeyLabel + */ +( function ( $, mw ) { + +// Cached access key prefix for used browser +var cachedAccessKeyPrefix, + + // Wether 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'; + +/** + * Get the prefix for the access key for browsers that don't support accessKeyLabel. + * + * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here. + * + * @private + * @param {Object} [ua] An object with a 'userAgent' and 'platform' property. + * @return {string} Access key prefix + */ +function getAccessKeyPrefix( ua ) { + // use cached prefix if possible + if ( !ua && cachedAccessKeyPrefix ) { + return cachedAccessKeyPrefix; + } + + var profile = $.client.profile( ua ), + accessKeyPrefix = 'alt-'; + + // Opera on any platform + if ( profile.name === 'opera' ) { + accessKeyPrefix = 'shift-esc-'; + + // Chrome on any platform + } else if ( profile.name === 'chrome' ) { + accessKeyPrefix = ( + 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 + ) { + accessKeyPrefix = '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' ) + ) { + accessKeyPrefix = 'ctrl-'; + + // Firefox/Iceweasel 2.x and later + } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' ) + && profile.versionBase > '1' + ) { + accessKeyPrefix = 'alt-shift-'; + } + + // cache prefix + if ( !ua ) { + cachedAccessKeyPrefix = accessKeyPrefix; + } + return accessKeyPrefix; +} + +/** + * Get the access key label for an element. + * + * Will use native accessKeyLabel if available (currently only in Firefox 8+), + * falls back to #getAccessKeyPrefix. + * + * @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 + // http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#dom-accesskeylabel + if ( !useTestPrefix && element.accessKeyLabel ) { + return element.accessKeyLabel; + } + return ( useTestPrefix ? 'test-' : getAccessKeyPrefix() ) + 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 array = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ), + regexp = new RegExp( $.map( array, $.escapeRE ).join( '.*?' ) + '$' ), + oldTitle = titleElement.title, + rawTitle = oldTitle.replace( regexp, '' ), + newTitle = rawTitle, + accessKeyLabel = getAccessKeyLabel( element ); + + // don't add a title if the element didn't have one before + if ( !oldTitle ) { + return; + } + + 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] ); + } + } + + // 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 ); + } ); +}; + +/** + * Exposed for testing. + * + * @method updateTooltipAccessKeys_getAccessKeyPrefix + * @inheritdoc #getAccessKeyPrefix + */ +$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = getAccessKeyPrefix; + +/** + * 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 ) ); diff --git a/resources/src/jquery/jquery.arrowSteps.css b/resources/src/jquery/jquery.arrowSteps.css new file mode 100644 index 00000000..f8f6e951 --- /dev/null +++ b/resources/src/jquery/jquery.arrowSteps.css @@ -0,0 +1,45 @@ +.arrowSteps { + list-style-type: none; + list-style-image: none; + border: 1px solid #666666; + position: relative; +} + +.arrowSteps li { + float: left; + padding: 0px; + margin: 0px; + border: 0 none; +} + +.arrowSteps li div { + padding: 0.5em; + text-align: center; + white-space: nowrap; + overflow: hidden; +} + +.arrowSteps li.arrow div { + /* @embed */ + background: url(images/jquery.arrowSteps.divider-ltr.png) no-repeat right center; +} + +/* applied to the element preceding the highlighted step */ +.arrowSteps li.arrow.tail div { + /* @embed */ + background: url(images/jquery.arrowSteps.tail-ltr.png) no-repeat right center; +} + +/* this applies to all highlighted, including the last */ +.arrowSteps li.head div { + /* @embed */ + background: url(images/jquery.arrowSteps.head-ltr.png) no-repeat left center; + font-weight: bold; +} + +/* this applies to all highlighted arrows except the last */ +.arrowSteps li.arrow.head div { + /* TODO: eliminate duplication of jquery.arrowSteps.head.png embedding */ + /* @embed */ + background: url(images/jquery.arrowSteps.head-ltr.png) no-repeat right center; +} diff --git a/resources/src/jquery/jquery.arrowSteps.js b/resources/src/jquery/jquery.arrowSteps.js new file mode 100644 index 00000000..f8641e10 --- /dev/null +++ b/resources/src/jquery/jquery.arrowSteps.js @@ -0,0 +1,98 @@ +/*! + * jQuery arrowSteps plugin + * Copyright Neil Kandalgaonkar, 2010 + * + * This work is licensed under the terms of the GNU General Public License, + * version 2 or later. + * (see http://www.fsf.org/licensing/licenses/gpl.html). + * Derivative works and later versions of the code must be free software + * licensed under the same or a compatible license. + */ + +/** + * @class jQuery.plugin.arrowSteps + */ +( function ( $ ) { + /** + * Show users their progress through a series of steps, via a row of items that fit + * together like arrows. One item can be highlighted at a time. + * + * + * + * + * + * @return {jQuery} + * @chainable + */ + $.fn.arrowSteps = function () { + var $steps, width, arrowWidth, $stepDiv, + $el = this, + paddingSide = $( 'body' ).hasClass( 'rtl' ) ? 'padding-left' : 'padding-right'; + + $el.addClass( 'arrowSteps' ); + $steps = $el.find( 'li' ); + + width = parseInt( 100 / $steps.length, 10 ); + $steps.css( 'width', width + '%' ); + + // Every step except the last one has an arrow pointing forward: + // at the right hand side in LTR languages, and at the left hand side in RTL. + // Also add in the padding for the calculated arrow width. + $stepDiv = $steps.filter( ':not(:last-child)' ).addClass( 'arrow' ).find( 'div' ); + + // Execute when complete page is fully loaded, including all frames, objects and images + $( window ).load( function () { + arrowWidth = parseInt( $el.outerHeight(), 10 ); + $stepDiv.css( paddingSide, arrowWidth.toString() + 'px' ); + } ); + + $el.data( 'arrowSteps', $steps ); + + return this; + }; + + /** + * Highlights the element selected by the selector. + * + * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#guard' ); + * // 'Guard!' is highlighted. + * + * // ... user completes the 'guard' step ... + * + * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#turn' ); + * // 'Turn!' is highlighted. + * + * @param {string} selector + */ + $.fn.arrowStepsHighlight = function ( selector ) { + var $previous, + $steps = this.data( 'arrowSteps' ); + $.each( $steps, function ( i, step ) { + var $step = $( step ); + if ( $step.is( selector ) ) { + if ($previous) { + $previous.addClass( 'tail' ); + } + $step.addClass( 'head' ); + } else { + $step.removeClass( 'head tail lasthead' ); + } + $previous = $step; + } ); + }; + + /** + * @class jQuery + * @mixins jQuery.plugin.arrowSteps + */ +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.autoEllipsis.js b/resources/src/jquery/jquery.autoEllipsis.js new file mode 100644 index 00000000..9a196b5d --- /dev/null +++ b/resources/src/jquery/jquery.autoEllipsis.js @@ -0,0 +1,168 @@ +/** + * @class jQuery.plugin.autoEllipsis + */ +( function ( $ ) { + +var + // Cache ellipsed substrings for every string-width-position combination + cache = {}, + + // Use a separate cache when match highlighting is enabled + matchTextCache = {}; + +/** + * Automatically truncate the plain text contents of an element and add an ellipsis + * + * @param {Object} options + * @param {'center'|'left'|'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 ); + + 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 = $( '' ) + .css( 'whiteSpace', 'nowrap' ) + .text( $container.text() ); + $container + .empty() + .append( $trimmableText ); + } + + 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 ); + } + 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; + } + } + + 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; + } + } + 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; + } + } + 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(); + } + + } ); +}; + +/** + * @class jQuery + * @mixins jQuery.plugin.autoEllipsis + */ + +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.badge.css b/resources/src/jquery/jquery.badge.css new file mode 100644 index 00000000..fa7ea702 --- /dev/null +++ b/resources/src/jquery/jquery.badge.css @@ -0,0 +1,36 @@ +.mw-badge { + min-width: 7px; + border-radius: 2px; + padding: 1px 4px; + text-align: center; + font-size: 12px; + line-height: 12px; + background-color: #d2d2d2; + cursor: pointer; +} + +.mw-badge-content { + font-weight: bold; + color: white; + vertical-align: baseline; + text-shadow: 0 1px rgba(0, 0, 0, 0.4); +} + +.mw-badge-inline { + margin-left: 3px; + display: inline-block; + /* Hack for IE6 and IE7 (bug 47926) */ + zoom: 1; + *display: inline; + +} +.mw-badge-overlay { + position: absolute; + bottom: -1px; + right: -3px; + z-index: 50; +} + +.mw-badge-important { + background-color: #cc0000; +} diff --git a/resources/src/jquery/jquery.badge.js b/resources/src/jquery/jquery.badge.js new file mode 100644 index 00000000..023b6e28 --- /dev/null +++ b/resources/src/jquery/jquery.badge.js @@ -0,0 +1,88 @@ +/*! + * jQuery Badge plugin + * + * @license MIT + * + * @author Ryan Kaldari , 2012 + * @author Andrew Garrett , 2012 + * @author Marius Hoch , 2012 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * This program is distributed WITHOUT ANY WARRANTY. + */ + +/** + * @class jQuery.plugin.badge + */ +( function ( $, mw ) { + /** + * Put a badge on an item on the page. The badge container will be appended to the selected element(s). + * + * $element.badge( text ); + * $element.badge( 5 ); + * $element.badge( '100+' ); + * $element.badge( text, inline ); + * $element.badge( 'New', true ); + * + * @param {number|string} text The value to display in the badge. If the value is falsey (0, + * null, false, '', etc.), any existing badge will be removed. + * @param {boolean} [inline=true] True if the badge should be displayed inline, false + * if the badge should overlay the parent element. + * @param {boolean} [displayZero=false] True if the number zero should be displayed, + * false if the number zero should result in the badge being hidden + * @return {jQuery} + * @chainable + */ + $.fn.badge = function ( text, inline, displayZero ) { + var $badge = this.find( '.mw-badge' ), + badgeStyleClass = 'mw-badge-' + ( inline ? 'inline' : 'overlay' ), + isImportant = true, displayBadge = true; + + // If we're displaying zero, ensure style to be non-important + if ( mw.language.convertNumber( text, true ) === 0 ) { + isImportant = false; + if ( !displayZero ) { + displayBadge = false; + } + // If text is falsey (besides 0), hide the badge + } else if ( !text ) { + displayBadge = false; + } + + if ( displayBadge ) { + // If a badge already exists, reuse it + if ( $badge.length ) { + $badge + .toggleClass( 'mw-badge-important', isImportant ) + .find( '.mw-badge-content' ) + .text( text ); + } else { + // Otherwise, create a new badge with the specified text and style + $badge = $( '
' ) + .addClass( badgeStyleClass ) + .toggleClass( 'mw-badge-important', isImportant ) + .append( + $( '' ).text( text ) + ) + .appendTo( this ); + } + } else { + $badge.remove(); + } + return this; + }; + + /** + * @class jQuery + * @mixins jQuery.plugin.badge + */ +}( jQuery, mediaWiki ) ); diff --git a/resources/src/jquery/jquery.byteLength.js b/resources/src/jquery/jquery.byteLength.js new file mode 100644 index 00000000..7fe25ee3 --- /dev/null +++ b/resources/src/jquery/jquery.byteLength.js @@ -0,0 +1,40 @@ +/** + * @class jQuery.plugin.byteLength + * @author Jan Paul Posma, 2011 + * @author Timo Tijhof, 2012 + * @author David Chan, 2013 + */ + +/** + * Calculate the byte length of a string (accounting for UTF-8). + * + * @static + * @inheritable + * @param {string} str + * @return {string} + */ +jQuery.byteLength = function ( str ) { + // This basically figures out how many bytes a UTF-16 string (which is what js sees) + // will take in UTF-8 by replacing a 2 byte character with 2 *'s, etc, and counting that. + // Note, surrogate (\uD800-\uDFFF) characters are counted as 2 bytes, since there's two of them + // and the actual character takes 4 bytes in UTF-8 (2*2=4). Might not work perfectly in + // edge cases such as illegal sequences, but that should never happen. + + // https://en.wikipedia.org/wiki/UTF-8#Description + // The mapping from UTF-16 code units to UTF-8 bytes is as follows: + // > Range 0000-007F: codepoints that become 1 byte of UTF-8 + // > Range 0080-07FF: codepoints that become 2 bytes of UTF-8 + // > Range 0800-D7FF: codepoints that become 3 bytes of UTF-8 + // > Range D800-DFFF: Surrogates (each pair becomes 4 bytes of UTF-8) + // > Range E000-FFFF: codepoints that become 3 bytes of UTF-8 (continued) + + return str + .replace( /[\u0080-\u07FF\uD800-\uDFFF]/g, '**' ) + .replace( /[\u0800-\uD7FF\uE000-\uFFFF]/g, '***' ) + .length; +}; + +/** + * @class jQuery + * @mixins jQuery.plugin.byteLength + */ diff --git a/resources/src/jquery/jquery.byteLimit.js b/resources/src/jquery/jquery.byteLimit.js new file mode 100644 index 00000000..5551232a --- /dev/null +++ b/resources/src/jquery/jquery.byteLimit.js @@ -0,0 +1,235 @@ +/** + * @class jQuery.plugin.byteLimit + */ +( function ( $ ) { + + /** + * Utility function to trim down a string, based on byteLimit + * and given a safe start position. It supports insertion anywhere + * in the string, so "foo" to "fobaro" if limit is 4 will result in + * "fobo", not "foba". Basically emulating the native maxlength by + * reconstructing where the insertion occurred. + * + * @private + * @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. + * @return {Object} + * @return {string} return.newVal + * @return {boolean} return.trimmed + */ + function trimValForByteLength( safeVal, newVal, byteLimit, fn ) { + var startMatches, endMatches, matchesLen, inpParts, + oldVal = safeVal; + + // Run the hook if one was provided, but only on the length + // assessment. The value itself is not to be affected by the hook. + if ( $.byteLength( fn ? fn( newVal ) : newVal ) <= byteLimit ) { + // Limit was not reached, just remember the new value + // and let the user continue. + return { + newVal: newVal, + trimmed: false + }; + } + + // Current input is longer than the active limit. + // Figure out what was added and limit the addition. + startMatches = 0; + endMatches = 0; + + // It is important that we keep the search within the range of + // the shortest string's length. + // Imagine a user adds text that matches the end of the old value + // (e.g. "foo" -> "foofoo"). startMatches would be 3, but without + // limiting both searches to the shortest length, endMatches would + // also be 3. + matchesLen = Math.min( newVal.length, oldVal.length ); + + // Count same characters from the left, first. + // (if "foo" -> "foofoo", assume addition was at the end). + while ( + startMatches < matchesLen && + oldVal.charAt( startMatches ) === newVal.charAt( startMatches ) + ) { + startMatches += 1; + } + + while ( + endMatches < ( matchesLen - startMatches ) && + oldVal.charAt( oldVal.length - 1 - endMatches ) === newVal.charAt( newVal.length - 1 - endMatches ) + ) { + endMatches += 1; + } + + inpParts = [ + // Same start + newVal.slice( 0, startMatches ), + // Inserted content + newVal.slice( startMatches, newVal.length - endMatches ), + // Same end + newVal.slice( newVal.length - endMatches ) + ]; + + // Chop off characters from the end of the "inserted content" string + // 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 ); + } + } else { + while ( $.byteLength( inpParts.join( '' ) ) > byteLimit ) { + inpParts[1] = inpParts[1].slice( 0, -1 ); + } + } + + newVal = inpParts.join( '' ); + + return { + newVal: newVal, + trimmed: true + }; + } + + 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. + * Plugin rationale: Browser has native maxlength for number of characters, this plugin + * exists to limit number of bytes instead. + * + * Can be called with a custom limit (to use that limit instead of the maxlength attribute + * value), a filter function (in case the limit should apply to something other than the + * exact input value), or both. Order of parameters is important! + * + * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute, + * called with fetched value as argument. + * @param {Function} [fn] Function to call on the string before assessing the length. + * @return {jQuery} + * @chainable + */ + $.fn.byteLimit = function ( limit, fn ) { + // If the first argument is the function, + // set fn to the first argument's value and ignore the second argument. + if ( $.isFunction( limit ) ) { + fn = limit; + limit = undefined; + // Either way, verify it is a function so we don't have to call + // isFunction again after this. + } else if ( !fn || !$.isFunction( fn ) ) { + fn = undefined; + } + + // The following is specific to each element in the collection. + return this.each( function ( i, el ) { + var $el, elLimit, prevSafeVal; + + $el = $( el ); + + // If no limit was passed to byteLimit(), use the maxlength value. + // Can't re-use 'limit' variable because it's in the higher scope + // that would affect the next each() iteration as well. + // Note that we use attribute to read the value instead of property, + // because in Chrome the maxLength property by default returns the + // highest supported value (no indication that it is being enforced + // by choice). We don't want to bind all of this for some ridiculously + // high default number, unless it was explicitly set in the HTML. + // Also cast to a (primitive) number (most commonly because the maxlength + // attribute contains a string, but theoretically the limit parameter + // could be something else as well). + elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit ); + + // If there is no (valid) limit passed or found in the property, + // skip this. The < 0 check is required for Firefox, which returns + // -1 (instead of undefined) for maxLength if it is not set. + if ( !elLimit || elLimit < 0 ) { + return; + } + + if ( fn ) { + // Save function for reference + $el.data( 'byteLimit.callback', fn ); + } + + // Remove old event handlers (if there are any) + $el.off( '.byteLimit' ); + + if ( fn ) { + // Disable the native maxLength (if there is any), because it interferes + // with the (differently calculated) byte limit. + // Aside from being differently calculated (average chars with byteLimit + // is lower), we also support a callback which can make it to allow longer + // values (e.g. count "Foo" from "User:Foo"). + // maxLength is a strange property. Removing or setting the property to + // undefined directly doesn't work. Instead, it can only be unset internally + // by the browser when removing the associated attribute (Firefox/Chrome). + // http://code.google.com/p/chromium/issues/detail?id=136004 + $el.removeAttr( 'maxlength' ); + + } else { + // If we don't have a callback the bytelimit can only be lower than the charlimit + // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce + // the native limit for efficiency when possible (it will make the while-loop below + // faster by there being less left to interate over). + $el.attr( 'maxlength', elLimit ); + } + + // Safe base value, used to determine the path between the previous state + // and the state that triggered the event handler below - and enforce the + // limit approppiately (e.g. don't chop from the end if text was inserted + // at the beginning of the string). + prevSafeVal = ''; + + // We need to listen to after the change has already happened because we've + // learned that trying to guess the new value and canceling the event + // accordingly doesn't work because the new value is not always as simple as: + // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag + // replacements, and custom input methods and what not. + // Even though we only trim input after it was changed (never prevent it), we do + // listen on events that input text, because there are cases where the text has + // changed while text is being entered and keyup/change will not be fired yet + // (such as holding down a single key, fires keydown, and after each keydown, + // we can trim the previous one). + // 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( + prevSafeVal, + this.value, + elLimit, + fn + ); + + // Only set value property if it was trimmed, because whenever the + // value property is set, the browser needs to re-initiate the text context, + // which moves the cursor at the end the input, moving it away from wherever it was. + // This is a side-effect of limiting after the fact. + if ( res.trimmed === true ) { + this.value = res.newVal; + } + // 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 + // old value, resulting in trimming always from the end (bug 40850). + prevSafeVal = res.newVal; + } ); + } ); + }; + + /** + * @class jQuery + * @mixins jQuery.plugin.byteLimit + */ +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.checkboxShiftClick.js b/resources/src/jquery/jquery.checkboxShiftClick.js new file mode 100644 index 00000000..d99e9f0a --- /dev/null +++ b/resources/src/jquery/jquery.checkboxShiftClick.js @@ -0,0 +1,43 @@ +/** + * @class jQuery.plugin.checkboxShiftClick + */ +( function ( $ ) { + + /** + * Enable checkboxes to be checked or unchecked in a row by clicking one, + * holding shift and clicking another one. + * + * @return {jQuery} + * @chainable + */ + $.fn.checkboxShiftClick = function () { + var prevCheckbox = null, + $box = this; + // When our boxes are clicked.. + $box.click( function ( e ) { + // And one has been clicked before... + if ( prevCheckbox !== null && e.shiftKey ) { + // Check or uncheck this one and all in-between checkboxes, + // except for disabled ones + $box + .slice( + Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ), + Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1 + ) + .filter( function () { + return !this.disabled; + } ) + .prop( 'checked', !!e.target.checked ); + } + // Either way, update the prevCheckbox variable to the one clicked now + prevCheckbox = e.target; + } ); + return $box; + }; + + /** + * @class jQuery + * @mixins jQuery.plugin.checkboxShiftClick + */ + +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.client.js b/resources/src/jquery/jquery.client.js new file mode 100644 index 00000000..662a6887 --- /dev/null +++ b/resources/src/jquery/jquery.client.js @@ -0,0 +1,301 @@ +/** + * User-agent detection + * + * @class jQuery.client + * @singleton + */ +( function ( $ ) { + + /** + * @private + * @property {Object} profileCache Keyed by userAgent string, + * value is the parsed $.client.profile object for that user agent. + */ + var profileCache = {}; + + $.client = { + + /** + * Get an object containing information about the client. + * + * @param {Object} [nav] An object with a 'userAgent' and 'platform' property. + * Defaults to the global `navigator` object. + * @return {Object} The resulting client object will be in the following format: + * + * { + * 'name': 'firefox', + * 'layout': 'gecko', + * 'layoutVersion': 20101026, + * 'platform': 'linux' + * 'version': '3.5.1', + * 'versionBase': '3', + * 'versionNumber': 3.5, + * } + */ + profile: function ( nav ) { + /*jshint boss: true */ + + if ( nav === undefined ) { + nav = window.navigator; + } + + // Use the cached version if possible + if ( profileCache[ nav.userAgent + '|' + nav.platform ] !== undefined ) { + return profileCache[ nav.userAgent + '|' + nav.platform ]; + } + + var + versionNumber, + key = nav.userAgent + '|' + nav.platform, + + // Configuration + + // Name of browsers or layout engines we don't recognize + uk = 'unknown', + // Generic version digit + x = 'x', + // Strings found in user agent strings that need to be conformed + wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3', 'Iceweasel'], + // Translations for conforming user agent strings + userAgentTranslations = [ + // Tons of browsers lie about being something they are not + [/(Firefox|MSIE|KHTML,?\slike\sGecko|Konqueror)/, ''], + // Chrome lives in the shadow of Safari still + ['Chrome Safari', 'Chrome'], + // KHTML is the layout engine not the browser - LIES! + ['KHTML', 'Konqueror'], + // Firefox nightly builds + ['Minefield', 'Firefox'], + // This helps keep different versions consistent + ['Navigator', 'Netscape'], + // This prevents version extraction issues, otherwise translation would happen later + ['PLAYSTATION 3', 'PS3'] + ], + // Strings which precede a version number in a user agent string - combined and used as + // match 1 in version detection + versionPrefixes = [ + 'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'netscape6', 'opera', 'version', 'konqueror', + 'lynx', 'msie', 'safari', 'ps3', 'android' + ], + // Used as matches 2, 3 and 4 in version extraction - 3 is used as actual version number + versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)', + // Names of known browsers + names = [ + 'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', + 'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq', 'android' + ], + // Tanslations for conforming browser names + nameTranslations = [], + // Names of known layout engines + layouts = ['gecko', 'konqueror', 'msie', 'trident', 'opera', 'webkit'], + // Translations for conforming layout names + layoutTranslations = [ ['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto'] ], + // Names of supported layout engines for version number + layoutVersions = ['applewebkit', 'gecko', 'trident'], + // Names of known operating systems + platforms = ['win', 'wow64', 'mac', 'linux', 'sunos', 'solaris', 'iphone'], + // Translations for conforming operating system names + platformTranslations = [ ['sunos', 'solaris'], ['wow64', 'win'] ], + + /** + * Performs multiple replacements on a string + * @ignore + */ + translate = function ( source, translations ) { + var i; + for ( i = 0; i < translations.length; i++ ) { + source = source.replace( translations[i][0], translations[i][1] ); + } + return source; + }, + + // Pre-processing + + ua = nav.userAgent, + match, + name = uk, + layout = uk, + layoutversion = uk, + platform = uk, + version = x; + + if ( match = new RegExp( '(' + wildUserAgents.join( '|' ) + ')' ).exec( ua ) ) { + // Takes a userAgent string and translates given text into something we can more easily work with + ua = translate( ua, userAgentTranslations ); + } + // Everything will be in lowercase from now on + ua = ua.toLowerCase(); + + // Extraction + + if ( match = new RegExp( '(' + names.join( '|' ) + ')' ).exec( ua ) ) { + name = translate( match[1], nameTranslations ); + } + if ( match = new RegExp( '(' + layouts.join( '|' ) + ')' ).exec( ua ) ) { + layout = translate( match[1], layoutTranslations ); + } + if ( match = new RegExp( '(' + layoutVersions.join( '|' ) + ')\\\/(\\d+)').exec( ua ) ) { + layoutversion = parseInt( match[2], 10 ); + } + if ( match = new RegExp( '(' + platforms.join( '|' ) + ')' ).exec( nav.platform.toLowerCase() ) ) { + platform = translate( match[1], platformTranslations ); + } + if ( match = new RegExp( '(' + versionPrefixes.join( '|' ) + ')' + versionSuffix ).exec( ua ) ) { + version = match[3]; + } + + // Edge Cases -- did I mention about how user agent string lie? + + // Decode Safari's crazy 400+ version numbers + if ( name === 'safari' && version > 400 ) { + version = '2.0'; + } + // Expose Opera 10's lies about being Opera 9.8 + if ( name === 'opera' && version >= 9.8 ) { + match = ua.match( /\bversion\/([0-9\.]*)/ ); + if ( match && match[1] ) { + version = match[1]; + } else { + version = '10'; + } + } + // And Opera 15's lies about being Chrome + if ( name === 'chrome' && ( match = ua.match( /\bopr\/([0-9\.]*)/ ) ) ) { + if ( match[1] ) { + name = 'opera'; + version = match[1]; + } + } + // And IE 11's lies about being not being IE + if ( layout === 'trident' && layoutversion >= 7 && ( match = ua.match( /\brv[ :\/]([0-9\.]*)/ ) ) ) { + if ( match[1] ) { + name = 'msie'; + version = match[1]; + } + } + // And Amazon Silk's lies about being Android on mobile or Safari on desktop + if ( match = ua.match( /\bsilk\/([0-9.\-_]*)/ ) ) { + if ( match[1] ) { + name = 'silk'; + version = match[1]; + } + } + + versionNumber = parseFloat( version, 10 ) || 0.0; + + // Caching + + return profileCache[ key ] = { + name: name, + layout: layout, + layoutVersion: layoutversion, + platform: platform, + version: version, + versionBase: ( version !== x ? Math.floor( versionNumber ).toString() : x ), + versionNumber: versionNumber + }; + }, + + /** + * Checks the current browser against a support map object. + * + * Version numbers passed as numeric values will be compared like numbers (1.2 > 1.11). + * Version numbers passed as string values will be compared using a simple component-wise + * algorithm, similar to PHP's version_compare ('1.2' < '1.11'). + * + * A browser map is in the following format: + * + * { + * // Multiple rules with configurable operators + * 'msie': [['>=', 7], ['!=', 9]], + * // Match no versions + * 'iphone': false, + * // Match any version + * 'android': null + * } + * + * It can optionally be split into ltr/rtl sections: + * + * { + * 'ltr': { + * 'android': null, + * 'iphone': false + * }, + * 'rtl': { + * 'android': false, + * // rules are not inherited from ltr + * 'iphone': false + * } + * } + * + * @param {Object} map Browser support map + * @param {Object} [profile] A client-profile object + * @param {boolean} [exactMatchOnly=false] Only return true if the browser is matched, otherwise + * returns true if the browser is not found. + * + * @return {boolean} The current browser is in the support map + */ + test: function ( map, profile, exactMatchOnly ) { + /*jshint evil: true */ + + var conditions, dir, i, op, val, j, pieceVersion, pieceVal, compare; + profile = $.isPlainObject( profile ) ? profile : $.client.profile(); + if ( map.ltr && map.rtl ) { + dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'; + map = map[dir]; + } + // Check over each browser condition to determine if we are running in a compatible client + if ( typeof map !== 'object' || map[profile.name] === undefined ) { + // Not found, return true if exactMatchOnly not set, false otherwise + return !exactMatchOnly; + } + conditions = map[profile.name]; + if ( conditions === false ) { + // Match no versions + return false; + } + if ( conditions === null ) { + // Match all versions + return true; + } + for ( i = 0; i < conditions.length; i++ ) { + op = conditions[i][0]; + val = conditions[i][1]; + if ( typeof val === 'string' ) { + // Perform a component-wise comparison of versions, similar to PHP's version_compare + // but simpler. '1.11' is larger than '1.2'. + pieceVersion = profile.version.toString().split( '.' ); + pieceVal = val.split( '.' ); + // Extend with zeroes to equal length + while ( pieceVersion.length < pieceVal.length ) { + pieceVersion.push( '0' ); + } + while ( pieceVal.length < pieceVersion.length ) { + pieceVal.push( '0' ); + } + // Compare components + compare = 0; + for ( j = 0; j < pieceVersion.length; j++ ) { + if ( Number( pieceVersion[j] ) < Number( pieceVal[j] ) ) { + compare = -1; + break; + } else if ( Number( pieceVersion[j] ) > Number( pieceVal[j] ) ) { + compare = 1; + break; + } + } + // compare will be -1, 0 or 1, depending on comparison result + if ( !( eval( '' + compare + op + '0' ) ) ) { + return false; + } + } else if ( typeof val === 'number' ) { + if ( !( eval( 'profile.versionNumber' + op + val ) ) ) { + return false; + } + } + } + + return true; + } + }; +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.color.js b/resources/src/jquery/jquery.color.js new file mode 100644 index 00000000..04f8047b --- /dev/null +++ b/resources/src/jquery/jquery.color.js @@ -0,0 +1,55 @@ +/** + * jQuery Color Animations + * + * @author John Resig, 2007 + * @author Krinkle, 2011 + * Released under the MIT and GPL licenses. + * + * - 2011-01-05: Forked for MediaWiki. See also jQuery.colorUtil plugin + */ +( function ( $ ) { + + function getColor( elem, attr ) { + /*jshint boss:true */ + var color; + + do { + color = $.css( elem, attr ); + + // Keep going until we find an element that has color, or we hit the body + if ( color !== '' && color !== 'transparent' || $.nodeName( elem, 'body' ) ) { + break; + } + + attr = 'backgroundColor'; + } while ( elem = elem.parentNode ); + + return $.colorUtil.getRGB( color ); + } + + // We override the animation for all of these color styles + $.each([ + 'backgroundColor', + 'borderBottomColor', + 'borderLeftColor', + 'borderRightColor', + 'borderTopColor', + 'color', + 'outlineColor' + ], function ( i, attr ) { + $.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 ) + ].join( ',' ) + ')'; + }; + } ); + +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.colorUtil.js b/resources/src/jquery/jquery.colorUtil.js new file mode 100644 index 00000000..a6ff8bc8 --- /dev/null +++ b/resources/src/jquery/jquery.colorUtil.js @@ -0,0 +1,262 @@ +/*! + * jQuery Color Utilities + * + * Released under the MIT and GPL licenses. + * + * Mostly based on other plugins and functions (linted and optimized a little). + * Sources cited inline. + */ +( function ( $ ) { + /** + * @class jQuery.colorUtil + * @singleton + */ + $.colorUtil = { + + /** + * Parse CSS color strings looking for color tuples + * + * Based on highlightFade by Blair Mitchelmore + * + * + * @param {Array|string} color + * @return {Array} + */ + getRGB: function ( color ) { + /*jshint boss:true */ + var result; + + // Check if we're already dealing with an array of colors + if ( color && $.isArray( color ) && color.length === 3 ) { + return color; + } + + // 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)) { + return [ + 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)) { + return [ + 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)) { + return [ + 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)) { + return [ + 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)) { + return $.colorUtil.colors.transparent; + } + + // Otherwise, we're most likely dealing with a named color + return $.colorUtil.colors[$.trim(color).toLowerCase()]; + }, + + /** + * Named color map + * + * Based on Interface by Stefan Petre + * + * + * @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] + }, + + /** + * Convert an RGB color value to HSL. + * + * Conversion formula based on + * + * + * Adapted from . + * + * Assumes `r`, `g`, and `b` are contained in the set `[0, 255]` and + * returns `h`, `s`, and `l` in the set `[0, 1]`. + * + * @param {number} r The red color value + * @param {number} g The green color value + * @param {number} b The blue color value + * @return {number[]} The HSL representation + */ + rgbToHsl: function ( r, g, b ) { + 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; + + if ( max === min ) { + // achromatic + h = s = 0; + } else { + 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); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [h, s, l]; + }, + + /** + * Convert an HSL color value to RGB. + * + * Conversion formula based on + * + * + * Adapted from . + * + * Assumes `h`, `s`, and `l` are contained in the set `[0, 1]` and + * returns `r`, `g`, and `b` in the set `[0, 255]`. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {number[]} The RGB representation + */ + hslToRgb: function ( h, s, l ) { + var r, g, b, hue2rgb, q, p; + + if ( s === 0 ) { + r = g = b = l; // achromatic + } else { + hue2rgb = function ( p, q, t ) { + if ( t < 0 ) { + t += 1; + } + if ( t > 1 ) { + t -= 1; + } + if ( t < 1 / 6 ) { + 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 = 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]; + }, + + /** + * Get a brighter or darker rgb() value string. + * + * Usage: + * + * $.colorUtil.getColorBrightness( 'red', +0.1 ); + * // > "rgb(255,50,50)" + * $.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 ); + * // > "rgb(118,29,29)" + * + * @param {Mixed} currentColor Current value in css + * @param {number} mod Wanted brightness modification between -1 and 1 + * @return {string} Like `'rgb(r,g,b)'` + */ + 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); + + return 'rgb(' + + [parseInt( rgbArr[0], 10), parseInt( rgbArr[1], 10 ), parseInt( rgbArr[2], 10 )].join( ',' ) + + ')'; + } + + }; + +}( jQuery ) ); diff --git a/resources/src/jquery/jquery.confirmable.css b/resources/src/jquery/jquery.confirmable.css new file mode 100644 index 00000000..de690726 --- /dev/null +++ b/resources/src/jquery/jquery.confirmable.css @@ -0,0 +1,28 @@ +.jquery-confirmable-button { + /* Automatically flipped */ + margin-left: 1ex; +} + +.jquery-confirmable-wrapper { + /* Line breaks within the interface text are unpleasant */ + white-space: nowrap; + /* Hiding the original text when it slides to the left */ + overflow: hidden; +} + +.jquery-confirmable-wrapper, +.jquery-confirmable-element, +.jquery-confirmable-interface { + /* We need inline-block to be able to size the elements and calculate their dimensions */ + display: inline-block; + /* inline-block elements in this context align to baseline by default */ + vertical-align: bottom; +} + +.jquery-confirmable-element { + transition: margin 250ms cubic-bezier(0.2, 0.8, 0.2, 0.8); +} + +.jquery-confirmable-interface { + transition: width 250ms cubic-bezier(0.2, 0.8, 0.2, 0.8); +} diff --git a/resources/src/jquery/jquery.confirmable.js b/resources/src/jquery/jquery.confirmable.js new file mode 100644 index 00000000..339e65a4 --- /dev/null +++ b/resources/src/jquery/jquery.confirmable.js @@ -0,0 +1,170 @@ +/** + * jQuery confirmable plugin + * + * Released under the MIT License. + * + * @author Bartosz Dziewoński + * + * @class jQuery.plugin.confirmable + */ +( function ( $ ) { + var identity = function ( data ) { + return data; + }; + + /** + * Enable inline confirmation for given clickable element (like `` or `