diff options
Diffstat (limited to 'resources')
256 files changed, 14241 insertions, 10770 deletions
diff --git a/resources/Resources.php b/resources/Resources.php index ab2610ce..0a70c5a2 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -4,15 +4,25 @@ return array( /* Special modules who have their own classes */ + // Scripts managed by the local wiki (stored in the MediaWiki namespace) 'site' => array( 'class' => 'ResourceLoaderSiteModule' ), 'noscript' => array( 'class' => 'ResourceLoaderNoscriptModule' ), 'startup' => array( 'class' => 'ResourceLoaderStartUpModule' ), - 'user' => array( 'class' => 'ResourceLoaderUserModule' ), + 'filepage' => array( 'class' => 'ResourceLoaderFilePageModule' ), 'user.groups' => array( 'class' => 'ResourceLoaderUserGroupsModule' ), - 'user.options' => array( 'class' => 'ResourceLoaderUserOptionsModule' ), + + // Scripts managed by the current user (stored in their user space) + 'user' => array( 'class' => 'ResourceLoaderUserModule' ), + + // Scripts generated based on the current user's preferences 'user.cssprefs' => array( 'class' => 'ResourceLoaderUserCSSPrefsModule' ), + + // Populate mediawiki.user placeholders with information about the current user + 'user.options' => array( 'class' => 'ResourceLoaderUserOptionsModule' ), 'user.tokens' => array( 'class' => 'ResourceLoaderUserTokensModule' ), - 'filepage' => array( 'class' => 'ResourceLoaderFilePageModule' ), + + // Scripts for the dynamic language specific data, like grammar forms. + 'mediawiki.language.data' => array( 'class' => 'ResourceLoaderLanguageDataModule' ), /* Skins */ @@ -68,7 +78,7 @@ return array( 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), 'skins.standard' => array( - 'styles' => array( 'common/wikistandard.css' => array( 'media' => 'screen' ) ), + 'styles' => array( 'standard/main.css' => array( 'media' => 'screen' ) ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), @@ -79,6 +89,7 @@ return array( 'common/commonContent.css' => array( 'media' => 'screen' ), 'common/commonInterface.css' => array( 'media' => 'screen' ), 'vector/screen.css' => array( 'media' => 'screen' ), + 'vector/screen-hd.css' => array( 'media' => 'screen and (min-width: 982px)' ), ), 'scripts' => 'vector/vector.js', 'remoteBasePath' => $GLOBALS['wgStylePath'], @@ -108,6 +119,10 @@ return array( 'scripts' => 'resources/jquery/jquery.autoEllipsis.js', 'dependencies' => 'jquery.highlightText', ), + 'jquery.badge' => array( + 'scripts' => 'resources/jquery/jquery.badge.js', + 'styles' => 'resources/jquery/jquery.badge.css', + ), 'jquery.byteLength' => array( 'scripts' => 'resources/jquery/jquery.byteLength.js', ), @@ -158,6 +173,7 @@ return array( ), 'jquery.highlightText' => array( 'scripts' => 'resources/jquery/jquery.highlightText.js', + 'dependencies' => 'jquery.mwExtension', ), 'jquery.hoverIntent' => array( 'scripts' => 'resources/jquery/jquery.hoverIntent.js', @@ -173,10 +189,6 @@ return array( 'styles' => 'resources/jquery/jquery.makeCollapsible.css', 'messages' => array( 'collapsible-expand', 'collapsible-collapse' ), ), - 'jquery.messageBox' => array( - 'scripts' => 'resources/jquery/jquery.messageBox.js', - 'styles' => 'resources/jquery/jquery.messageBox.css', - ), 'jquery.mockjax' => array( 'scripts' => 'resources/jquery/jquery.mockjax.js', ), @@ -202,6 +214,10 @@ return array( 'scripts' => 'resources/jquery/jquery.spinner.js', 'styles' => 'resources/jquery/jquery.spinner.css', ), + 'jquery.jStorage' => array( + 'scripts' => 'resources/jquery/jquery.jStorage.js', + 'dependencies' => 'jquery.json', + ), 'jquery.suggestions' => array( 'scripts' => 'resources/jquery/jquery.suggestions.js', 'styles' => 'resources/jquery/jquery.suggestions.css', @@ -214,9 +230,11 @@ return array( 'scripts' => 'resources/jquery/jquery.tablesorter.js', 'styles' => 'resources/jquery/jquery.tablesorter.css', 'messages' => array( 'sort-descending', 'sort-ascending' ), + 'dependencies' => 'jquery.mwExtension', ), 'jquery.textSelection' => array( 'scripts' => 'resources/jquery/jquery.textSelection.js', + 'dependencies' => 'jquery.client', ), 'jquery.validate' => array( 'scripts' => 'resources/jquery/jquery.validate.js', @@ -353,10 +371,10 @@ return array( 'fa' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-fa.js', 'fi' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-fi.js', 'fo' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-fo.js', - 'fr-ch' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-fr-CH.js', 'fr' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-fr.js', 'gl' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-gl.js', 'he' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-he.js', + 'hi' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-hi.js', 'hr' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-hr.js', 'hu' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-hu.js', 'hy' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-hy.js', @@ -364,7 +382,9 @@ return array( 'is' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-is.js', 'it' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-it.js', 'ja' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ja.js', + 'ka' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ka.js', 'kk' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-kk.js', + 'km' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-km.js', 'ko' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ko.js', 'lb' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-lb.js', 'lt' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-lt.js', @@ -524,34 +544,37 @@ return array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.js', 'dependencies' => 'mediawiki.util', ), - 'mediawiki.api.category' => array( + 'mediawiki.api.category' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.category.js', - 'dependencies' => array( + 'dependencies' => array( 'mediawiki.api', - 'mediawiki.Title' + 'mediawiki.Title', ), ), - 'mediawiki.api.edit' => array( + 'mediawiki.api.edit' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.edit.js', - 'dependencies' => array( + 'dependencies' => array( 'mediawiki.api', - 'mediawiki.Title' + 'mediawiki.Title', ), ), - 'mediawiki.api.parse' => array( + 'mediawiki.api.parse' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.parse.js', 'dependencies' => 'mediawiki.api', ), - 'mediawiki.api.titleblacklist' => array( + 'mediawiki.api.titleblacklist' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.titleblacklist.js', - 'dependencies' => array( + 'dependencies' => array( 'mediawiki.api', - 'mediawiki.Title' + 'mediawiki.Title', ), ), - 'mediawiki.api.watch' => array( + 'mediawiki.api.watch' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.watch.js', - 'dependencies' => array('mediawiki.api', 'mediawiki.user'), + 'dependencies' => array( + 'mediawiki.api', + 'user.tokens', + ), ), 'mediawiki.debug' => array( 'scripts' => 'resources/mediawiki/mediawiki.debug.js', @@ -569,13 +592,13 @@ return array( 'mediawiki.feedback' => array( 'scripts' => 'resources/mediawiki/mediawiki.feedback.js', 'styles' => 'resources/mediawiki/mediawiki.feedback.css', - 'dependencies' => array( - 'mediawiki.api.edit', + 'dependencies' => array( + 'mediawiki.api.edit', 'mediawiki.Title', 'mediawiki.jqueryMsg', 'jquery.ui.dialog', ), - 'messages' => array( + 'messages' => array( 'feedback-bugornote', 'feedback-subject', 'feedback-message', @@ -594,6 +617,29 @@ return array( 'mediawiki.htmlform' => array( 'scripts' => 'resources/mediawiki/mediawiki.htmlform.js', ), + 'mediawiki.notification' => array( + 'styles' => 'resources/mediawiki/mediawiki.notification.css', + 'scripts' => 'resources/mediawiki/mediawiki.notification.js', + 'dependencies' => array( + 'mediawiki.page.startup', + ), + ), + 'mediawiki.notify' => array( + 'scripts' => 'resources/mediawiki/mediawiki.notify.js', + ), + 'mediawiki.searchSuggest' => array( + 'scripts' => 'resources/mediawiki/mediawiki.searchSuggest.js', + 'messages' => array( + 'searchsuggest-search', + 'searchsuggest-containing', + ), + 'dependencies' => array( + 'jquery.autoEllipsis', + 'jquery.client', + 'jquery.placeholder', + 'jquery.suggestions', + ), + ), 'mediawiki.Title' => array( 'scripts' => 'resources/mediawiki/mediawiki.Title.js', 'dependencies' => 'mediawiki.util', @@ -605,6 +651,7 @@ return array( 'scripts' => 'resources/mediawiki/mediawiki.user.js', 'dependencies' => array( 'jquery.cookie', + 'mediawiki.api', ), ), 'mediawiki.util' => array( @@ -612,8 +659,8 @@ return array( 'dependencies' => array( 'jquery.client', 'jquery.cookie', - 'jquery.messageBox', 'jquery.mwExtension', + 'mediawiki.notify', ), 'messages' => array( 'showtoc', 'hidetoc' ), 'position' => 'top', // For $wgPreloadJavaScriptMwUtil @@ -627,10 +674,17 @@ return array( 'jquery.textSelection', 'jquery.byteLimit', ), + 'position' => 'top', + ), + 'mediawiki.action.edit.preview' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.preview.js', + 'dependencies' => array( + 'jquery.form', + 'jquery.spinner', + ), ), 'mediawiki.action.history' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.history.js', - 'dependencies' => 'jquery.ui.button', 'group' => 'mediawiki.action.history', ), 'mediawiki.action.history.diff' => array( @@ -651,21 +705,9 @@ return array( 'mediawiki.action.view.rightClickEdit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js', ), + // Alias for backwards compatibility 'mediawiki.action.watch.ajax' => array( - 'scripts' => 'resources/mediawiki.action/mediawiki.action.watch.ajax.js', - 'dependencies' => array( - 'mediawiki.api.watch', - 'mediawiki.util', - ), - 'messages' => array( - 'watch', - 'unwatch', - 'watching', - 'unwatching', - 'tooltip-ca-watch', - 'tooltip-ca-unwatch', - 'watcherrortext', - ), + 'dependencies' => 'mediawiki.page.watch.ajax' ), /* MediaWiki Language */ @@ -673,59 +715,47 @@ return array( 'mediawiki.language' => array( 'scripts' => 'resources/mediawiki.language/mediawiki.language.js', 'languageScripts' => array( - 'am' => 'resources/mediawiki.language/languages/am.js', - 'ar' => 'resources/mediawiki.language/languages/ar.js', - 'bat-smg' => 'resources/mediawiki.language/languages/bat-smg.js', - 'be' => 'resources/mediawiki.language/languages/be.js', - 'be-tarask' => 'resources/mediawiki.language/languages/be-tarask.js', - 'bh' => 'resources/mediawiki.language/languages/bh.js', 'bs' => 'resources/mediawiki.language/languages/bs.js', - 'cs' => 'resources/mediawiki.language/languages/cs.js', - 'cu' => 'resources/mediawiki.language/languages/cu.js', - 'cy' => 'resources/mediawiki.language/languages/cy.js', 'dsb' => 'resources/mediawiki.language/languages/dsb.js', - 'fr' => 'resources/mediawiki.language/languages/fr.js', + 'fi' => 'resources/mediawiki.language/languages/fi.js', 'ga' => 'resources/mediawiki.language/languages/ga.js', - 'gd' => 'resources/mediawiki.language/languages/gd.js', - 'gv' => 'resources/mediawiki.language/languages/gv.js', 'he' => 'resources/mediawiki.language/languages/he.js', - 'hi' => 'resources/mediawiki.language/languages/hi.js', - 'hr' => 'resources/mediawiki.language/languages/hr.js', 'hsb' => 'resources/mediawiki.language/languages/hsb.js', + 'hu' => 'resources/mediawiki.language/languages/hu.js', 'hy' => 'resources/mediawiki.language/languages/hy.js', - 'ksh' => 'resources/mediawiki.language/languages/ksh.js', - 'ln' => 'resources/mediawiki.language/languages/ln.js', - 'lt' => 'resources/mediawiki.language/languages/lt.js', - 'lv' => 'resources/mediawiki.language/languages/lv.js', - 'mg' => 'resources/mediawiki.language/languages/mg.js', - 'mk' => 'resources/mediawiki.language/languages/mk.js', - 'mo' => 'resources/mediawiki.language/languages/mo.js', - 'mt' => 'resources/mediawiki.language/languages/mt.js', - 'nl' => 'resources/mediawiki.language/languages/nl.js', - 'nso' => 'resources/mediawiki.language/languages/nso.js', - 'pl' => 'resources/mediawiki.language/languages/pl.js', - 'pt' => 'resources/mediawiki.language/languages/pt.js', - 'pt-br' => 'resources/mediawiki.language/languages/pt-br.js', - 'ro' => 'resources/mediawiki.language/languages/ro.js', + 'la' => 'resources/mediawiki.language/languages/la.js', + 'os' => 'resources/mediawiki.language/languages/os.js', 'ru' => 'resources/mediawiki.language/languages/ru.js', - 'se' => 'resources/mediawiki.language/languages/se.js', - 'sh' => 'resources/mediawiki.language/languages/sh.js', - 'sk' => 'resources/mediawiki.language/languages/sk.js', 'sl' => 'resources/mediawiki.language/languages/sl.js', - 'sma' => 'resources/mediawiki.language/languages/sma.js', - 'sr-ec' => 'resources/mediawiki.language/languages/sr-ec.js', - 'sr-el' => 'resources/mediawiki.language/languages/sr-el.js', - 'sr' => 'resources/mediawiki.language/languages/sr.js', - 'ti' => 'resources/mediawiki.language/languages/ti.js', - 'tl' => 'resources/mediawiki.language/languages/tl.js', 'uk' => 'resources/mediawiki.language/languages/uk.js', - 'wa' => 'resources/mediawiki.language/languages/wa.js', ), + 'dependencies' => array( + 'mediawiki.language.data', + 'mediawiki.cldr' + ), + ), + + 'mediawiki.cldr' => array( + 'scripts' => 'resources/mediawiki.language/mediawiki.cldr.js', + 'dependencies' => array( + 'mediawiki.libs.pluralruleparser', + ), + ), + + 'mediawiki.libs.pluralruleparser' => array( + 'scripts' => 'resources/mediawiki.libs/CLDRPluralRuleParser.js', + ), + + 'mediawiki.language.init' => array( + 'scripts' => 'resources/mediawiki.language/mediawiki.language.init.js', ), 'mediawiki.jqueryMsg' => array( - 'dependencies' => array( 'mediawiki.language', 'mediawiki.util' ), - 'scripts' => 'resources/mediawiki/mediawiki.jqueryMsg.js' + 'scripts' => 'resources/mediawiki/mediawiki.jqueryMsg.js', + 'dependencies' => array( + 'mediawiki.util', + 'mediawiki.language', + ), ), /* MediaWiki Libs */ @@ -754,7 +784,25 @@ return array( ), 'position' => 'top', ), - + 'mediawiki.page.watch.ajax' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.watch.ajax.js', + 'dependencies' => array( + 'mediawiki.page.startup', + 'mediawiki.api.watch', + 'mediawiki.util', + 'mediawiki.notify', + 'jquery.mwExtension', + ), + 'messages' => array( + 'watch', + 'unwatch', + 'watching', + 'unwatching', + 'tooltip-ca-watch', + 'tooltip-ca-unwatch', + 'watcherrortext', + ), + ), /* MediaWiki Special pages */ @@ -789,6 +837,7 @@ return array( ), 'mediawiki.special.preferences' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.preferences.js', + 'styles' => 'resources/mediawiki.special/mediawiki.special.preferences.css', ), 'mediawiki.special.recentchanges' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.recentchanges.js', @@ -798,6 +847,11 @@ return array( 'mediawiki.special.search' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.search.js', 'styles' => 'resources/mediawiki.special/mediawiki.special.search.css', + 'messages' => array( + 'powersearch-togglelabel', + 'powersearch-toggleall', + 'powersearch-togglenone', + ), ), 'mediawiki.special.undelete' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.undelete.js', @@ -868,19 +922,6 @@ return array( 'localBasePath' => $GLOBALS['wgStyleDirectory'], 'dependencies' => 'mediawiki.legacy.wikibits', ), - 'mediawiki.legacy.mwsuggest' => array( - 'scripts' => 'common/mwsuggest.js', - 'remoteBasePath' => $GLOBALS['wgStylePath'], - 'localBasePath' => $GLOBALS['wgStyleDirectory'], - 'dependencies' => 'mediawiki.legacy.wikibits', - 'messages' => array( 'search-mwsuggest-enabled', 'search-mwsuggest-disabled' ), - ), - 'mediawiki.legacy.preview' => array( - 'scripts' => 'common/preview.js', - 'remoteBasePath' => $GLOBALS['wgStylePath'], - 'localBasePath' => $GLOBALS['wgStyleDirectory'], - 'dependencies' => 'mediawiki.legacy.wikibits', - ), 'mediawiki.legacy.protect' => array( 'scripts' => 'common/protect.js', 'remoteBasePath' => $GLOBALS['wgStylePath'], diff --git a/resources/jquery.effects/jquery.effects.blind.js b/resources/jquery.effects/jquery.effects.blind.js index 2ecbbed3..1e997690 100644 --- a/resources/jquery.effects/jquery.effects.blind.js +++ b/resources/jquery.effects/jquery.effects.blind.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Blind 1.8.17 +/*! + * jQuery UI Effects Blind 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.bounce.js b/resources/jquery.effects/jquery.effects.bounce.js index f8e44730..7927a4a9 100644 --- a/resources/jquery.effects/jquery.effects.bounce.js +++ b/resources/jquery.effects/jquery.effects.bounce.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Bounce 1.8.17 +/*! + * jQuery UI Effects Bounce 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -32,7 +32,7 @@ $.effects.bounce = function(o) { $.effects.createWrapper(el); // Create Wrapper var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; - var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) / 3 : el.outerWidth({margin:true}) / 3); + var distance = o.options.distance || (ref == 'top' ? el.outerHeight(true) / 3 : el.outerWidth(true) / 3); if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift if (mode == 'hide') distance = distance / (times * 2); if (mode != 'hide') times--; diff --git a/resources/jquery.effects/jquery.effects.clip.js b/resources/jquery.effects/jquery.effects.clip.js index 74845c08..d8b8218f 100644 --- a/resources/jquery.effects/jquery.effects.clip.js +++ b/resources/jquery.effects/jquery.effects.clip.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Clip 1.8.17 +/*! + * jQuery UI Effects Clip 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.core.js b/resources/jquery.effects/jquery.effects.core.js index 7f568964..91ac5755 100644 --- a/resources/jquery.effects/jquery.effects.core.js +++ b/resources/jquery.effects/jquery.effects.core.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects 1.8.17 +/*! + * jQuery UI Effects 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -75,7 +75,8 @@ function getColor(elem, attr) { var color; do { - color = $.curCSS(elem, attr); + // jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css + color = ($.curCSS || $.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") ) @@ -236,7 +237,7 @@ $.effects.animateClass = function(value, duration, easing, callback) { originalStyleAttr = that.attr('style') || ' ', originalStyle = filterStyles(getElementStyles.call(this)), newStyle, - className = that.attr('class'); + className = that.attr('class') || ""; $.each(classAnimationActions, function(i, action) { if (value[action]) { @@ -306,7 +307,7 @@ $.fn.extend({ /******************************************************************************/ $.extend($.effects, { - version: "1.8.17", + version: "1.8.23", // Saves a set of properties in a data storage save: function(element, set) { @@ -370,7 +371,16 @@ $.extend($.effects, { }), active = document.activeElement; - element.wrap(wrapper); + // support: Firefox + // Firefox incorrectly exposes anonymous content + // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 + try { + active.id; + } catch( e ) { + active = document.body; + } + + element.wrap( wrapper ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { @@ -419,7 +429,7 @@ $.extend($.effects, { setTransition: function(element, list, factor, value) { value = value || {}; $.each(list, function(i, x){ - unit = element.cssUnit(x); + var unit = element.cssUnit(x); if (unit[0] > 0) value[x] = unit[0] * factor + unit[1]; }); return value; @@ -554,210 +564,49 @@ $.fn.extend({ /*********************************** EASING ***********************************/ /******************************************************************************/ -/* - * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ - * - * Uses the built in easing capabilities added In jQuery 1.1 - * to offer multiple easing options - * - * TERMS OF USE - jQuery Easing - * - * Open source under the BSD License. - * - * Copyright 2008 George McGinley Smith - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse - * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * -*/ +// based on easing equations from Robert Penner (http://www.robertpenner.com/easing) -// t: current time, b: begInnIng value, c: change In value, d: duration -$.easing.jswing = $.easing.swing; +var baseEasings = {}; -$.extend($.easing, -{ - def: 'easeOutQuad', - swing: function (x, t, b, c, d) { - //alert($.easing.default); - return $.easing[$.easing.def](x, t, b, c, d); - }, - easeInQuad: function (x, t, b, c, d) { - return c*(t/=d)*t + b; - }, - easeOutQuad: function (x, t, b, c, d) { - return -c *(t/=d)*(t-2) + b; - }, - easeInOutQuad: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t + b; - return -c/2 * ((--t)*(t-2) - 1) + b; - }, - easeInCubic: function (x, t, b, c, d) { - return c*(t/=d)*t*t + b; - }, - easeOutCubic: function (x, t, b, c, d) { - return c*((t=t/d-1)*t*t + 1) + b; - }, - easeInOutCubic: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t + b; - return c/2*((t-=2)*t*t + 2) + b; - }, - easeInQuart: function (x, t, b, c, d) { - return c*(t/=d)*t*t*t + b; - }, - easeOutQuart: function (x, t, b, c, d) { - return -c * ((t=t/d-1)*t*t*t - 1) + b; - }, - easeInOutQuart: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t*t + b; - return -c/2 * ((t-=2)*t*t*t - 2) + b; - }, - easeInQuint: function (x, t, b, c, d) { - return c*(t/=d)*t*t*t*t + b; - }, - easeOutQuint: function (x, t, b, c, d) { - return c*((t=t/d-1)*t*t*t*t + 1) + b; - }, - easeInOutQuint: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; - return c/2*((t-=2)*t*t*t*t + 2) + b; - }, - easeInSine: function (x, t, b, c, d) { - return -c * Math.cos(t/d * (Math.PI/2)) + c + b; - }, - easeOutSine: function (x, t, b, c, d) { - return c * Math.sin(t/d * (Math.PI/2)) + b; - }, - easeInOutSine: function (x, t, b, c, d) { - return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; - }, - easeInExpo: function (x, t, b, c, d) { - return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; - }, - easeOutExpo: function (x, t, b, c, d) { - return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; - }, - easeInOutExpo: function (x, t, b, c, d) { - if (t==0) return b; - if (t==d) return b+c; - if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; - }, - easeInCirc: function (x, t, b, c, d) { - return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; - }, - easeOutCirc: function (x, t, b, c, d) { - return c * Math.sqrt(1 - (t=t/d-1)*t) + b; - }, - easeInOutCirc: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; - return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; - }, - easeInElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; - }, - easeOutElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; - }, - easeInOutElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; - return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; - }, - easeInBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - return c*(t/=d)*t*((s+1)*t - s) + b; - }, - easeOutBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; +$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { + baseEasings[ name ] = function( p ) { + return Math.pow( p, i + 2 ); + }; +}); + +$.extend( baseEasings, { + Sine: function ( p ) { + return 1 - Math.cos( p * Math.PI / 2 ); }, - easeInOutBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; - return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + Circ: function ( p ) { + return 1 - Math.sqrt( 1 - p * p ); }, - easeInBounce: function (x, t, b, c, d) { - return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b; + Elastic: function( p ) { + return p === 0 || p === 1 ? p : + -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 ); }, - easeOutBounce: function (x, t, b, c, d) { - if ((t/=d) < (1/2.75)) { - return c*(7.5625*t*t) + b; - } else if (t < (2/2.75)) { - return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; - } else if (t < (2.5/2.75)) { - return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; - } else { - return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; - } + Back: function( p ) { + return p * p * ( 3 * p - 2 ); }, - easeInOutBounce: function (x, t, b, c, d) { - if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; - return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + Bounce: function ( p ) { + var pow2, + bounce = 4; + + while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} + return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); } }); -/* - * - * TERMS OF USE - EASING EQUATIONS - * - * Open source under the BSD License. - * - * Copyright 2001 Robert Penner - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse - * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ +$.each( baseEasings, function( name, easeIn ) { + $.easing[ "easeIn" + name ] = easeIn; + $.easing[ "easeOut" + name ] = function( p ) { + return 1 - easeIn( 1 - p ); + }; + $.easing[ "easeInOut" + name ] = function( p ) { + return p < .5 ? + easeIn( p * 2 ) / 2 : + easeIn( p * -2 + 2 ) / -2 + 1; + }; +}); })(jQuery); diff --git a/resources/jquery.effects/jquery.effects.drop.js b/resources/jquery.effects/jquery.effects.drop.js index 4bbd1087..6d25bd30 100644 --- a/resources/jquery.effects/jquery.effects.drop.js +++ b/resources/jquery.effects/jquery.effects.drop.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Drop 1.8.17 +/*! + * jQuery UI Effects Drop 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -28,7 +28,7 @@ $.effects.drop = function(o) { $.effects.createWrapper(el); // Create Wrapper var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; - var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) / 2 : el.outerWidth({margin:true}) / 2); + var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) / 2 : el.outerWidth( true ) / 2); if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift // Animation diff --git a/resources/jquery.effects/jquery.effects.explode.js b/resources/jquery.effects/jquery.effects.explode.js index 2c12fdb4..1caeca86 100644 --- a/resources/jquery.effects/jquery.effects.explode.js +++ b/resources/jquery.effects/jquery.effects.explode.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Explode 1.8.17 +/*! + * jQuery UI Effects Explode 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.fade.js b/resources/jquery.effects/jquery.effects.fade.js index bae63661..61249798 100644 --- a/resources/jquery.effects/jquery.effects.fade.js +++ b/resources/jquery.effects/jquery.effects.fade.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Fade 1.8.17 +/*! + * jQuery UI Effects Fade 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.fold.js b/resources/jquery.effects/jquery.effects.fold.js index 8e732739..81b15b83 100644 --- a/resources/jquery.effects/jquery.effects.fold.js +++ b/resources/jquery.effects/jquery.effects.fold.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Fold 1.8.17 +/*! + * jQuery UI Effects Fold 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.highlight.js b/resources/jquery.effects/jquery.effects.highlight.js index 306d515b..dee0639f 100644 --- a/resources/jquery.effects/jquery.effects.highlight.js +++ b/resources/jquery.effects/jquery.effects.highlight.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Highlight 1.8.17 +/*! + * jQuery UI Effects Highlight 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.pulsate.js b/resources/jquery.effects/jquery.effects.pulsate.js index 766690fb..45cdc884 100644 --- a/resources/jquery.effects/jquery.effects.pulsate.js +++ b/resources/jquery.effects/jquery.effects.pulsate.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Pulsate 1.8.17 +/*! + * jQuery UI Effects Pulsate 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -15,8 +15,8 @@ $.effects.pulsate = function(o) { return this.queue(function() { var elem = $(this), - mode = $.effects.setMode(elem, o.options.mode || 'show'); - times = ((o.options.times || 5) * 2) - 1; + mode = $.effects.setMode(elem, o.options.mode || 'show'), + times = ((o.options.times || 5) * 2) - 1, duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2, isVisible = elem.is(':visible'), animateTo = 0; diff --git a/resources/jquery.effects/jquery.effects.scale.js b/resources/jquery.effects/jquery.effects.scale.js index 4cb73822..44ecee18 100644 --- a/resources/jquery.effects/jquery.effects.scale.js +++ b/resources/jquery.effects/jquery.effects.scale.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Scale 1.8.17 +/*! + * jQuery UI Effects Scale 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -140,7 +140,7 @@ $.effects.size = function(o) { hProps = hProps.concat(['marginLeft','marginRight']); // Add margins props2 = props.concat(vProps).concat(hProps); // Concat el.find("*[width]").each(function(){ - child = $(this); + var child = $(this); if (restore) $.effects.save(child, props2); var c_original = {height: child.height(), width: child.width()}; // Save original child.from = {height: c_original.height * factor.from.y, width: c_original.width * factor.from.x}; diff --git a/resources/jquery.effects/jquery.effects.shake.js b/resources/jquery.effects/jquery.effects.shake.js index b5a7352e..bc1fd191 100644 --- a/resources/jquery.effects/jquery.effects.shake.js +++ b/resources/jquery.effects/jquery.effects.shake.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Shake 1.8.17 +/*! + * jQuery UI Effects Shake 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.effects/jquery.effects.slide.js b/resources/jquery.effects/jquery.effects.slide.js index e41869b9..0a430278 100644 --- a/resources/jquery.effects/jquery.effects.slide.js +++ b/resources/jquery.effects/jquery.effects.slide.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Slide 1.8.17 +/*! + * jQuery UI Effects Slide 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -28,7 +28,7 @@ $.effects.slide = function(o) { $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; - var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) : el.outerWidth({margin:true})); + var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) : el.outerWidth( true )); if (mode == 'show') el.css(ref, motion == 'pos' ? (isNaN(distance) ? "-" + distance : -distance) : distance); // Shift // Animation diff --git a/resources/jquery.effects/jquery.effects.transfer.js b/resources/jquery.effects/jquery.effects.transfer.js index d51b9c43..64f2a17b 100644 --- a/resources/jquery.effects/jquery.effects.transfer.js +++ b/resources/jquery.effects/jquery.effects.transfer.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Effects Transfer 1.8.17 +/*! + * jQuery UI Effects Transfer 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.tipsy/images/tipsy.png b/resources/jquery.tipsy/images/tipsy.png Binary files differindex 9a80e942..fef8c4b5 100644 --- a/resources/jquery.tipsy/images/tipsy.png +++ b/resources/jquery.tipsy/images/tipsy.png diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-ar.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-ar.js index 743a15db..8a9218d8 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-ar.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-ar.js @@ -12,7 +12,7 @@ jQuery(function($){ monthNamesShort: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], dayNames: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], dayNamesShort: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], - dayNamesMin: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + dayNamesMin: ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], weekHeader: 'أسبوع', dateFormat: 'dd/mm/yy', firstDay: 6, diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-de.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-de.js index 166f5371..f3ef9e82 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-de.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-de.js @@ -13,7 +13,7 @@ jQuery(function($){ dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'], - weekHeader: 'Wo', + weekHeader: 'KW', dateFormat: 'dd.mm.yy', firstDay: 1, isRTL: false, diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-et.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-et.js index 92f81f63..32702b24 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-et.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-et.js @@ -13,7 +13,7 @@ jQuery(function($){ dayNames: ['Pühapäev', 'Esmaspäev', 'Teisipäev', 'Kolmapäev', 'Neljapäev', 'Reede', 'Laupäev'], dayNamesShort: ['Pühap', 'Esmasp', 'Teisip', 'Kolmap', 'Neljap', 'Reede', 'Laup'], dayNamesMin: ['P','E','T','K','N','R','L'], - weekHeader: 'Sm', + weekHeader: 'näd', dateFormat: 'dd.mm.yy', firstDay: 1, isRTL: false, diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-eu.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-eu.js index bee4bfba..ff66e494 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-eu.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-eu.js @@ -6,15 +6,15 @@ jQuery(function($){ prevText: '<Aur', nextText: 'Hur>', currentText: 'Gaur', - monthNames: ['Urtarrila','Otsaila','Martxoa','Apirila','Maiatza','Ekaina', - 'Uztaila','Abuztua','Iraila','Urria','Azaroa','Abendua'], - monthNamesShort: ['Urt','Ots','Mar','Api','Mai','Eka', - 'Uzt','Abu','Ira','Urr','Aza','Abe'], - dayNames: ['Igandea','Astelehena','Asteartea','Asteazkena','Osteguna','Ostirala','Larunbata'], - dayNamesShort: ['Iga','Ast','Ast','Ast','Ost','Ost','Lar'], - dayNamesMin: ['Ig','As','As','As','Os','Os','La'], - weekHeader: 'Wk', - dateFormat: 'yy/mm/dd', + monthNames: ['urtarrila','otsaila','martxoa','apirila','maiatza','ekaina', + 'uztaila','abuztua','iraila','urria','azaroa','abendua'], + monthNamesShort: ['urt.','ots.','mar.','api.','mai.','eka.', + 'uzt.','abu.','ira.','urr.','aza.','abe.'], + dayNames: ['igandea','astelehena','asteartea','asteazkena','osteguna','ostirala','larunbata'], + dayNamesShort: ['ig.','al.','ar.','az.','og.','ol.','lr.'], + dayNamesMin: ['ig','al','ar','az','og','ol','lr'], + weekHeader: 'As', + dateFormat: 'yy-mm-dd', firstDay: 1, isRTL: false, showMonthAfterYear: false, diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-fa.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-fa.js index 81de4da4..be8acd2a 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-fa.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-fa.js @@ -4,15 +4,51 @@ jQuery(function($) { $.datepicker.regional['fa'] = { closeText: 'بستن', - prevText: '<قبلي', - nextText: 'بعدي>', + prevText: '<قبلی', + nextText: 'بعدی>', currentText: 'امروز', - monthNames: ['فروردين','ارديبهشت','خرداد','تير','مرداد','شهريور', - 'مهر','آبان','آذر','دي','بهمن','اسفند'], + monthNames: [ + 'فروردين', + 'ارديبهشت', + 'خرداد', + 'تير', + 'مرداد', + 'شهريور', + 'مهر', + 'آبان', + 'آذر', + 'دی', + 'بهمن', + 'اسفند' + ], monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], - dayNames: ['يکشنبه','دوشنبه','سهشنبه','چهارشنبه','پنجشنبه','جمعه','شنبه'], - dayNamesShort: ['ي','د','س','چ','پ','ج', 'ش'], - dayNamesMin: ['ي','د','س','چ','پ','ج', 'ش'], + dayNames: [ + 'يکشنبه', + 'دوشنبه', + 'سهشنبه', + 'چهارشنبه', + 'پنجشنبه', + 'جمعه', + 'شنبه' + ], + dayNamesShort: [ + 'ی', + 'د', + 'س', + 'چ', + 'پ', + 'ج', + 'ش' + ], + dayNamesMin: [ + 'ی', + 'د', + 'س', + 'چ', + 'پ', + 'ج', + 'ش' + ], weekHeader: 'هف', dateFormat: 'yy/mm/dd', firstDay: 6, diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-fi.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-fi.js index e1f25fd8..4c5adda3 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-fi.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-fi.js @@ -1,23 +1,23 @@ /* Finnish initialisation for the jQuery UI date picker plugin. */ -/* Written by Harri Kilpi� (harrikilpio@gmail.com). */ +/* Written by Harri Kilpiö (harrikilpio@gmail.com). */ jQuery(function($){ - $.datepicker.regional['fi'] = { + $.datepicker.regional['fi'] = { closeText: 'Sulje', - prevText: '«Edellinen', - nextText: 'Seuraava»', - currentText: 'Tänään', - monthNames: ['Tammikuu','Helmikuu','Maaliskuu','Huhtikuu','Toukokuu','Kesäkuu', - 'Heinäkuu','Elokuu','Syyskuu','Lokakuu','Marraskuu','Joulukuu'], - monthNamesShort: ['Tammi','Helmi','Maalis','Huhti','Touko','Kesä', - 'Heinä','Elo','Syys','Loka','Marras','Joulu'], - dayNamesShort: ['Su','Ma','Ti','Ke','To','Pe','Su'], + prevText: '«Edellinen', + nextText: 'Seuraava»', + currentText: 'Tänään', + monthNames: ['Tammikuu','Helmikuu','Maaliskuu','Huhtikuu','Toukokuu','Kesäkuu', + 'Heinäkuu','Elokuu','Syyskuu','Lokakuu','Marraskuu','Joulukuu'], + monthNamesShort: ['Tammi','Helmi','Maalis','Huhti','Touko','Kesä', + 'Heinä','Elo','Syys','Loka','Marras','Joulu'], + dayNamesShort: ['Su','Ma','Ti','Ke','To','Pe','La'], dayNames: ['Sunnuntai','Maanantai','Tiistai','Keskiviikko','Torstai','Perjantai','Lauantai'], dayNamesMin: ['Su','Ma','Ti','Ke','To','Pe','La'], weekHeader: 'Vk', - dateFormat: 'dd.mm.yy', + dateFormat: 'dd.mm.yy', firstDay: 1, isRTL: false, showMonthAfterYear: false, yearSuffix: ''}; - $.datepicker.setDefaults($.datepicker.regional['fi']); + $.datepicker.setDefaults($.datepicker.regional['fi']); }); diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-fr-CH.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-fr-CH.js deleted file mode 100644 index 244eacff..00000000 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-fr-CH.js +++ /dev/null @@ -1,23 +0,0 @@ -/* Swiss-French initialisation for the jQuery UI date picker plugin. */ -/* Written Martin Voelkle (martin.voelkle@e-tc.ch). */ -jQuery(function($){ - $.datepicker.regional['fr-CH'] = { - closeText: 'Fermer', - prevText: '<Préc', - nextText: 'Suiv>', - currentText: 'Courant', - monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin', - 'Juillet','Août','Septembre','Octobre','Novembre','Décembre'], - monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun', - 'Jul','Aoû','Sep','Oct','Nov','Déc'], - dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], - dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], - dayNamesMin: ['Di','Lu','Ma','Me','Je','Ve','Sa'], - weekHeader: 'Sm', - dateFormat: 'dd.mm.yy', - firstDay: 1, - isRTL: false, - showMonthAfterYear: false, - yearSuffix: ''}; - $.datepicker.setDefaults($.datepicker.regional['fr-CH']); -});
\ No newline at end of file diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-he.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-he.js index 88a78668..135cdec1 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-he.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-he.js @@ -8,8 +8,8 @@ jQuery(function($){ currentText: 'היום', monthNames: ['ינואר','פברואר','מרץ','אפריל','מאי','יוני', 'יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'], - monthNamesShort: ['1','2','3','4','5','6', - '7','8','9','10','11','12'], + monthNamesShort: ['ינו','פבר','מרץ','אפר','מאי','יוני', + 'יולי','אוג','ספט','אוק','נוב','דצמ'], dayNames: ['ראשון','שני','שלישי','רביעי','חמישי','שישי','שבת'], dayNamesShort: ['א\'','ב\'','ג\'','ד\'','ה\'','ו\'','שבת'], dayNamesMin: ['א\'','ב\'','ג\'','ד\'','ה\'','ו\'','שבת'], diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-hi.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-hi.js new file mode 100644 index 00000000..6c563b99 --- /dev/null +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-hi.js @@ -0,0 +1,23 @@ +/* Hindi initialisation for the jQuery UI date picker plugin. */ +/* Written by Michael Dawart. */ +jQuery(function($){ + $.datepicker.regional['hi'] = { + closeText: 'बंद', + prevText: 'पिछला', + nextText: 'अगला', + currentText: 'आज', + monthNames: ['जनवरी ','फरवरी','मार्च','अप्रेल','मई','जून', + 'जूलाई','अगस्त ','सितम्बर','अक्टूबर','नवम्बर','दिसम्बर'], + monthNamesShort: ['जन', 'फर', 'मार्च', 'अप्रेल', 'मई', 'जून', + 'जूलाई', 'अग', 'सित', 'अक्ट', 'नव', 'दि'], + dayNames: ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'], + dayNamesShort: ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + dayNamesMin: ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + weekHeader: 'हफ्ता', + dateFormat: 'dd/mm/yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['hi']); +}); diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-ka.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-ka.js new file mode 100644 index 00000000..c10658d7 --- /dev/null +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-ka.js @@ -0,0 +1,21 @@ +/* Georgian (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Lado Lomidze (lado.lomidze@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['ka'] = { + closeText: 'დახურვა', + prevText: '< წინა', + nextText: 'შემდეგი >', + currentText: 'დღეს', + monthNames: ['იანვარი','თებერვალი','მარტი','აპრილი','მაისი','ივნისი', 'ივლისი','აგვისტო','სექტემბერი','ოქტომბერი','ნოემბერი','დეკემბერი'], + monthNamesShort: ['იან','თებ','მარ','აპრ','მაი','ივნ', 'ივლ','აგვ','სექ','ოქტ','ნოე','დეკ'], + dayNames: ['კვირა','ორშაბათი','სამშაბათი','ოთხშაბათი','ხუთშაბათი','პარასკევი','შაბათი'], + dayNamesShort: ['კვ','ორშ','სამ','ოთხ','ხუთ','პარ','შაბ'], + dayNamesMin: ['კვ','ორშ','სამ','ოთხ','ხუთ','პარ','შაბ'], + weekHeader: 'კვირა', + dateFormat: 'dd-mm-yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['ka']); +}); diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-km.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-km.js new file mode 100644 index 00000000..f9c4e3a0 --- /dev/null +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-km.js @@ -0,0 +1,23 @@ +/* Khmer initialisation for the jQuery calendar extension. */ +/* Written by Chandara Om (chandara.teacher@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['km'] = { + closeText: 'ធ្វើរួច', + prevText: 'មុន', + nextText: 'បន្ទាប់', + currentText: 'ថ្ងៃនេះ', + monthNames: ['មករា','កុម្ភៈ','មីនា','មេសា','ឧសភា','មិថុនា', + 'កក្កដា','សីហា','កញ្ញា','តុលា','វិច្ឆិកា','ធ្នូ'], + monthNamesShort: ['មករា','កុម្ភៈ','មីនា','មេសា','ឧសភា','មិថុនា', + 'កក្កដា','សីហា','កញ្ញា','តុលា','វិច្ឆិកា','ធ្នូ'], + dayNames: ['អាទិត្យ', 'ចន្ទ', 'អង្គារ', 'ពុធ', 'ព្រហស្បតិ៍', 'សុក្រ', 'សៅរ៍'], + dayNamesShort: ['អា', 'ច', 'អ', 'ពុ', 'ព្រហ', 'សុ', 'សៅ'], + dayNamesMin: ['អា', 'ច', 'អ', 'ពុ', 'ព្រហ', 'សុ', 'សៅ'], + weekHeader: 'សប្ដាហ៍', + dateFormat: 'dd-mm-yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['km']); +}); diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-ko.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-ko.js index 5b353165..04112424 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-ko.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-ko.js @@ -1,23 +1,23 @@ /* Korean initialisation for the jQuery calendar extension. */ -/* Written by DaeKwon Kang (ncrash.dk@gmail.com). */ +/* Written by DaeKwon Kang (ncrash.dk@gmail.com), Edited by Genie. */ jQuery(function($){ $.datepicker.regional['ko'] = { closeText: '닫기', prevText: '이전달', nextText: '다음달', currentText: '오늘', - monthNames: ['1월(JAN)','2월(FEB)','3월(MAR)','4월(APR)','5월(MAY)','6월(JUN)', - '7월(JUL)','8월(AUG)','9월(SEP)','10월(OCT)','11월(NOV)','12월(DEC)'], - monthNamesShort: ['1월(JAN)','2월(FEB)','3월(MAR)','4월(APR)','5월(MAY)','6월(JUN)', - '7월(JUL)','8월(AUG)','9월(SEP)','10월(OCT)','11월(NOV)','12월(DEC)'], - dayNames: ['일','월','화','수','목','금','토'], + monthNames: ['1월','2월','3월','4월','5월','6월', + '7월','8월','9월','10월','11월','12월'], + monthNamesShort: ['1월','2월','3월','4월','5월','6월', + '7월','8월','9월','10월','11월','12월'], + dayNames: ['일요일','월요일','화요일','수요일','목요일','금요일','토요일'], dayNamesShort: ['일','월','화','수','목','금','토'], dayNamesMin: ['일','월','화','수','목','금','토'], weekHeader: 'Wk', dateFormat: 'yy-mm-dd', firstDay: 0, isRTL: false, - showMonthAfterYear: false, + showMonthAfterYear: true, yearSuffix: '년'}; $.datepicker.setDefaults($.datepicker.regional['ko']); });
\ No newline at end of file diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-mk.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-mk.js index 554ad20b..02853255 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-mk.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-mk.js @@ -6,15 +6,15 @@ jQuery(function($){ prevText: '<', nextText: '>', currentText: 'Денес', - monthNames: ['Јануари','Фебруари','Март','Април','Мај','Јуни', + monthNames: ['Јануари','Февруари','Март','Април','Мај','Јуни', 'Јули','Август','Септември','Октомври','Ноември','Декември'], - monthNamesShort: ['Јан','Феб','Мар','Апр','Мај','Јун', + monthNamesShort: ['Јан','Фев','Мар','Апр','Мај','Јун', 'Јул','Авг','Сеп','Окт','Ное','Дек'], dayNames: ['Недела','Понеделник','Вторник','Среда','Четврток','Петок','Сабота'], dayNamesShort: ['Нед','Пон','Вто','Сре','Чет','Пет','Саб'], dayNamesMin: ['Не','По','Вт','Ср','Че','Пе','Са'], weekHeader: 'Сед', - dateFormat: 'dd/mm/yy', + dateFormat: 'dd.mm.yy', firstDay: 1, isRTL: false, showMonthAfterYear: false, diff --git a/resources/jquery.ui/jquery.ui.accordion.js b/resources/jquery.ui/jquery.ui.accordion.js index 8fa8c0b8..b3340e09 100644 --- a/resources/jquery.ui/jquery.ui.accordion.js +++ b/resources/jquery.ui/jquery.ui.accordion.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Accordion 1.8.17 +/*! + * jQuery UI Accordion 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -518,7 +518,7 @@ $.widget( "ui.accordion", { }); $.extend( $.ui.accordion, { - version: "1.8.17", + version: "1.8.23", animations: { slide: function( options, additions ) { options = $.extend({ diff --git a/resources/jquery.ui/jquery.ui.autocomplete.js b/resources/jquery.ui/jquery.ui.autocomplete.js index 48ede20b..b634cce5 100644 --- a/resources/jquery.ui/jquery.ui.autocomplete.js +++ b/resources/jquery.ui/jquery.ui.autocomplete.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Autocomplete 1.8.17 +/*! + * jQuery UI Autocomplete 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -37,6 +37,7 @@ $.widget( "ui.autocomplete", { var self = this, doc = this.element[ 0 ].ownerDocument, suppressKeyPress; + this.isMultiLine = this.element.is( "textarea" ); this.element .addClass( "ui-autocomplete-input" ) @@ -62,14 +63,10 @@ $.widget( "ui.autocomplete", { self._move( "nextPage", event ); break; case keyCode.UP: - self._move( "previous", event ); - // prevent moving cursor to beginning of text field in some browsers - event.preventDefault(); + self._keyEvent( "previous", event ); break; case keyCode.DOWN: - self._move( "next", event ); - // prevent moving cursor to end of text field in some browsers - event.preventDefault(); + self._keyEvent( "next", event ); break; case keyCode.ENTER: case keyCode.NUMPAD_ENTER: @@ -131,9 +128,6 @@ $.widget( "ui.autocomplete", { }, 150 ); }); this._initSource(); - this.response = function() { - return self._response.apply( self, arguments ); - }; this.menu = $( "<ul></ul>" ) .addClass( "ui-autocomplete" ) .appendTo( $( this.options.appendTo || "body", doc )[0] ) @@ -268,16 +262,11 @@ $.widget( "ui.autocomplete", { url: url, data: request, dataType: "json", - autocompleteRequest: ++requestIndex, success: function( data, status ) { - if ( this.autocompleteRequest === requestIndex ) { - response( data ); - } + response( data ); }, error: function() { - if ( this.autocompleteRequest === requestIndex ) { - response( [] ); - } + response( [] ); } }); }; @@ -308,10 +297,26 @@ $.widget( "ui.autocomplete", { this.pending++; this.element.addClass( "ui-autocomplete-loading" ); - this.source( { term: value }, this.response ); + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var that = this, + index = ++requestIndex; + + return function( content ) { + if ( index === requestIndex ) { + that.__response( content ); + } + + that.pending--; + if ( !that.pending ) { + that.element.removeClass( "ui-autocomplete-loading" ); + } + }; }, - _response: function( content ) { + __response: function( content ) { if ( !this.options.disabled && content && content.length ) { content = this._normalize( content ); this._suggest( content ); @@ -319,10 +324,6 @@ $.widget( "ui.autocomplete", { } else { this.close(); } - this.pending--; - if ( !this.pending ) { - this.element.removeClass( "ui-autocomplete-loading" ); - } }, close: function( event ) { @@ -420,6 +421,14 @@ $.widget( "ui.autocomplete", { widget: function() { return this.menu.element; + }, + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } } }); @@ -592,7 +601,7 @@ $.widget("ui.menu", { } var base = this.active.offset().top, - height = this.element.height(); + height = this.element.height(), result = this.element.children(".ui-menu-item").filter(function() { var close = $(this).offset().top - base + height - $(this).height(); // TODO improve approximation diff --git a/resources/jquery.ui/jquery.ui.button.js b/resources/jquery.ui/jquery.ui.button.js index 94dce1c3..db2637e8 100644 --- a/resources/jquery.ui/jquery.ui.button.js +++ b/resources/jquery.ui/jquery.ui.button.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Button 1.8.17 +/*! + * jQuery UI Button 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -56,7 +56,9 @@ $.widget( "ui.button", { .bind( "reset.button", formResetHandler ); if ( typeof this.options.disabled !== "boolean" ) { - this.options.disabled = this.element.propAttr( "disabled" ); + this.options.disabled = !!this.element.propAttr( "disabled" ); + } else { + this.element.propAttr( "disabled", this.options.disabled ); } this._determineButtonType(); @@ -72,10 +74,6 @@ $.widget( "ui.button", { options.label = this.buttonElement.html(); } - if ( this.element.is( ":disabled" ) ) { - options.disabled = true; - } - this.buttonElement .addClass( baseClasses ) .attr( "role", "button" ) diff --git a/resources/jquery.ui/jquery.ui.core.js b/resources/jquery.ui/jquery.ui.core.js index 6d82ff13..1285a6dd 100644 --- a/resources/jquery.ui/jquery.ui.core.js +++ b/resources/jquery.ui/jquery.ui.core.js @@ -1,7 +1,7 @@ /*! - * jQuery UI 1.8.17 + * jQuery UI 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -18,7 +18,7 @@ if ( $.ui.version ) { } $.extend( $.ui, { - version: "1.8.17", + version: "1.8.23", keyCode: { ALT: 18, @@ -131,49 +131,52 @@ $.fn.extend({ } }); -$.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; +// support: jQuery <1.8 +if ( !$( "<a>" ).outerWidth( 1 ).jquery ) { + $.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; + if ( border ) { + size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; + } + }); + return size; + } - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; - if ( border ) { - size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); } - }); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - return this.each(function() { - $( this ).css( type, reduce( this, size ) + "px" ); - }); - }; + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; - $.fn[ "outer" + name] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } - return this.each(function() { - $( this).css( type, reduce( this, size, true, margin ) + "px" ); - }); - }; -}); + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; + }); +} // selectors function focusable( element, isTabIndexNotNaN ) { @@ -205,9 +208,16 @@ function visible( element ) { } $.extend( $.expr[ ":" ], { - data: function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - }, + data: $.expr.createPseudo ? + $.expr.createPseudo(function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + }) : + // support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, focusable: function( element ) { return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); @@ -225,6 +235,11 @@ $(function() { var body = document.body, div = body.appendChild( div = document.createElement( "div" ) ); + // access offsetHeight before setting the style to prevent a layout bug + // in IE 9 which causes the elemnt to continue to take up space even + // after it is removed from the DOM (#8026) + div.offsetHeight; + $.extend( div.style, { minHeight: "100px", height: "auto", @@ -240,6 +255,11 @@ $(function() { body.removeChild( div ).style.display = "none"; }); +// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css +if ( !$.curCSS ) { + $.curCSS = $.css; +} + diff --git a/resources/jquery.ui/jquery.ui.datepicker.js b/resources/jquery.ui/jquery.ui.datepicker.js index bbf59175..7ea5b079 100644 --- a/resources/jquery.ui/jquery.ui.datepicker.js +++ b/resources/jquery.ui/jquery.ui.datepicker.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Datepicker 1.8.17 +/*! + * jQuery UI Datepicker 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -12,7 +12,7 @@ */ (function( $, undefined ) { -$.extend($.ui, { datepicker: { version: "1.8.17" } }); +$.extend($.ui, { datepicker: { version: "1.8.23" } }); var PROP_NAME = 'datepicker'; var dpuuid = new Date().getTime(); @@ -233,7 +233,10 @@ $.extend(Datepicker.prototype, { inst.trigger.click(function() { if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0]) $.datepicker._hideDatepicker(); - else + else if ($.datepicker._datepickerShowing && $.datepicker._lastInput != input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else $.datepicker._showDatepicker(input[0]); return false; }); @@ -305,7 +308,7 @@ $.extend(Datepicker.prototype, { this.uuid += 1; var id = 'dp' + this.uuid; this._dialogInput = $('<input type="text" id="' + id + - '" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'); + '" style="position: absolute; top: -100px; width: 0px;"/>'); this._dialogInput.keydown(this._doKeyDown); $('body').append(this._dialogInput); inst = this._dialogInst = this._newInst(this._dialogInput, false); @@ -617,8 +620,8 @@ $.extend(Datepicker.prototype, { $.datepicker._updateDatepicker(inst); } } - catch (event) { - $.datepicker.log(event); + catch (err) { + $.datepicker.log(err); } } return true; @@ -711,6 +714,7 @@ $.extend(Datepicker.prototype, { var borders = $.datepicker._getBorders(inst.dpDiv); instActive = inst; // for delegate hover events inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6 cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()}) @@ -761,8 +765,8 @@ $.extend(Datepicker.prototype, { var dpHeight = inst.dpDiv.outerHeight(); var inputWidth = inst.input ? inst.input.outerWidth() : 0; var inputHeight = inst.input ? inst.input.outerHeight() : 0; - var viewWidth = document.documentElement.clientWidth + $(document).scrollLeft(); - var viewHeight = document.documentElement.clientHeight + $(document).scrollTop(); + var viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()); + var viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0); offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0; @@ -797,10 +801,8 @@ $.extend(Datepicker.prototype, { if (this._datepickerShowing) { var showAnim = this._get(inst, 'showAnim'); var duration = this._get(inst, 'duration'); - var self = this; var postProcess = function() { $.datepicker._tidyDialog(inst); - self._curInst = null; }; if ($.effects && $.effects[showAnim]) inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess); @@ -842,7 +844,7 @@ $.extend(Datepicker.prototype, { if ( ( ( $target[0].id != $.datepicker._mainDivId && $target.parents('#' + $.datepicker._mainDivId).length == 0 && !$target.hasClass($.datepicker.markerClassName) && - !$target.hasClass($.datepicker._triggerClass) && + !$target.closest("." + $.datepicker._triggerClass).length && $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) ) $.datepicker._hideDatepicker(); @@ -1401,6 +1403,43 @@ $.extend(Datepicker.prototype, { return startDate; }, + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function(inst) { + var stepMonths = this._get(inst, 'stepMonths'); + var id = '#' + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find('[data-handler]').map(function () { + var handler = { + prev: function () { + window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, -stepMonths, 'M'); + }, + next: function () { + window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, +stepMonths, 'M'); + }, + hide: function () { + window['DP_jQuery_' + dpuuid].datepicker._hideDatepicker(); + }, + today: function () { + window['DP_jQuery_' + dpuuid].datepicker._gotoToday(id); + }, + selectDay: function () { + window['DP_jQuery_' + dpuuid].datepicker._selectDay(id, +this.getAttribute('data-month'), +this.getAttribute('data-year'), this); + return false; + }, + selectMonth: function () { + window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'M'); + return false; + }, + selectYear: function () { + window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'Y'); + return false; + } + }; + $(this).bind(this.getAttribute('data-event'), handler[this.getAttribute('data-handler')]); + }); + }, + /* Generate the HTML for the current state of the date picker. */ _generateHTML: function(inst) { var today = new Date(); @@ -1443,8 +1482,7 @@ $.extend(Datepicker.prototype, { this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), this._getFormatConfig(inst))); var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? - '<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_' + dpuuid + - '.datepicker._adjustDate(\'#' + inst.id + '\', -' + stepMonths + ', \'M\');"' + + '<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' + ' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' : (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>')); var nextText = this._get(inst, 'nextText'); @@ -1452,19 +1490,17 @@ $.extend(Datepicker.prototype, { this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), this._getFormatConfig(inst))); var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? - '<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_' + dpuuid + - '.datepicker._adjustDate(\'#' + inst.id + '\', +' + stepMonths + ', \'M\');"' + + '<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' + ' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' : (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>')); var currentText = this._get(inst, 'currentText'); var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today); currentText = (!navigationAsDateFormat ? currentText : this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); - var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_' + dpuuid + - '.datepicker._hideDatepicker();">' + this._get(inst, 'closeText') + '</button>' : ''); + var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">' + + this._get(inst, 'closeText') + '</button>' : ''); var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') + - (this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_' + dpuuid + - '.datepicker._gotoToday(\'#' + inst.id + '\');"' + + (this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' + '>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : ''; var firstDay = parseInt(this._get(inst, 'firstDay'),10); firstDay = (isNaN(firstDay) ? 0 : firstDay); @@ -1543,8 +1579,7 @@ $.extend(Datepicker.prototype, { (printDate.getTime() == currentDate.getTime() ? ' ' + this._currentClass : '') + // highlight selected day (printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different) ((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title - (unselectable ? '' : ' onclick="DP_jQuery_' + dpuuid + '.datepicker._selectDay(\'#' + - inst.id + '\',' + printDate.getMonth() + ',' + printDate.getFullYear() + ', this);return false;"') + '>' + // actions + (unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' + // actions (otherMonth && !showOtherMonths ? ' ' : // display for other months (unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' + (printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') + @@ -1587,9 +1622,7 @@ $.extend(Datepicker.prototype, { else { var inMinYear = (minDate && minDate.getFullYear() == drawYear); var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear); - monthHtml += '<select class="ui-datepicker-month" ' + - 'onchange="DP_jQuery_' + dpuuid + '.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'M\');" ' + - '>'; + monthHtml += '<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">'; for (var month = 0; month < 12; month++) { if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) @@ -1620,9 +1653,7 @@ $.extend(Datepicker.prototype, { var endYear = Math.max(year, determineYear(years[1] || '')); year = (minDate ? Math.max(year, minDate.getFullYear()) : year); endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); - inst.yearshtml += '<select class="ui-datepicker-year" ' + - 'onchange="DP_jQuery_' + dpuuid + '.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'Y\');" ' + - '>'; + inst.yearshtml += '<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">'; for (; year <= endYear; year++) { inst.yearshtml += '<option value="' + year + '"' + (year == drawYear ? ' selected="selected"' : '') + @@ -1814,7 +1845,7 @@ $.fn.datepicker = function(options){ $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); -$.datepicker.version = "1.8.17"; +$.datepicker.version = "1.8.23"; // Workaround for #4055 // Add another global to avoid noConflict issues with inline event handlers diff --git a/resources/jquery.ui/jquery.ui.dialog.js b/resources/jquery.ui/jquery.ui.dialog.js index 33119af2..082bf2c0 100644 --- a/resources/jquery.ui/jquery.ui.dialog.js +++ b/resources/jquery.ui/jquery.ui.dialog.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Dialog 1.8.17 +/*! + * jQuery UI Dialog 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -37,18 +37,6 @@ var uiDialogClasses = maxWidth: true, minHeight: true, minWidth: true - }, - // support for jQuery 1.3.2 - handle common attrFn methods for dialog - attrFn = $.attrFn || { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true, - click: true }; $.widget("ui.dialog", { @@ -397,7 +385,7 @@ $.widget("ui.dialog", { if ( key === "click" ) { return; } - if ( key in attrFn ) { + if ( key in button ) { button[ key ]( value ); } else { button.attr( key, value ); @@ -702,7 +690,7 @@ $.widget("ui.dialog", { }); $.extend($.ui.dialog, { - version: "1.8.17", + version: "1.8.23", uuid: 0, maxZ: 0, diff --git a/resources/jquery.ui/jquery.ui.draggable.js b/resources/jquery.ui/jquery.ui.draggable.js index 41aba9d6..6da1aafe 100644 --- a/resources/jquery.ui/jquery.ui.draggable.js +++ b/resources/jquery.ui/jquery.ui.draggable.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Draggable 1.8.17 +/*! + * jQuery UI Draggable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -103,6 +103,8 @@ $.widget("ui.draggable", $.ui.mouse, { //Create and append the visible helper this.helper = this._createHelper(event); + this.helper.addClass("ui-draggable-dragging"); + //Cache the helper size this._cacheHelperProportions(); @@ -163,7 +165,7 @@ $.widget("ui.draggable", $.ui.mouse, { if ($.ui.ddmanager && !o.dropBehaviour) $.ui.ddmanager.prepareOffsets(this, event); - this.helper.addClass("ui-draggable-dragging"); + this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) @@ -208,8 +210,14 @@ $.widget("ui.draggable", $.ui.mouse, { this.dropped = false; } - //if the original element is removed, don't bother to continue if helper is set to "original" - if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original") + //if the original element is no longer in the DOM don't bother to continue (see #8269) + var element = this.element[0], elementInDom = false; + while ( element && (element = element.parentNode) ) { + if (element == document ) { + elementInDom = true; + } + } + if ( !elementInDom && this.options.helper === "original" ) return false; if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { @@ -505,7 +513,7 @@ $.widget("ui.draggable", $.ui.mouse, { }); $.extend($.ui.draggable, { - version: "1.8.17" + version: "1.8.23" }); $.ui.plugin.add("draggable", "connectToSortable", { diff --git a/resources/jquery.ui/jquery.ui.droppable.js b/resources/jquery.ui/jquery.ui.droppable.js index afce00aa..4b98b3ad 100644 --- a/resources/jquery.ui/jquery.ui.droppable.js +++ b/resources/jquery.ui/jquery.ui.droppable.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Droppable 1.8.17 +/*! + * jQuery UI Droppable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -147,7 +147,7 @@ $.widget("ui.droppable", { }); $.extend($.ui.droppable, { - version: "1.8.17" + version: "1.8.23" }); $.ui.intersect = function(draggable, droppable, toleranceMode) { diff --git a/resources/jquery.ui/jquery.ui.mouse.js b/resources/jquery.ui/jquery.ui.mouse.js index 6bb65de0..e051055d 100644 --- a/resources/jquery.ui/jquery.ui.mouse.js +++ b/resources/jquery.ui/jquery.ui.mouse.js @@ -1,7 +1,7 @@ /*! - * jQuery UI Mouse 1.8.17 + * jQuery UI Mouse 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -45,6 +45,11 @@ $.widget("ui.mouse", { // other instances of mouse _mouseDestroy: function() { this.element.unbind('.'+this.widgetName); + if ( this._mouseMoveDelegate ) { + $(document) + .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); + } }, _mouseDown: function(event) { diff --git a/resources/jquery.ui/jquery.ui.position.js b/resources/jquery.ui/jquery.ui.position.js index 8eb30f62..03f2606c 100644 --- a/resources/jquery.ui/jquery.ui.position.js +++ b/resources/jquery.ui/jquery.ui.position.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Position 1.8.17 +/*! + * jQuery UI Position 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -243,7 +243,12 @@ if ( !$.offset.setOffset ) { $.fn.offset = function( options ) { var elem = this[ 0 ]; if ( !elem || !elem.ownerDocument ) { return null; } - if ( options ) { + if ( options ) { + if ( $.isFunction( options ) ) { + return this.each(function( i ) { + $( this ).offset( options.call( this, i, $( this ).offset() ) ); + }); + } return this.each(function() { $.offset.setOffset( this, options ); }); @@ -252,6 +257,11 @@ if ( !$.offset.setOffset ) { }; } +// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css +if ( !$.curCSS ) { + $.curCSS = $.css; +} + // fraction support test (older versions of jQuery don't support fractions) (function () { var body = document.getElementsByTagName( "body" )[ 0 ], @@ -269,7 +279,7 @@ if ( !$.offset.setOffset ) { background: "none" }; if ( body ) { - jQuery.extend( testElementStyle, { + $.extend( testElementStyle, { position: "absolute", left: "-1000px", top: "-1000px" diff --git a/resources/jquery.ui/jquery.ui.progressbar.js b/resources/jquery.ui/jquery.ui.progressbar.js index 27bae872..c1d9f3c2 100644 --- a/resources/jquery.ui/jquery.ui.progressbar.js +++ b/resources/jquery.ui/jquery.ui.progressbar.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Progressbar 1.8.17 +/*! + * jQuery UI Progressbar 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -103,7 +103,7 @@ $.widget( "ui.progressbar", { }); $.extend( $.ui.progressbar, { - version: "1.8.17" + version: "1.8.23" }); })( jQuery ); diff --git a/resources/jquery.ui/jquery.ui.resizable.js b/resources/jquery.ui/jquery.ui.resizable.js index 52b1fc4f..f6ce6949 100644 --- a/resources/jquery.ui/jquery.ui.resizable.js +++ b/resources/jquery.ui/jquery.ui.resizable.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Resizable 1.8.17 +/*! + * jQuery UI Resizable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -50,10 +50,6 @@ $.widget("ui.resizable", $.ui.mouse, { //Wrap the element if it cannot hold child nodes if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { - //Opera fix for relative positioning - if (/relative/.test(this.element.css('position')) && $.browser.opera) - this.element.css({ position: 'relative', top: 'auto', left: 'auto' }); - //Create a wrapper element and set the wrapper to the new current internal element this.element.wrap( $('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({ @@ -102,9 +98,8 @@ $.widget("ui.resizable", $.ui.mouse, { var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle; var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>'); - // increase zIndex of sw, se, ne, nw axis - //TODO : this modifies original option - if(/sw|se|ne|nw/.test(handle)) axis.css({ zIndex: ++o.zIndex }); + // Apply zIndex to all handles - see #7960 + axis.css({ zIndex: o.zIndex }); //TODO : What's going on here? if ('se' == handle) { @@ -247,10 +242,6 @@ $.widget("ui.resizable", $.ui.mouse, { el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left }); } - //Opera fixing relative position - if ($.browser.opera && (/relative/).test(el.css('position'))) - el.css({ position: 'relative', top: 'auto', left: 'auto' }); - this._renderProxy(); var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top')); @@ -549,7 +540,7 @@ $.widget("ui.resizable", $.ui.mouse, { }); $.extend($.ui.resizable, { - version: "1.8.17" + version: "1.8.23" }); /* @@ -566,8 +557,7 @@ $.ui.plugin.add("resizable", "alsoResize", { var el = $(this); el.data("resizable-alsoresize", { width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), - left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10), - position: el.css('position') // to reset Opera on stop() + left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10) }); }); }; @@ -599,12 +589,6 @@ $.ui.plugin.add("resizable", "alsoResize", { style[prop] = sum || null; }); - // Opera fixing relative position - if ($.browser.opera && /relative/.test(el.css('position'))) { - self._revertToRelativePosition = true; - el.css({ position: 'absolute', top: 'auto', left: 'auto' }); - } - el.css(style); }); }; @@ -617,25 +601,6 @@ $.ui.plugin.add("resizable", "alsoResize", { }, stop: function (event, ui) { - var self = $(this).data("resizable"), o = self.options; - - var _reset = function (exp) { - $(exp).each(function() { - var el = $(this); - // reset position for Opera - no need to verify it was changed - el.css({ position: el.data("resizable-alsoresize").position }); - }); - }; - - if (self._revertToRelativePosition) { - self._revertToRelativePosition = false; - if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) { - $.each(o.alsoResize, function (exp) { _reset(exp); }); - }else{ - _reset(o.alsoResize); - } - } - $(this).removeData("resizable-alsoresize"); } }); @@ -725,13 +690,13 @@ $.ui.plugin.add("resizable", "containment", { if (cp.left < (self._helper ? co.left : 0)) { self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left)); - if (pRatio) self.size.height = self.size.width / o.aspectRatio; + if (pRatio) self.size.height = self.size.width / self.aspectRatio; self.position.left = o.helper ? co.left : 0; } if (cp.top < (self._helper ? co.top : 0)) { self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top); - if (pRatio) self.size.width = self.size.height * o.aspectRatio; + if (pRatio) self.size.width = self.size.height * self.aspectRatio; self.position.top = self._helper ? co.top : 0; } diff --git a/resources/jquery.ui/jquery.ui.selectable.js b/resources/jquery.ui/jquery.ui.selectable.js index d790ac0d..ac5bf046 100644 --- a/resources/jquery.ui/jquery.ui.selectable.js +++ b/resources/jquery.ui/jquery.ui.selectable.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Selectable 1.8.17 +/*! + * jQuery UI Selectable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -261,7 +261,7 @@ $.widget("ui.selectable", $.ui.mouse, { }); $.extend($.ui.selectable, { - version: "1.8.17" + version: "1.8.23" }); })(jQuery); diff --git a/resources/jquery.ui/jquery.ui.slider.js b/resources/jquery.ui/jquery.ui.slider.js index 53fdc42a..5ea589e6 100644 --- a/resources/jquery.ui/jquery.ui.slider.js +++ b/resources/jquery.ui/jquery.ui.slider.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Slider 1.8.17 +/*! + * jQuery UI Slider 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -115,8 +115,7 @@ $.widget( "ui.slider", $.ui.mouse, { this.handles .keydown(function( event ) { - var ret = true, - index = $( this ).data( "index.ui-slider-handle" ), + var index = $( this ).data( "index.ui-slider-handle" ), allowed, curVal, newVal, @@ -135,7 +134,7 @@ $.widget( "ui.slider", $.ui.mouse, { case $.ui.keyCode.RIGHT: case $.ui.keyCode.DOWN: case $.ui.keyCode.LEFT: - ret = false; + event.preventDefault(); if ( !self._keySliding ) { self._keySliding = true; $( this ).addClass( "ui-state-active" ); @@ -184,9 +183,6 @@ $.widget( "ui.slider", $.ui.mouse, { } self._slide( event, index, newVal ); - - return ret; - }) .keyup(function( event ) { var index = $( this ).data( "index.ui-slider-handle" ); @@ -660,7 +656,7 @@ $.widget( "ui.slider", $.ui.mouse, { }); $.extend( $.ui.slider, { - version: "1.8.17" + version: "1.8.23" }); }(jQuery)); diff --git a/resources/jquery.ui/jquery.ui.sortable.js b/resources/jquery.ui/jquery.ui.sortable.js index 26ba85f0..1d87f653 100644 --- a/resources/jquery.ui/jquery.ui.sortable.js +++ b/resources/jquery.ui/jquery.ui.sortable.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Sortable 1.8.17 +/*! + * jQuery UI Sortable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -16,6 +16,7 @@ $.widget("ui.sortable", $.ui.mouse, { widgetEventPrefix: "sort", + ready: false, options: { appendTo: "parent", axis: false, @@ -57,10 +58,14 @@ $.widget("ui.sortable", $.ui.mouse, { //Initialize mouse events for interaction this._mouseInit(); + + //We're ready to go + this.ready = true }, destroy: function() { + $.Widget.prototype.destroy.call( this ); this.element .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); @@ -150,11 +155,6 @@ $.widget("ui.sortable", $.ui.mouse, { left: this.offset.left - this.margins.left }; - // Only after we got the offset, we can change the helper's position to absolute - // TODO: Still need to figure out a way to make relative sorting possible - this.helper.css("position", "absolute"); - this.cssPosition = this.helper.css("position"); - $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, @@ -164,6 +164,11 @@ $.widget("ui.sortable", $.ui.mouse, { relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); + // Only after we got the offset, we can change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css("position", "absolute"); + this.cssPosition = this.helper.css("position"); + //Generate the original position this.originalPosition = this._generatePosition(event); this.originalPageX = event.pageX; @@ -463,8 +468,8 @@ $.widget("ui.sortable", $.ui.mouse, { _intersectsWithPointer: function(item) { - var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), - isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), + var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), + isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), isOverElement = isOverElementHeight && isOverElementWidth, verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); @@ -571,7 +576,7 @@ $.widget("ui.sortable", $.ui.mouse, { var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]]; var connectWith = this._connectWith(); - if(connectWith) { + if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down for (var i = connectWith.length - 1; i >= 0; i--){ var cur = $(connectWith[i]); for (var j = cur.length - 1; j >= 0; j--){ @@ -725,30 +730,31 @@ $.widget("ui.sortable", $.ui.mouse, { if(this.containers.length === 1) { this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; - } else if(this.currentContainer != this.containers[innermostIndex]) { - - //When entering a new container, we will find the item with the least distance and append our item near it - var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; - for (var j = this.items.length - 1; j >= 0; j--) { - if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; - var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; - if(Math.abs(cur - base) < dist) { - dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; - } - } - - if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled - return; - - this.currentContainer = this.containers[innermostIndex]; - itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); - this._trigger("change", event, this._uiHash()); - this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); - - //Update the placeholder - this.options.placeholder.update(this.currentContainer, this.placeholder); - - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + } else if(this.currentContainer != this.containers[innermostIndex]) { + + //When entering a new container, we will find the item with the least distance and append our item near it + var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; + for (var j = this.items.length - 1; j >= 0; j--) { + if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; + var cur = this.containers[innermostIndex].floating ? this.items[j].item.offset().left : this.items[j].item.offset().top; + if(Math.abs(cur - base) < dist) { + dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; + this.direction = (cur - base > 0) ? 'down' : 'up'; + } + } + + if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled + return; + + this.currentContainer = this.containers[innermostIndex]; + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } @@ -1028,6 +1034,8 @@ $.widget("ui.sortable", $.ui.mouse, { for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } + + this.fromOutside = false; return false; } @@ -1070,7 +1078,7 @@ $.widget("ui.sortable", $.ui.mouse, { }); $.extend($.ui.sortable, { - version: "1.8.17" + version: "1.8.23" }); })(jQuery); diff --git a/resources/jquery.ui/jquery.ui.tabs.js b/resources/jquery.ui/jquery.ui.tabs.js index ff7938ce..de453cc3 100644 --- a/resources/jquery.ui/jquery.ui.tabs.js +++ b/resources/jquery.ui/jquery.ui.tabs.js @@ -1,7 +1,7 @@ -/* - * jQuery UI Tabs 1.8.17 +/*! + * jQuery UI Tabs 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -427,7 +427,7 @@ $.widget( "ui.tabs", { // meta-function to give users option to provide a href string instead of a numerical index. // also sanitizes numerical indexes to valid values. if ( typeof index == "string" ) { - index = this.anchors.index( this.anchors.filter( "[href$=" + index + "]" ) ); + index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); } return index; @@ -698,7 +698,7 @@ $.widget( "ui.tabs", { }); $.extend( $.ui.tabs, { - version: "1.8.17" + version: "1.8.23" }); /* @@ -733,7 +733,6 @@ $.extend( $.ui.tabs.prototype, { } } : function( e ) { - t = o.selected; rotate(); }); diff --git a/resources/jquery.ui/jquery.ui.widget.js b/resources/jquery.ui/jquery.ui.widget.js index facd77b3..befdcc25 100644 --- a/resources/jquery.ui/jquery.ui.widget.js +++ b/resources/jquery.ui/jquery.ui.widget.js @@ -1,7 +1,7 @@ /*! - * jQuery UI Widget 1.8.17 + * jQuery UI Widget 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.accordion.css b/resources/jquery.ui/themes/default/jquery.ui.accordion.css index e426c762..1ce7d5ea 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.accordion.css +++ b/resources/jquery.ui/themes/default/jquery.ui.accordion.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Accordion 1.8.17 +/*! + * jQuery UI Accordion 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css b/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css index 4613938a..a9817ceb 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css +++ b/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Autocomplete 1.8.17 +/*! + * jQuery UI Autocomplete 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -13,7 +13,7 @@ * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ /* - * jQuery UI Menu 1.8.17 + * jQuery UI Menu 1.8.23 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/themes/default/jquery.ui.button.css b/resources/jquery.ui/themes/default/jquery.ui.button.css index a7565af8..c1f26009 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.button.css +++ b/resources/jquery.ui/themes/default/jquery.ui.button.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Button 1.8.17 +/*! + * jQuery UI Button 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.core.css b/resources/jquery.ui/themes/default/jquery.ui.core.css index bde176d2..c24627e7 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.core.css +++ b/resources/jquery.ui/themes/default/jquery.ui.core.css @@ -1,7 +1,7 @@ -/* - * jQuery UI CSS Framework 1.8.17 +/*! + * jQuery UI CSS Framework 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.datepicker.css b/resources/jquery.ui/themes/default/jquery.ui.datepicker.css index 6cb5aefc..0282eeee 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.datepicker.css +++ b/resources/jquery.ui/themes/default/jquery.ui.datepicker.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Datepicker 1.8.17 +/*! + * jQuery UI Datepicker 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * @@ -56,8 +56,6 @@ /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ .ui-datepicker-cover { - display: none; /*sorry for IE5*/ - display/**/: block; /*sorry for IE5*/ position: absolute; /*must have*/ z-index: -1; /*must have*/ filter: mask(); /*must have*/ diff --git a/resources/jquery.ui/themes/default/jquery.ui.dialog.css b/resources/jquery.ui/themes/default/jquery.ui.dialog.css index a5914ff1..ba50ba5a 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.dialog.css +++ b/resources/jquery.ui/themes/default/jquery.ui.dialog.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Dialog 1.8.17 +/*! + * jQuery UI Dialog 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.progressbar.css b/resources/jquery.ui/themes/default/jquery.ui.progressbar.css index 630fab39..c775a33a 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.progressbar.css +++ b/resources/jquery.ui/themes/default/jquery.ui.progressbar.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Progressbar 1.8.17 +/*! + * jQuery UI Progressbar 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.resizable.css b/resources/jquery.ui/themes/default/jquery.ui.resizable.css index 582bcb31..420c4afa 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.resizable.css +++ b/resources/jquery.ui/themes/default/jquery.ui.resizable.css @@ -1,14 +1,14 @@ -/* - * jQuery UI Resizable 1.8.17 +/*! + * jQuery UI Resizable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Resizable#theming */ .ui-resizable { position: relative;} -.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; } +.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; } .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } diff --git a/resources/jquery.ui/themes/default/jquery.ui.selectable.css b/resources/jquery.ui/themes/default/jquery.ui.selectable.css index b36b8988..33202748 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.selectable.css +++ b/resources/jquery.ui/themes/default/jquery.ui.selectable.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Selectable 1.8.17 +/*! + * jQuery UI Selectable 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.slider.css b/resources/jquery.ui/themes/default/jquery.ui.slider.css index 4cc54810..650ad7e2 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.slider.css +++ b/resources/jquery.ui/themes/default/jquery.ui.slider.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Slider 1.8.17 +/*! + * jQuery UI Slider 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.tabs.css b/resources/jquery.ui/themes/default/jquery.ui.tabs.css index fb827458..64ac9bf5 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.tabs.css +++ b/resources/jquery.ui/themes/default/jquery.ui.tabs.css @@ -1,7 +1,7 @@ -/* - * jQuery UI Tabs 1.8.17 +/*! + * jQuery UI Tabs 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/default/jquery.ui.theme.css b/resources/jquery.ui/themes/default/jquery.ui.theme.css index d0c93afb..536c8e0e 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.theme.css +++ b/resources/jquery.ui/themes/default/jquery.ui.theme.css @@ -1,7 +1,7 @@ -/* - * jQuery UI CSS Framework 1.8.17 +/*! + * jQuery UI CSS Framework 1.8.23 * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * diff --git a/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png b/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png Binary files differnew file mode 100644 index 00000000..8f7cf74e --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-blue-hover.png b/resources/jquery.ui/themes/vector/images/button-blue-hover.png Binary files differnew file mode 100644 index 00000000..1fc28163 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-blue-hover.png diff --git a/resources/jquery.ui/themes/vector/images/button-blue-large.png b/resources/jquery.ui/themes/vector/images/button-blue-large.png Binary files differnew file mode 100644 index 00000000..22ee5e59 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-blue-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-blue.png b/resources/jquery.ui/themes/vector/images/button-blue.png Binary files differnew file mode 100644 index 00000000..2e6d121a --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-blue.png diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-blue.png b/resources/jquery.ui/themes/vector/images/button-disabled-blue.png Binary files differindex 6242aca3..28eb1fcd 100644 --- a/resources/jquery.ui/themes/vector/images/button-disabled-blue.png +++ b/resources/jquery.ui/themes/vector/images/button-disabled-blue.png diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-green.png b/resources/jquery.ui/themes/vector/images/button-disabled-green.png Binary files differindex 16209389..0e29d85b 100644 --- a/resources/jquery.ui/themes/vector/images/button-disabled-green.png +++ b/resources/jquery.ui/themes/vector/images/button-disabled-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-red.png b/resources/jquery.ui/themes/vector/images/button-disabled-red.png Binary files differindex 0658a1db..ede69988 100644 --- a/resources/jquery.ui/themes/vector/images/button-disabled-red.png +++ b/resources/jquery.ui/themes/vector/images/button-disabled-red.png diff --git a/resources/jquery.ui/themes/vector/images/button-disabled.png b/resources/jquery.ui/themes/vector/images/button-disabled.png Binary files differindex cde3b1fb..e4e4ec1c 100644 --- a/resources/jquery.ui/themes/vector/images/button-disabled.png +++ b/resources/jquery.ui/themes/vector/images/button-disabled.png diff --git a/resources/jquery.ui/themes/vector/images/button-down-blue.png b/resources/jquery.ui/themes/vector/images/button-down-blue.png Binary files differindex e0f5a6c0..766e5746 100644 --- a/resources/jquery.ui/themes/vector/images/button-down-blue.png +++ b/resources/jquery.ui/themes/vector/images/button-down-blue.png diff --git a/resources/jquery.ui/themes/vector/images/button-down-green.png b/resources/jquery.ui/themes/vector/images/button-down-green.png Binary files differindex edfd4275..90969c39 100644 --- a/resources/jquery.ui/themes/vector/images/button-down-green.png +++ b/resources/jquery.ui/themes/vector/images/button-down-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-down-red.png b/resources/jquery.ui/themes/vector/images/button-down-red.png Binary files differindex b0565fd8..f6257298 100644 --- a/resources/jquery.ui/themes/vector/images/button-down-red.png +++ b/resources/jquery.ui/themes/vector/images/button-down-red.png diff --git a/resources/jquery.ui/themes/vector/images/button-down.png b/resources/jquery.ui/themes/vector/images/button-down.png Binary files differindex 160330ec..c6467571 100644 --- a/resources/jquery.ui/themes/vector/images/button-down.png +++ b/resources/jquery.ui/themes/vector/images/button-down.png diff --git a/resources/jquery.ui/themes/vector/images/button-green-hover-large.png b/resources/jquery.ui/themes/vector/images/button-green-hover-large.png Binary files differnew file mode 100644 index 00000000..dd8851e1 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-green-hover-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-green-hover.png b/resources/jquery.ui/themes/vector/images/button-green-hover.png Binary files differnew file mode 100644 index 00000000..19c39911 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-green-hover.png diff --git a/resources/jquery.ui/themes/vector/images/button-green-large.png b/resources/jquery.ui/themes/vector/images/button-green-large.png Binary files differnew file mode 100644 index 00000000..a8e830eb --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-green-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-green.png b/resources/jquery.ui/themes/vector/images/button-green.png Binary files differnew file mode 100644 index 00000000..54c418e9 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png b/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png Binary files differnew file mode 100644 index 00000000..f76f7b05 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-large-off-green.png b/resources/jquery.ui/themes/vector/images/button-large-off-green.png Binary files differnew file mode 100644 index 00000000..f997431b --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-large-off-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-off-blue.png b/resources/jquery.ui/themes/vector/images/button-off-blue.png Binary files differindex 8a8224b0..82dedb53 100644 --- a/resources/jquery.ui/themes/vector/images/button-off-blue.png +++ b/resources/jquery.ui/themes/vector/images/button-off-blue.png diff --git a/resources/jquery.ui/themes/vector/images/button-off-green.png b/resources/jquery.ui/themes/vector/images/button-off-green.png Binary files differindex c6f5ee4f..109907f0 100644 --- a/resources/jquery.ui/themes/vector/images/button-off-green.png +++ b/resources/jquery.ui/themes/vector/images/button-off-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-off-red.png b/resources/jquery.ui/themes/vector/images/button-off-red.png Binary files differindex 1c609166..5a85b8aa 100644 --- a/resources/jquery.ui/themes/vector/images/button-off-red.png +++ b/resources/jquery.ui/themes/vector/images/button-off-red.png diff --git a/resources/jquery.ui/themes/vector/images/button-off.png b/resources/jquery.ui/themes/vector/images/button-off.png Binary files differindex 1bdc85c7..cc5eb119 100644 --- a/resources/jquery.ui/themes/vector/images/button-off.png +++ b/resources/jquery.ui/themes/vector/images/button-off.png diff --git a/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png b/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png Binary files differnew file mode 100644 index 00000000..6f0dbd5d --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-orange-hover.png b/resources/jquery.ui/themes/vector/images/button-orange-hover.png Binary files differnew file mode 100644 index 00000000..a1077c58 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-orange-hover.png diff --git a/resources/jquery.ui/themes/vector/images/button-orange-large.png b/resources/jquery.ui/themes/vector/images/button-orange-large.png Binary files differnew file mode 100644 index 00000000..3d7f37f6 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-orange-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-orange.png b/resources/jquery.ui/themes/vector/images/button-orange.png Binary files differnew file mode 100644 index 00000000..02347cf9 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-orange.png diff --git a/resources/jquery.ui/themes/vector/images/button-over-blue.png b/resources/jquery.ui/themes/vector/images/button-over-blue.png Binary files differindex a744947d..9edb7aa2 100644 --- a/resources/jquery.ui/themes/vector/images/button-over-blue.png +++ b/resources/jquery.ui/themes/vector/images/button-over-blue.png diff --git a/resources/jquery.ui/themes/vector/images/button-over-green.png b/resources/jquery.ui/themes/vector/images/button-over-green.png Binary files differindex d47eb758..47a0b1b8 100644 --- a/resources/jquery.ui/themes/vector/images/button-over-green.png +++ b/resources/jquery.ui/themes/vector/images/button-over-green.png diff --git a/resources/jquery.ui/themes/vector/images/button-over-red.png b/resources/jquery.ui/themes/vector/images/button-over-red.png Binary files differindex 28003bd8..a2445365 100644 --- a/resources/jquery.ui/themes/vector/images/button-over-red.png +++ b/resources/jquery.ui/themes/vector/images/button-over-red.png diff --git a/resources/jquery.ui/themes/vector/images/button-over.png b/resources/jquery.ui/themes/vector/images/button-over.png Binary files differindex 23d13ecb..558f1f80 100644 --- a/resources/jquery.ui/themes/vector/images/button-over.png +++ b/resources/jquery.ui/themes/vector/images/button-over.png diff --git a/resources/jquery.ui/themes/vector/images/button-red-hover-large.png b/resources/jquery.ui/themes/vector/images/button-red-hover-large.png Binary files differnew file mode 100644 index 00000000..11ccef06 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-red-hover-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-red-hover.png b/resources/jquery.ui/themes/vector/images/button-red-hover.png Binary files differnew file mode 100644 index 00000000..55a61743 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-red-hover.png diff --git a/resources/jquery.ui/themes/vector/images/button-red-large.png b/resources/jquery.ui/themes/vector/images/button-red-large.png Binary files differnew file mode 100644 index 00000000..8d089efd --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-red-large.png diff --git a/resources/jquery.ui/themes/vector/images/button-red.png b/resources/jquery.ui/themes/vector/images/button-red.png Binary files differnew file mode 100644 index 00000000..7a292fc1 --- /dev/null +++ b/resources/jquery.ui/themes/vector/images/button-red.png diff --git a/resources/jquery.ui/themes/vector/images/close.png b/resources/jquery.ui/themes/vector/images/close.png Binary files differindex 41d53913..ef0dd9e3 100644 --- a/resources/jquery.ui/themes/vector/images/close.png +++ b/resources/jquery.ui/themes/vector/images/close.png diff --git a/resources/jquery.ui/themes/vector/images/titlebar-fade.png b/resources/jquery.ui/themes/vector/images/titlebar-fade.png Binary files differindex b491ec49..f9fde8b3 100644 --- a/resources/jquery.ui/themes/vector/images/titlebar-fade.png +++ b/resources/jquery.ui/themes/vector/images/titlebar-fade.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_flat_15_cd0a0a_40x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_flat_15_cd0a0a_40x100.png Binary files differindex 7680b543..09de537f 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_flat_15_cd0a0a_40x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_flat_15_cd0a0a_40x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_flat_70_000000_40x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_flat_70_000000_40x100.png Binary files differindex abdc0108..c06dd561 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_flat_70_000000_40x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_flat_70_000000_40x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png Binary files differindex 28b566c2..5308b466 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_80_d7ebf9_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_80_d7ebf9_1x100.png Binary files differindex dac84622..0c8997f7 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_80_d7ebf9_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-hard_80_d7ebf9_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_e4f1fb_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_e4f1fb_1x100.png Binary files differindex ad7f982b..31492556 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_e4f1fb_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_e4f1fb_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_ffffff_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_ffffff_1x100.png Binary files differindex 8169ec3e..09b23761 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_ffffff_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_100_ffffff_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_25_ffef8f_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_25_ffef8f_1x100.png Binary files differindex 54aff0cb..66627c18 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_25_ffef8f_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_highlight-soft_25_ffef8f_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-bg_inset-hard_100_f0f0f0_1x100.png b/resources/jquery.ui/themes/vector/images/ui-bg_inset-hard_100_f0f0f0_1x100.png Binary files differindex 3d87ac7b..ccb6dc06 100644 --- a/resources/jquery.ui/themes/vector/images/ui-bg_inset-hard_100_f0f0f0_1x100.png +++ b/resources/jquery.ui/themes/vector/images/ui-bg_inset-hard_100_f0f0f0_1x100.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_2694e8_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_2694e8_256x240.png Binary files differindex 9d192c2f..998ac3bc 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_2694e8_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_2694e8_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_2e83ff_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_2e83ff_256x240.png Binary files differindex 09d1cdc8..34e38d11 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_2e83ff_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_2e83ff_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_3d80b3_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_3d80b3_256x240.png Binary files differindex f13b2066..ec129a8b 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_3d80b3_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_3d80b3_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_666666_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_666666_256x240.png Binary files differindex f87de1ca..a32c57d8 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_666666_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_666666_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_72a7cf_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_72a7cf_256x240.png Binary files differindex 0d20b730..88fad1a5 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_72a7cf_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_72a7cf_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/ui-icons_ffffff_256x240.png b/resources/jquery.ui/themes/vector/images/ui-icons_ffffff_256x240.png Binary files differindex 42f8f992..29ba7d28 100644 --- a/resources/jquery.ui/themes/vector/images/ui-icons_ffffff_256x240.png +++ b/resources/jquery.ui/themes/vector/images/ui-icons_ffffff_256x240.png diff --git a/resources/jquery.ui/themes/vector/jquery.ui.button.css b/resources/jquery.ui/themes/vector/jquery.ui.button.css index 631fa756..006bbeac 100644 --- a/resources/jquery.ui/themes/vector/jquery.ui.button.css +++ b/resources/jquery.ui/themes/vector/jquery.ui.button.css @@ -4,18 +4,16 @@ .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ -.ui-button-icons-only { width: 3.4em; } -button.ui-button-icons-only { width: 3.7em; } +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } /*button text element */ -.ui-button .ui-button-text { display: block; line-height: 1.4em; } +.ui-button .ui-button-text { display: block; line-height: 1.4; } .ui-button-text-only .ui-button-text { padding: 0.3em 1em 0.25em 1em; } .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: 0.3em; text-indent: -9999999px; } .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.3em 1em 0.25em 2.1em; } .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.3em 2.1em 0.25em 1em; } .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } -/* for older versions of jQuery UI */ -.ui-button-text-icon .ui-button-text { padding: 0.3em 1em 0.3em 2.1em; } /* no icon support for input elements, provide padding by default */ input.ui-button { padding: 0.3em 1em; } @@ -34,10 +32,7 @@ input.ui-button { padding: 0.3em 1em; } button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ body .ui-button { - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - margin: 0.5em 0 0.5em 0.4em !important; + margin: 0.5em 0 0.5em 0.4em; border: 1px solid #a6a6a6 !important; /* @embed */ background: #f2f2f2 url(images/button-off.png) repeat-x scroll 50% 100% !important; @@ -47,6 +42,15 @@ body .ui-button { width: auto; overflow: visible; } + +/* Corner radius */ +/* This is normally handled in jquery.ui.theme.css, but in our case, the corner + styling of our buttons doesn't match our default widget corner styling */ +.ui-button.ui-corner-all, .ui-button.ui-corner-top, .ui-button.ui-corner-left, .ui-button.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-button.ui-corner-all, .ui-button.ui-corner-top, .ui-button.ui-corner-right, .ui-button.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-button.ui-corner-all, .ui-button.ui-corner-bottom, .ui-button.ui-corner-left, .ui-button.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-button.ui-corner-all, .ui-button.ui-corner-bottom, .ui-button.ui-corner-right, .ui-button.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } + body .ui-button:hover { border-color: #6e7273; /* @embed */ @@ -65,83 +69,127 @@ body .ui-button.disabled { background: #f2f2f2 url(images/button-disabled.png) repeat-x scroll 50% 100% !important; } /* Disables the annoying dashed border Firefox puts on active buttons */ -body button.ui-button::-moz-focus-inner { +body button.ui-button::-moz-focus-inner { border: 0; } +/* Give large buttons some extra padding */ +body .ui-button-large { + padding: 5px; +} +/* Use white icons for colored buttons */ +.ui-button-green .ui-icon, .ui-button-blue .ui-icon, .ui-button-red .ui-icon, .ui-button-orange .ui-icon { + /* @embed */ + background-image: url(images/ui-icons_ffffff_256x240.png) !important; +} /* Green buttons */ body .ui-button.ui-button-green { - color: white; + color: white !important; border-color: #97af7e !important; /* @embed */ - background: #85c940 url(images/button-off-green.png) repeat-x scroll 50% 100% !important; + background: #3cb677 url(images/button-green.png) repeat-x scroll 50% 100% !important; } body .ui-button.ui-button-green:hover { - border-color: #778e61; + border-color: #778e61 !important; /* @embed */ - background: #77ad40 url(images/button-over-green.png) repeat-x scroll 50% 100% !important; + background: #339b65 url(images/button-green-hover.png) repeat-x scroll 50% 100% !important; } -body .ui-button.ui-button-green:active, -body .ui-button.ui-button-green:focus { - border-color: #61b000; +body .ui-button.ui-button-green.ui-button-large { /* @embed */ - background: #54a800 url(images/button-down-green.png) repeat-x scroll 50% 100% !important; + background: #3cb677 url(images/button-green-large.png) repeat-x scroll 50% 100% !important; } -body .ui-button.ui-button-green.disabled { - color: #7f7f7f; - border-color: #b8d29f; +body .ui-button.ui-button-green.ui-button-large:hover { /* @embed */ - background: #c9cfc3 url(images/button-disabled-green.png) repeat-x scroll 50% 100% !important; + background: #339b65 url(images/button-green-hover-large.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-green.disabled { + filter:alpha(opacity=50); + -moz-opacity:0.50; + -khtml-opacity: 0.50; + opacity: 0.50; } /* Blue buttons */ body .ui-button.ui-button-blue { - color: white; - border-color: #407ec9 !important; + color: white !important; + border-color: #628acb !important; /* @embed */ - background: #407ec9 url(images/button-off-blue.png) repeat-x scroll 50% 100% !important; + background: #3365ba url(images/button-blue.png) repeat-x scroll 50% 100% !important; } body .ui-button.ui-button-blue:hover { - border-color: #245289; + border-color: #5375ad !important; /* @embed */ - background: #4071ad url(images/button-over-blue.png) repeat-x scroll 50% 100% !important; + background: #2b569e url(images/button-blue-hover.png) repeat-x scroll 50% 100% !important; } -body .ui-button.ui-button-blue:active, -body .ui-button.ui-button-blue:focus { - border-color: #003980; +body .ui-button.ui-button-blue.ui-button-large { /* @embed */ - background: #004daa url(images/button-down-blue.png) repeat-x scroll 50% 100% !important; + background: #3365ba url(images/button-blue-large.png) repeat-x scroll 50% 100% !important; } -body .ui-button.ui-button-blue.disabled { - border-color: #9eafc6; +body .ui-button.ui-button-blue.ui-button-large:hover { /* @embed */ - background: #c3c8cf url(images/button-disabled-blue.png) repeat-x scroll 50% 100% !important; + background: #2b569e url(images/button-blue-hover-large.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-blue.disabled { + filter:alpha(opacity=50); + -moz-opacity:0.50; + -khtml-opacity: 0.50; + opacity: 0.50; } /* Red buttons */ body .ui-button.ui-button-red { - color: white; + color: white !important; border-color: #af977e !important; /* @embed */ - background: #c9404c url(images/button-off-red.png) repeat-x scroll 50% 100% !important; + background: #cb0000 url(images/button-red.png) repeat-x scroll 50% 100% !important; } body .ui-button.ui-button-red:hover { - border-color: #8e7761; + border-color: #8e7761 !important; /* @embed */ - background: #ad404a url(images/button-over-red.png) repeat-x scroll 50% 100% !important; + background: #ad0000 url(images/button-red-hover.png) repeat-x scroll 50% 100% !important; } -body .ui-button.ui-button-red:active, -body .ui-button.ui-button-red:focus { - border-color: #b06100; +body .ui-button.ui-button-red.ui-button-large { /* @embed */ - background: #aa000f url(images/button-down-red.png) repeat-x scroll 50% 100% !important; + background: #cb0000 url(images/button-red.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-red.ui-button-large:hover { + /* @embed */ + background: #ad0000 url(images/button-red-hover.png) repeat-x scroll 50% 100% !important; } body .ui-button.ui-button-red.disabled { - color: #7f7f7f; - border-color: #c3acae; + filter:alpha(opacity=50); + -moz-opacity:0.50; + -khtml-opacity: 0.50; + opacity: 0.50; +} + +/* Orange buttons */ + +body .ui-button.ui-button-orange { + color: white !important; + border-color: #f3a863 !important; + /* @embed */ + background: #f07f14 url(images/button-orange.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-orange:hover { + border-color: #ce9055 !important; + /* @embed */ + background: #cc6c11 url(images/button-orange-hover.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-orange.ui-button-large { /* @embed */ - background: #cfc3c4 url(images/button-disabled-red.png) repeat-x scroll 50% 100% !important; + background: #f07f14 url(images/button-orange-large.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-orange.ui-button-large:hover { + /* @embed */ + background: #cc6c11 url(images/button-orange-hover-large.png) repeat-x scroll 50% 100% !important; +} +body .ui-button.ui-button-orange.disabled { + filter:alpha(opacity=50); + -moz-opacity:0.50; + -khtml-opacity: 0.50; + opacity: 0.50; } diff --git a/resources/jquery.ui/themes/vector/jquery.ui.resizable.css b/resources/jquery.ui/themes/vector/jquery.ui.resizable.css index 366643b5..f1bd7c5e 100644 --- a/resources/jquery.ui/themes/vector/jquery.ui.resizable.css +++ b/resources/jquery.ui/themes/vector/jquery.ui.resizable.css @@ -5,9 +5,15 @@ .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +/* @noflip */ .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +/* @noflip */ .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +/* @noflip */ .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +/* @noflip */ .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +/* @noflip */ .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +/* @noflip */ .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
\ No newline at end of file diff --git a/resources/jquery/images/jquery.arrowSteps.divider-ltr.png b/resources/jquery/images/jquery.arrowSteps.divider-ltr.png Binary files differindex 0de1ae9c..83d6ff84 100644 --- a/resources/jquery/images/jquery.arrowSteps.divider-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.divider-ltr.png diff --git a/resources/jquery/images/jquery.arrowSteps.divider-rtl.png b/resources/jquery/images/jquery.arrowSteps.divider-rtl.png Binary files differindex f3f06519..529d7b84 100644 --- a/resources/jquery/images/jquery.arrowSteps.divider-rtl.png +++ b/resources/jquery/images/jquery.arrowSteps.divider-rtl.png diff --git a/resources/jquery/images/jquery.arrowSteps.head-ltr.png b/resources/jquery/images/jquery.arrowSteps.head-ltr.png Binary files differindex dbcc71b9..3289617d 100644 --- a/resources/jquery/images/jquery.arrowSteps.head-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.head-ltr.png diff --git a/resources/jquery/images/jquery.arrowSteps.head-rtl.png b/resources/jquery/images/jquery.arrowSteps.head-rtl.png Binary files differindex 760798bf..3d9f70cb 100644 --- a/resources/jquery/images/jquery.arrowSteps.head-rtl.png +++ b/resources/jquery/images/jquery.arrowSteps.head-rtl.png diff --git a/resources/jquery/images/jquery.arrowSteps.tail-ltr.png b/resources/jquery/images/jquery.arrowSteps.tail-ltr.png Binary files differindex 61340fa8..92b872b2 100644 --- a/resources/jquery/images/jquery.arrowSteps.tail-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.tail-ltr.png diff --git a/resources/jquery/images/jquery.arrowSteps.tail-rtl.png b/resources/jquery/images/jquery.arrowSteps.tail-rtl.png Binary files differindex dd2d8e8a..1d3048ef 100644 --- a/resources/jquery/images/jquery.arrowSteps.tail-rtl.png +++ b/resources/jquery/images/jquery.arrowSteps.tail-rtl.png diff --git a/resources/jquery/images/spinner-large.gif b/resources/jquery/images/spinner-large.gif Binary files differnew file mode 100644 index 00000000..72203fdd --- /dev/null +++ b/resources/jquery/images/spinner-large.gif diff --git a/resources/jquery/images/spinner.gif b/resources/jquery/images/spinner.gif Binary files differindex 37d3a43d..6146be4e 100644 --- a/resources/jquery/images/spinner.gif +++ b/resources/jquery/images/spinner.gif diff --git a/resources/jquery/images/wheel.png b/resources/jquery/images/wheel.png Binary files differindex 97b343d9..7e53103e 100644 --- a/resources/jquery/images/wheel.png +++ b/resources/jquery/images/wheel.png diff --git a/resources/jquery/jquery.arrowSteps.css b/resources/jquery/jquery.arrowSteps.css index 60461032..f8f6e951 100644 --- a/resources/jquery/jquery.arrowSteps.css +++ b/resources/jquery/jquery.arrowSteps.css @@ -42,4 +42,4 @@ /* TODO: eliminate duplication of jquery.arrowSteps.head.png embedding */ /* @embed */ background: url(images/jquery.arrowSteps.head-ltr.png) no-repeat right center; -}
\ No newline at end of file +} diff --git a/resources/jquery/jquery.arrowSteps.js b/resources/jquery/jquery.arrowSteps.js index f9637545..488d1065 100644 --- a/resources/jquery/jquery.arrowSteps.js +++ b/resources/jquery/jquery.arrowSteps.js @@ -1,21 +1,21 @@ /** * 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 + * + * 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. * * * DESCRIPTION * - * Show users their progress through a series of steps, via a row of items that fit + * 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. * * - * SYNOPSIS + * SYNOPSIS * * <ul id="robin-hood-daffy"> * <li id="guard"><div>Guard!</div></li> @@ -27,45 +27,43 @@ * <li id="thrust"><div>Thrust!</div></li> * </ul> * - * <script language="javascript"><!-- + * <script> * $( '#robin-hood-daffy' ).arrowSteps(); * * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#guard' ); * // 'Guard!' is highlighted. * * // ... user completes the 'guard' step ... - * + * * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#turn' ); * // 'Turn!' is highlighted. - * - * //--> * </script> * */ - -( function( $j ) { - $j.fn.arrowSteps = function() { +( function ( $ ) { + $.fn.arrowSteps = function () { + var $steps, width, arrowWidth; this.addClass( 'arrowSteps' ); - var $steps = this.find( 'li' ); + $steps = this.find( 'li' ); - var width = parseInt( 100 / $steps.length, 10 ); + width = parseInt( 100 / $steps.length, 10 ); $steps.css( 'width', width + '%' ); - // every step except the last one has an arrow at the right hand side. Also add in the padding + // every step except the last one has an arrow at the right hand side. Also add in the padding // for the calculated arrow width. - var arrowWidth = parseInt( this.outerHeight(), 10 ); + arrowWidth = parseInt( this.outerHeight(), 10 ); $steps.filter( ':not(:last-child)' ).addClass( 'arrow' ) .find( 'div' ).css( 'padding-right', arrowWidth.toString() + 'px' ); this.data( 'arrowSteps', $steps ); return this; }; - - $j.fn.arrowStepsHighlight = function( selector ) { - var $steps = this.data( 'arrowSteps' ); - var $previous; - $j.each( $steps, function( i, step ) { - var $step = $j( step ); + + $.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' ); @@ -75,7 +73,7 @@ $step.removeClass( 'head tail lasthead' ); } $previous = $step; - } ); + } ); }; -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.autoEllipsis.js b/resources/jquery/jquery.autoEllipsis.js index 9a5fcc9c..49a932a1 100644 --- a/resources/jquery/jquery.autoEllipsis.js +++ b/resources/jquery/jquery.autoEllipsis.js @@ -1,53 +1,57 @@ /** - * Plugin that automatically truncates the plain text contents of an element and adds an ellipsis + * Plugin that automatically truncates the plain text contents of an element + * and adds an ellipsis. */ -( function( $ ) { +( function ( $ ) { -// Cache ellipsed substrings for every string-width-position combination -var cache = { }; -// Use a separate cache when match highlighting is enabled -var matchTextCache = { }; +var + // Cache ellipsed substrings for every string-width-position combination + cache = {}, -$.fn.autoEllipsis = function( options ) { + // Use a separate cache when match highlighting is enabled + matchTextCache = {}; + +$.fn.autoEllipsis = function ( options ) { options = $.extend( { - 'position': 'center', - 'tooltip': false, - 'restoreText': false, - 'hasSpan': false, - 'matchText': null + position: 'center', + tooltip: false, + restoreText: false, + hasSpan: false, + matchText: null }, options ); - $(this).each( function() { - var $el = $(this); + + return this.each( function () { + var $trimmableText, + text, trimmableText, w, pw, + l, r, i, side, m, + // container element - used for measuring against + $container = $(this); + if ( options.restoreText ) { - if ( !$el.data( 'autoEllipsis.originalText' ) ) { - $el.data( 'autoEllipsis.originalText', $el.text() ); + if ( !$container.data( 'autoEllipsis.originalText' ) ) { + $container.data( 'autoEllipsis.originalText', $container.text() ); } else { - $el.text( $el.data( 'autoEllipsis.originalText' ) ); + $container.text( $container.data( 'autoEllipsis.originalText' ) ); } } - // container element - used for measuring against - var $container = $el; // trimmable text element - only the text within this element will be trimmed - var $trimmableText = null; - // protected text element - the width of this element is counted, but next is never trimmed from it - var $protectedText = null; - if ( options.hasSpan ) { - $trimmableText = $el.children( options.selector ); + $trimmableText = $container.children( options.selector ); } else { - $trimmableText = $( '<span />' ) + $trimmableText = $( '<span>' ) .css( 'whiteSpace', 'nowrap' ) - .text( $el.text() ); - $el + .text( $container.text() ); + $container .empty() .append( $trimmableText ); } - var text = $container.text(); - var trimmableText = $trimmableText.text(); - var w = $container.width(); - var pw = $protectedText ? $protectedText.width() : 0; + text = $container.text(); + trimmableText = $trimmableText.text(); + w = $container.width(); + pw = 0; + // Try cache if ( options.matchText ) { if ( !( text in matchTextCache ) ) { @@ -86,9 +90,10 @@ $.fn.autoEllipsis = function( options ) { switch ( options.position ) { case 'right': // Use binary search-like technique for efficiency - var l = 0, r = trimmableText.length; + l = 0; + r = trimmableText.length; do { - var m = Math.ceil( ( l + r ) / 2 ); + m = Math.ceil( ( l + r ) / 2 ); $trimmableText.text( trimmableText.substr( 0, m ) + '...' ); if ( $trimmableText.width() + pw > w ) { // Text is too long @@ -101,9 +106,10 @@ $.fn.autoEllipsis = function( options ) { break; case 'center': // TODO: Use binary search like for 'right' - var i = [Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 )]; - var side = 1; // Begin with making the end shorter - while ( $trimmableText.outerWidth() + pw > w && i[0] > 0 ) { + 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.substr( 0, i[0] ) + '...' + trimmableText.substr( i[1] ) ); // Alternate between trimming the end and begining if ( side === 0 ) { @@ -119,7 +125,7 @@ $.fn.autoEllipsis = function( options ) { break; case 'left': // TODO: Use binary search like for 'right' - var r = 0; + r = 0; while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) { $trimmableText.text( '...' + trimmableText.substr( r ) ); r++; @@ -140,4 +146,4 @@ $.fn.autoEllipsis = function( options ) { } ); }; -} )( jQuery );
\ No newline at end of file +}( jQuery ) );
\ No newline at end of file diff --git a/resources/jquery/jquery.badge.css b/resources/jquery/jquery.badge.css new file mode 100644 index 00000000..92e72555 --- /dev/null +++ b/resources/jquery/jquery.badge.css @@ -0,0 +1,39 @@ +.mw-badge { + min-width: 8px; + height: 14px; + border: 1px solid white; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; + -moz-box-shadow: 0px 1px 4px #ccc; + -webkit-box-shadow: 0px 1px 4px #ccc; + box-shadow: 0px 1px 4px #ccc; + background-color: #b60a00; + background-image: -o-linear-gradient(bottom, #a70802 0%, #cf0e00 100%); + background-image: -moz-linear-gradient(bottom, #a70802 0%, #cf0e00 100%); + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #a70802), color-stop(1, #cf0e00)); + background-image: -webkit-linear-gradient(bottom, #a70802 0%, #cf0e00 100%); + background-image: -ms-linear-gradient(bottom, #a70802 0%, #cf0e00 100%); + background-image: linear-gradient(bottom, #a70802 0%, #cf0e00 100%); + padding: 0 3px; + text-align: center; +} + +.mw-badge-content { + font-size: 12px; + line-height: 14px; + color: white; + vertical-align: top; +} + +.mw-badge-inline { + display: inline-block; + margin-left: 3px; +} + +.mw-badge-overlay { + position: absolute; + bottom: -1px; + right: -3px; + z-index: 50; +} diff --git a/resources/jquery/jquery.badge.js b/resources/jquery/jquery.badge.js new file mode 100644 index 00000000..04495b71 --- /dev/null +++ b/resources/jquery/jquery.badge.js @@ -0,0 +1,117 @@ +/** + * jQuery Badge plugin + * + * Based on Badger plugin by Daniel Raftery (http://thrivingkings.com/badger). + * + * @license MIT + */ + +/** + * @author Ryan Kaldari <rkaldari@wikimedia.org>, 2012 + * @author Andrew Garrett <agarrett@wikimedia.org>, 2012 + * @author Marius Hoch <hoo@online.de>, 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. + */ +( function ( $ ) { + + /** + * Allows you to put a numeric "badge" on an item on the page. + * See mediawiki.org/wiki/ResourceLoader/Default_modules#jQuery.badge + * + * @param {string|number} badgeCount An explicit number, or "+n"/ "-n" + * to modify the existing value. If the new value is equal or lower than 0, + * any existing badge will be removed. The badge container will be appended + * to the selected element(s). + * @param {Object} options Optional parameters specified below + * type: 'inline' or 'overlay' (default) + * callback: will be called with the number now shown on the badge as a parameter + */ + $.fn.badge = function ( badgeCount, options ) { + var $badge, + oldBadgeCount, + newBadgeCount, + $existingBadge = this.find( '.mw-badge' ); + + options = $.extend( { type : 'overlay' }, options ); + + // If there is no existing badge, this will give an empty string + oldBadgeCount = Number( $existingBadge.text() ); + if ( isNaN( oldBadgeCount ) ) { + oldBadgeCount = 0; + } + + // If badgeCount is a number, use that as the new badge + if ( typeof badgeCount === 'number' ) { + newBadgeCount = badgeCount; + } else if ( typeof badgeCount === 'string' ) { + // If badgeCount is "+x", add x to the old badge + if ( badgeCount.charAt(0) === '+' ) { + newBadgeCount = oldBadgeCount + Number( badgeCount.substr(1) ); + // If badgeCount is "-x", subtract x from the old badge + } else if ( badgeCount.charAt(0) === '-' ) { + newBadgeCount = oldBadgeCount - Number( badgeCount.substr(1) ); + // If badgeCount can be converted into a number, convert it + } else if ( !isNaN( Number( badgeCount ) ) ) { + newBadgeCount = Number( badgeCount ); + } else { + newBadgeCount = 0; + } + // Other types are not supported, fall back to 0. + } else { + newBadgeCount = 0; + } + + // Badge count must be a whole number + newBadgeCount = Math.round( newBadgeCount ); + + if ( newBadgeCount <= 0 ) { + // Badges should only exist for values > 0. + $existingBadge.remove(); + } else { + // Don't add duplicates + if ( $existingBadge.length ) { + $badge = $existingBadge; + // Insert the new count into the badge + this.find( '.mw-badge-content' ).text( newBadgeCount ); + } else { + // Contruct a new badge with the count + $badge = $( '<div>' ) + .addClass( 'mw-badge' ) + .append( + $( '<span>' ) + .addClass( 'mw-badge-content' ) + .text( newBadgeCount ) + ); + this.append( $badge ); + } + + if ( options.type === 'inline' ) { + $badge + .removeClass( 'mw-badge-overlay' ) + .addClass( 'mw-badge-inline' ); + // Default: overlay + } else { + $badge + .removeClass( 'mw-badge-inline' ) + .addClass( 'mw-badge-overlay' ); + + } + + // If a callback was specified, call it with the badge count + if ( $.isFunction( options.callback ) ) { + options.callback( newBadgeCount ); + } + } + }; +}( jQuery ) ); diff --git a/resources/jquery/jquery.byteLength.js b/resources/jquery/jquery.byteLength.js index 20fa5c8e..3d5b7206 100644 --- a/resources/jquery/jquery.byteLength.js +++ b/resources/jquery/jquery.byteLength.js @@ -3,9 +3,9 @@ * * Calculate the byte length of a string (accounting for UTF-8). * - * @author Jan Paul Posma + * @author Jan Paul Posma, 2011 */ -jQuery.byteLength = function( str ) { +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. @@ -16,4 +16,4 @@ jQuery.byteLength = function( str ) { .replace( /[\u0080-\u07FF\uD800-\uDFFF]/g, '**' ) .replace( /[\u0800-\uD7FF\uE000-\uFFFF]/g, '***' ) .length; -} +}; diff --git a/resources/jquery/jquery.byteLimit.js b/resources/jquery/jquery.byteLimit.js index 10411924..75dc2b90 100644 --- a/resources/jquery/jquery.byteLimit.js +++ b/resources/jquery/jquery.byteLimit.js @@ -1,87 +1,229 @@ /** - * jQuery byteLimit + * jQuery byteLimit plugin. * - * @author Jan Paul Posma + * @author Jan Paul Posma, 2011 + * @author Timo Tijhof, 2011-2012 */ -( function( $ ) { +( function ( $ ) { /** - * Enforces a byte limit to a textbox, so that UTF-8 entries are counted as well, when, for example, - * a databae 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. + * 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 occured. * - * 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 arguments is important! + * @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 [optional] See $.fn.byteLimit. + * @return {Object} Object with: + * - {string} newVal + * - {boolean} 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.substring( 0, startMatches ), + // Inserted content + newVal.substring( startMatches, newVal.length - endMatches ), + // Same end + newVal.substring( newVal.length - endMatches ) + ]; + + // Chop off characters from the end of the "inserted content" string + // until the limit is statisfied. + if ( fn ) { + while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit ) { + 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! * * @context {jQuery} Instance of jQuery for one or more input elements - * @param limit {Number} (optional) Limit to enforce, fallsback to maxLength-attribute, - * called with fetched value as argument. - * @param fn {Function} (optional) Function to call on the input string before assessing the length + * @param {Number} limit [optional] Limit to enforce, fallsback to maxLength-attribute, + * called with fetched value as argument. + * @param {Function} fn [optional] Function to call on the string before assessing the length. * @return {jQuery} The context */ - $.fn.byteLimit = function( limit, fn ) { + $.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; } - // Default limit to current attribute value - if ( limit === undefined ) { - limit = this.prop( 'maxLength' ); - } + // The following is specific to each element in the collection. + return this.each( function ( i, el ) { + var $el, elLimit, prevSafeVal; - // Update/set attribute value, but only if there is no callback set. - // If there's a callback set, it's possible that the limit being enforced - // is too low (ie. if the callback would return "Foo" for "User:Foo"). - // Usually this isn't a problem since browsers ignore maxLength when setting - // the value property through JavaScript, but Safari 4 violates that rule, so - // we have to remove or not set the property if we have a callback. - if ( fn == undefined ) { - this.prop( 'maxLength', limit ); - } else { - this.removeProp( 'maxLength' ); - } + $el = $( el ); - // Nothing passed and/or empty attribute, return without binding an event. - if ( limit === undefined ) { - return this; - } + // 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 ); - // Save function for reference - this.data( 'byteLimit-callback', fn ); - - // We've got something, go for it: - return this.keypress( function( e ) { - // First check to see if this is actually a character key - // being pressed. - // Based on key-event info from http://unixpapa.com/js/key.html - // jQuery should also normalize e.which to be consistent cross-browser, - // however the same check is still needed regardless of jQuery. - - // Note: At the moment, for some older opera versions (~< 10.5) - // some special keys won't be recognized (aka left arrow key). - // Backspace will be, so not big issue. - - if ( e.which === 0 || e.charCode === 0 || e.which === 8 || - e.ctrlKey || e.altKey || e.metaKey ) - { - return true; //a special key (backspace, etc) so don't interfere. + // 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; } - var val = fn !== undefined ? fn( $( this ).val() ): $( this ).val(), - len = $.byteLength( val ), - // Note that keypress returns a character code point, not a keycode. - // However, this may not be super reliable depending on how keys come in... - charLen = $.byteLength( String.fromCharCode( e.which ) ); + if ( fn ) { + // Save function for reference + $el.data( 'byteLimit.callback', fn ); + } + + // Remove old event handlers (if there are any) + $el.off( '.byteLimit' ); - if ( ( len + charLen ) > limit ) { - e.preventDefault(); + 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 ); } - }); - }; -} )( jQuery ); + + // 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; + prevSafeVal = res.newVal; + } + } ); + } ); + }; +}( jQuery ) ); diff --git a/resources/jquery/jquery.checkboxShiftClick.js b/resources/jquery/jquery.checkboxShiftClick.js index 0a1d7d7d..1990dc0d 100644 --- a/resources/jquery/jquery.checkboxShiftClick.js +++ b/resources/jquery/jquery.checkboxShiftClick.js @@ -6,23 +6,22 @@ * @author Krinkle <krinklemail@gmail.com> * @license GPL v2 */ -( function( $ ) { -$.fn.checkboxShiftClick = function( text ) { - var prevCheckbox = null; - var $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 - $box.slice( - Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ), - Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1 - ).prop( 'checked', e.target.checked ? true : false ); - } - // Either way, update the prevCheckbox variable to the one clicked now - prevCheckbox = e.target; - } ); - return $box; -}; -} )( jQuery );
\ No newline at end of file +( function ( $ ) { + $.fn.checkboxShiftClick = function ( text ) { + 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 + $box.slice( + Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ), + Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1 + ).prop( 'checked', !!e.target.checked ); + } + // Either way, update the prevCheckbox variable to the one clicked now + prevCheckbox = e.target; + } ); + return $box; + }; +}( jQuery ) ); diff --git a/resources/jquery/jquery.client.js b/resources/jquery/jquery.client.js index ae74a324..24f8959e 100644 --- a/resources/jquery/jquery.client.js +++ b/resources/jquery/jquery.client.js @@ -1,7 +1,7 @@ /** * User-agent detection */ -( function( $ ) { +( function ( $ ) { /* Private Members */ @@ -18,7 +18,7 @@ /** * Get an object containing information about the client. * - * @param nav {Object} An object with atleast a 'userAgent' and 'platform' key.= + * @param nav {Object} An object with atleast a 'userAgent' and 'platform' key. * Defaults to the global Navigator object. * @return {Object} The resulting client object will be in the following format: * { @@ -31,7 +31,9 @@ * 'versionNumber': 3.5, * } */ - profile: function( nav ) { + profile: function ( nav ) { + /*jshint boss: true */ + if ( nav === undefined ) { nav = window.navigator; } @@ -64,15 +66,15 @@ // Strings which precede a version number in a user agent string - combined and used as match 1 in // version detectection var versionPrefixes = [ - 'camino', 'chrome', 'firefox', 'netscape', 'netscape6', 'opera', 'version', 'konqueror', 'lynx', - 'msie', 'safari', 'ps3' + 'camino', 'chrome', 'firefox', 'netscape', 'netscape6', 'opera', 'version', 'konqueror', + 'lynx', 'msie', 'safari', 'ps3' ]; // Used as matches 2, 3 and 4 in version extraction - 3 is used as actual version number var versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)'; // Names of known browsers var names = [ - 'camino', 'chrome', 'firefox', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', 'safari', 'ipod', - 'iphone', 'blackberry', 'ps3' + 'camino', 'chrome', 'firefox', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', + 'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq' ]; // Tanslations for conforming browser names var nameTranslations = []; @@ -89,9 +91,12 @@ /* Methods */ - // Performs multiple replacements on a string - var translate = function( source, translations ) { - for ( var i = 0; i < translations.length; i++ ) { + /** + * Performs multiple replacements on a string + */ + var translate = function ( source, translations ) { + var i; + for ( i = 0; i < translations.length; i++ ) { source = source.replace( translations[i][0], translations[i][1] ); } return source; @@ -147,13 +152,13 @@ /* Caching */ profileCache[nav.userAgent] = { - 'name': name, - 'layout': layout, - 'layoutVersion': layoutversion, - 'platform': platform, - 'version': version, - 'versionBase': ( version !== x ? Math.floor( versionNumber ).toString() : x ), - 'versionNumber': versionNumber + name: name, + layout: layout, + layoutVersion: layoutversion, + platform: platform, + version: version, + versionBase: ( version !== x ? Math.floor( versionNumber ).toString() : x ), + versionNumber: versionNumber }; } return profileCache[nav.userAgent]; @@ -185,26 +190,30 @@ * * @return Boolean true if browser known or assumed to be supported, false if blacklisted */ - test: function( map, profile ) { + test: function ( map, profile ) { + /*jshint evil:true */ + + var conditions, dir, i, op, val; profile = $.isPlainObject( profile ) ? profile : $.client.profile(); - var dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'; + dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'; // Check over each browser condition to determine if we are running in a compatible client - if ( typeof map[dir] !== 'object' || typeof map[dir][profile.name] === 'undefined' ) { + if ( typeof map[dir] !== 'object' || map[dir][profile.name] === undefined ) { // Unknown, so we assume it's working return true; } - var conditions = map[dir][profile.name]; - for ( var i = 0; i < conditions.length; i++ ) { - var op = conditions[i][0]; - var val = conditions[i][1]; + conditions = map[dir][profile.name]; + for ( i = 0; i < conditions.length; i++ ) { + op = conditions[i][0]; + val = conditions[i][1]; if ( val === false ) { return false; - } else if ( typeof val == 'string' ) { + } + if ( typeof val === 'string' ) { if ( !( eval( 'profile.version' + op + '"' + val + '"' ) ) ) { return false; } - } else if ( typeof val == 'number' ) { + } else if ( typeof val === 'number' ) { if ( !( eval( 'profile.versionNumber' + op + val ) ) ) { return false; } @@ -213,4 +222,4 @@ return true; } }; -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.collapsibleTabs.js b/resources/jquery/jquery.collapsibleTabs.js index 1784f86a..cb25796f 100644 --- a/resources/jquery/jquery.collapsibleTabs.js +++ b/resources/jquery/jquery.collapsibleTabs.js @@ -1,30 +1,34 @@ -/* +/** * Collapsible tabs jQuery Plugin */ -( function( $ ) { - $.fn.collapsibleTabs = function( options ) { +( function ( $ ) { + $.fn.collapsibleTabs = function ( options ) { // return if the function is called on an empty jquery object - if( !this.length ) return this; - //merge options into the defaults + if ( !this.length ) { + return this; + } + // Merge options into the defaults var $settings = $.extend( {}, $.collapsibleTabs.defaults, options ); - this.each( function() { - var $this = $( this ); + this.each( function () { + var $el = $( this ); // add the element to our array of collapsible managers - $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length == 0 ? - $this : $.collapsibleTabs.instances.add( $this ) ); + $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length === 0 ? + $el : $.collapsibleTabs.instances.add( $el ) ); // attach the settings to the elements - $this.data( 'collapsibleTabsSettings', $settings ); + $el.data( 'collapsibleTabsSettings', $settings ); // attach data to our collapsible elements - $this.children( $settings.collapsible ).each( function() { + $el.children( $settings.collapsible ).each( function () { $.collapsibleTabs.addData( $( this ) ); } ); } ); // if we haven't already bound our resize hanlder, bind it now - if( !$.collapsibleTabs.boundEvent ) { + if ( !$.collapsibleTabs.boundEvent ) { $( window ) - .delayedBind( '500', 'resize', function( ) { $.collapsibleTabs.handleResize(); } ); + .delayedBind( '500', 'resize', function ( ) { + $.collapsibleTabs.handleResize(); + } ); } // call our resize handler to setup the page $.collapsibleTabs.handleResize(); @@ -38,63 +42,67 @@ collapsedContainer: '#p-cactions ul', collapsible: 'li.collapsible', shifting: false, - expandCondition: function( eleWidth ) { + expandCondition: function ( eleWidth ) { return ( $( '#left-navigation' ).position().left + $( '#left-navigation' ).width() ) < ( $( '#right-navigation' ).position().left - eleWidth ); }, - collapseCondition: function() { + collapseCondition: function () { return ( $( '#left-navigation' ).position().left + $( '#left-navigation' ).width() ) > $( '#right-navigation' ).position().left; } }, - addData: function( $collapsible ) { + addData: function ( $collapsible ) { var $settings = $collapsible.parent().data( 'collapsibleTabsSettings' ); - if ( $settings != null ) { + if ( $settings !== null ) { $collapsible.data( 'collapsibleTabsSettings', { - 'expandedContainer': $settings.expandedContainer, - 'collapsedContainer': $settings.collapsedContainer, - 'expandedWidth': $collapsible.width(), - 'prevElement': $collapsible.prev() + expandedContainer: $settings.expandedContainer, + collapsedContainer: $settings.collapsedContainer, + expandedWidth: $collapsible.width(), + prevElement: $collapsible.prev() } ); } }, - getSettings: function( $collapsible ) { + getSettings: function ( $collapsible ) { var $settings = $collapsible.data( 'collapsibleTabsSettings' ); - if ( typeof $settings == 'undefined' ) { + if ( $settings === undefined ) { $.collapsibleTabs.addData( $collapsible ); $settings = $collapsible.data( 'collapsibleTabsSettings' ); } return $settings; }, - handleResize: function( e ){ - $.collapsibleTabs.instances.each( function() { - var $this = $( this ), data = $.collapsibleTabs.getSettings( $this ); - if( data.shifting ) return; + handleResize: function ( e ) { + $.collapsibleTabs.instances.each( function () { + var $el = $( this ), + data = $.collapsibleTabs.getSettings( $el ); + + if ( data.shifting ) { + return; + } // if the two navigations are colliding - if( $this.children( data.collapsible ).length > 0 && data.collapseCondition() ) { + if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) { - $this.trigger( "beforeTabCollapse" ); + $el.trigger( 'beforeTabCollapse' ); // move the element to the dropdown menu - $.collapsibleTabs.moveToCollapsed( $this.children( data.collapsible + ':last' ) ); + $.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) ); } // if there are still moveable items in the dropdown menu, // and there is sufficient space to place them in the tab container - if( $( data.collapsedContainer + ' ' + data.collapsible ).length > 0 + if ( $( data.collapsedContainer + ' ' + data.collapsible ).length > 0 && data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children( - data.collapsible+":first" ) ).expandedWidth ) ) { + data.collapsible + ':first' ) ).expandedWidth ) ) { //move the element from the dropdown to the tab - $this.trigger( "beforeTabExpand" ); + $el.trigger( 'beforeTabExpand' ); $.collapsibleTabs - .moveToExpanded( data.collapsedContainer + " " + data.collapsible + ':first' ); + .moveToExpanded( data.collapsedContainer + ' ' + data.collapsible + ':first' ); } }); }, - moveToCollapsed: function( ele ) { - var $moving = $( ele ); - var data = $.collapsibleTabs.getSettings( $moving ); - var dataExp = $.collapsibleTabs.getSettings( data.expandedContainer ); + moveToCollapsed: function ( ele ) { + var $moving = $( ele ), + data = $.collapsibleTabs.getSettings( $moving ), + dataExp = $.collapsibleTabs.getSettings( data.expandedContainer ); dataExp.shifting = true; $moving .detach() @@ -103,10 +111,10 @@ dataExp.shifting = false; $.collapsibleTabs.handleResize(); }, - moveToExpanded: function( ele ) { - var $moving = $( ele ); - var data = $.collapsibleTabs.getSettings( $moving ); - var dataExp = $.collapsibleTabs.getSettings( data.expandedContainer ); + moveToExpanded: function ( ele ) { + var $moving = $( ele ), + data = $.collapsibleTabs.getSettings( $moving ), + dataExp = $.collapsibleTabs.getSettings( data.expandedContainer ); dataExp.shifting = true; // remove this element from where it's at and put it in the dropdown menu $moving.detach().insertAfter( data.prevElement ).data( 'collapsibleTabsSettings', data ); @@ -114,4 +122,5 @@ $.collapsibleTabs.handleResize(); } }; -} )( jQuery ); + +}( jQuery ) ); diff --git a/resources/jquery/jquery.color.js b/resources/jquery/jquery.color.js index 8a619b5c..8bc45c97 100644 --- a/resources/jquery/jquery.color.js +++ b/resources/jquery/jquery.color.js @@ -1,44 +1,54 @@ /** * jQuery Color Animations - * Copyright 2007 John Resig + * + * @author John Resig, 2007 + * @author Krinkle, 2011 * Released under the MIT and GPL licenses. * - * - 2011-01-05: Modified by Krinkle to use the jQuery.colorUtil plugin (which has to be loaded first!) + * - 2011-01-05: Forked for MediaWiki. See also jQuery.colorUtil plugin */ -(function( $ ) { +( function ( $ ) { - // 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.state == 0 ) { - fx.start = getColor( fx.elem, attr ); - fx.end = $.colorUtil.getRGB( fx.end ); - } - - fx.elem.style[attr] = 'rgb(' + [ - Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0), - Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0), - Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0) - ].join( ',' ) + ')'; - } - } - ); - - function getColor(elem, attr) { + function getColor( elem, attr ) { + /*jshint boss:true */ var color; do { - color = $.curCSS(elem, attr); + color = $.curCSS( elem, attr ); // Keep going until we find an element that has color, or we hit the body - if ( color != '' && color != 'transparent' || $.nodeName(elem, 'body') ) + if ( color !== '' && color !== 'transparent' || $.nodeName( elem, 'body' ) ) { break; + } attr = 'backgroundColor'; } while ( elem = elem.parentNode ); - return $.colorUtil.getRGB(color); - }; + 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.state === 0 ) { + fx.start = getColor( fx.elem, attr ); + fx.end = $.colorUtil.getRGB( fx.end ); + } + + 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 ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.colorUtil.js b/resources/jquery/jquery.colorUtil.js index 1116aec6..c1fe7fe3 100644 --- a/resources/jquery/jquery.colorUtil.js +++ b/resources/jquery/jquery.colorUtil.js @@ -2,192 +2,215 @@ * jQuery Color Utilities * Written by Krinkle in 2011 * Released under the MIT and GPL licenses. - * Mostly based on other plugins and functions (taken through JSLint and optimized a little). - * Sources cited locally. + * Mostly based on other plugins and functions (linted and optimized a little). + * Sources cited inline. */ -( function( $ ) { -$.colorUtil = { - - // Color Conversion function from highlightFade - // By Blair Mitchelmore - // http://jquery.offput.ca/highlightFade/ - // Parse strings looking for color tuples [255,255,255] - getRGB : function( color ) { - var result; - - // Check if we're already dealing with an array of colors - if ( color && color.constructor == Array && color.length == 3 ){ - return color; - } +( function ( $ ) { + $.colorUtil = { - // 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)]; - } + // Color Conversion function from highlightFade + // By Blair Mitchelmore + // http://jquery.offput.ca/highlightFade/ + // Parse strings looking for color tuples [255,255,255] + getRGB : function ( color ) { + /*jshint boss:true */ + var result; - // 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],10)*2.55, parseFloat(result[2],10)*2.55, parseFloat(result[3])*2.55]; - } + // Check if we're already dealing with an array of colors + if ( color && $.isArray( color ) && color.length === 3 ) { + return color; + } - // 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 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 #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 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],10)*2.55, parseFloat(result[2],10)*2.55, parseFloat(result[3])*2.55]; + } - // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 - if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) { - return $.colorUtil.colors.transparent; - } + // 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)]; + } - // Otherwise, we're most likely dealing with a named color - return $.colorUtil.colors[$.trim(color).toLowerCase()]; - }, - - // Some named colors to work with - // From Interface by Stefan Petre - // http://interface.eyecon.ro/ - 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] - }, - /** - * http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * 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 Array The HSL representation - */ - rgbToHsl: function( R, G, B ) { - var r = R / 255, - g = G / 255, - b = B / 255; - var max = Math.max(r, g, b), min = Math.min(r, g, b); - var h, s, l = (max + min) / 2; - - if(max == min){ - h = s = 0; // achromatic - }else{ - var 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; + // 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; } - h /= 6; - } - return [h, s, l]; - }, - /** - * http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * 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 Array The RGB representation - */ - hslToRgb: function( h, s, l ) { - var r, g, b; - - if(s === 0){ - r = g = b = l; // achromatic - }else{ - var 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; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); + // Otherwise, we're most likely dealing with a named color + return $.colorUtil.colors[$.trim(color).toLowerCase()]; + }, + + // Some named colors to work with + // From Interface by Stefan Petre + // http://interface.eyecon.ro/ + 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] + }, + + /** + * http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * 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 Array The HSL representation + */ + rgbToHsl: function ( R, G, B ) { + var r = R / 255, + g = G / 255, + b = B / 255; + var max = Math.max( r, g, b ), min = Math.min( r, g, b ); + var h, s, l = (max + min) / 2; + + if ( max === min ) { + // achromatic + h = s = 0; + } else { + var 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]; + }, + + /** + * http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * 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 Array The RGB representation + */ + hslToRgb: function ( h, s, l ) { + var r, g, b; + + if ( s === 0 ) { + r = g = b = l; // achromatic + } else { + var 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; + }; + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var 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's a brighter or darker rgb() value string. + * + * @author Krinkle + * + * @example getCSSColorMod( 'red', +0.1 ) + * @example getCSSColorMod( 'rgb(200,50,50)', -0.2 ) + * + * @param Mixed currentColor current value in css + * @param Number mod wanted brightness modification between -1 and 1 + * @return String '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( ',' ) + + ')'; } - return [r * 255, g * 255, b * 255]; - }, - /** - * Get's a brighter or darker rgb() value string. - * - * @author Krinkle - * - * @example getCSSColorMod( 'red', +0.1 ) - * @example getCSSColorMod( 'rgb(200,50,50)', -0.2 ) - * - * @param Mixed currentColor current value in css - * @param Number mod wanted brightness modification between -1 and 1 - * @return String '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 );
\ No newline at end of file + }; + +}( jQuery ) ); diff --git a/resources/jquery/jquery.cookie.js b/resources/jquery/jquery.cookie.js index 79317f74..6d5974a2 100644 --- a/resources/jquery/jquery.cookie.js +++ b/resources/jquery/jquery.cookie.js @@ -1,96 +1,47 @@ -/** - * Cookie plugin +/*! + * jQuery Cookie Plugin + * https://github.com/carhartl/jquery-cookie * - * Copyright (c) 2006 Klaus Hartl (stilbuero.de) - * Dual licensed under the MIT and GPL licenses: + * Copyright 2011, Klaus Hartl + * Dual licensed under the MIT or GPL Version 2 licenses. * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * + * http://www.opensource.org/licenses/GPL-2.0 */ +(function($) { + $.cookie = function(key, value, options) { -/** - * Create a cookie with the given name and value and other optional parameters. - * - * @example $.cookie('the_cookie', 'the_value'); - * @desc Set the value of a cookie. - * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); - * @desc Create a cookie with all available options. - * @example $.cookie('the_cookie', 'the_value'); - * @desc Create a session cookie. - * @example $.cookie('the_cookie', null); - * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain - * used when the cookie was set. - * - * @param String name The name of the cookie. - * @param String value The value of the cookie. - * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. - * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. - * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. - * If set to null or omitted, the cookie will be a session cookie and will not be retained - * when the the browser exits. - * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). - * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). - * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will - * require a secure protocol (like HTTPS). - * @type undefined - * - * @name $.cookie - * @cat Plugins/Cookie - * @author Klaus Hartl/klaus.hartl@stilbuero.de - */ + // key and at least value given, set cookie... + if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) { + options = $.extend({}, options); -/** - * Get the value of a cookie with the given name. - * - * @example $.cookie('the_cookie'); - * @desc Get the value of a cookie. - * - * @param String name The name of the cookie. - * @return The value of the cookie. - * @type String - * - * @name $.cookie - * @cat Plugins/Cookie - * @author Klaus Hartl/klaus.hartl@stilbuero.de - */ -jQuery.cookie = function(name, value, options) { - if (typeof value != 'undefined') { // name and value given, set cookie - options = options || {}; - if (value === null) { - value = ''; - options.expires = -1; - } - var expires = ''; - if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { - var date; - if (typeof options.expires == 'number') { - date = new Date(); - date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); - } else { - date = options.expires; + if (value === null || value === undefined) { + options.expires = -1; } - expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE - } - // CAUTION: Needed to parenthesize options.path and options.domain - // in the following expressions, otherwise they evaluate to undefined - // in the packed version for some reason... - var path = options.path ? '; path=' + (options.path) : ''; - var domain = options.domain ? '; domain=' + (options.domain) : ''; - var secure = options.secure ? '; secure' : ''; - document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); - } else { // only name given, get cookie - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } + + if (typeof options.expires === 'number') { + var days = options.expires, t = options.expires = new Date(); + t.setDate(t.getDate() + days); } + + value = String(value); + + return (document.cookie = [ + encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); + } + + // key and possibly options given, get cookie... + options = value || {}; + var decode = options.raw ? function(s) { return s; } : decodeURIComponent; + + var pairs = document.cookie.split('; '); + for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) { + if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined } - return cookieValue; - } -}; + return null; + }; +})(jQuery); diff --git a/resources/jquery/jquery.delayedBind.js b/resources/jquery/jquery.delayedBind.js index d84ee267..5d32b6b0 100644 --- a/resources/jquery/jquery.delayedBind.js +++ b/resources/jquery/jquery.delayedBind.js @@ -1,4 +1,4 @@ -(function( $ ) { +( function ( $ ) { /** * Function that escapes spaces in event names. This is needed because * "_delayedBind-foo bar-1000" refers to two events @@ -18,25 +18,26 @@ $.fn.extend( { * @param data Data to pass to the event handler (optional) * @param callback Function to call */ - delayedBind: function( timeout, event, data, callback ) { - if ( arguments.length == 3 ) { + delayedBind: function ( timeout, event, data, callback ) { + if ( arguments.length === 3 ) { // Shift optional parameter down callback = data; data = undefined; } var encEvent = encodeEvent( event ); - return this.each( function() { + return this.each( function () { var that = this; // Bind the top half // Do this only once for every (event, timeout) pair if ( !( $(this).data( '_delayedBindBound-' + encEvent + '-' + timeout ) ) ) { $(this).data( '_delayedBindBound-' + encEvent + '-' + timeout, true ); - $(this).bind( event, function() { + $(this).bind( event, function () { var timerID = $(this).data( '_delayedBindTimerID-' + encEvent + '-' + timeout ); // Cancel the running timer - if ( typeof timerID != 'undefined' ) + if ( timerID !== null ) { clearTimeout( timerID ); - timerID = setTimeout( function() { + } + timerID = setTimeout( function () { $(that).trigger( '_delayedBind-' + encEvent + '-' + timeout ); }, timeout ); $(this).data( '_delayedBindTimerID-' + encEvent + '-' + timeout, timerID ); @@ -51,23 +52,25 @@ $.fn.extend( { /** * Cancel the timers for delayed events on the selected elements. */ - delayedBindCancel: function( timeout, event ) { + delayedBindCancel: function ( timeout, event ) { var encEvent = encodeEvent( event ); - return this.each( function() { + return this.each( function () { var timerID = $(this).data( '_delayedBindTimerID-' + encEvent + '-' + timeout ); - if ( typeof timerID != 'undefined' ) + if ( timerID !== null ) { clearTimeout( timerID ); + } } ); }, /** * Unbind an event bound with delayedBind() */ - delayedBindUnbind: function( timeout, event, callback ) { + delayedBindUnbind: function ( timeout, event, callback ) { var encEvent = encodeEvent( event ); - return this.each( function() { + return this.each( function () { $(this).unbind( '_delayedBind-' + encEvent + '-' + timeout, callback ); } ); } } ); -} )( jQuery );
\ No newline at end of file + +}( jQuery ) ); diff --git a/resources/jquery/jquery.expandableField.js b/resources/jquery/jquery.expandableField.js index 3c65d016..063f2609 100644 --- a/resources/jquery/jquery.expandableField.js +++ b/resources/jquery/jquery.expandableField.js @@ -14,116 +14,124 @@ * Options: * */ -( function( $ ) { - -$.expandableField = { - /** - * Expand the field, make the callback - */ - expandField: function( e, context ) { - context.config.beforeExpand.call( context.data.$field, context ); - context.data.$field - .animate( { 'width': context.data.expandedWidth }, 'fast', function() { - context.config.afterExpand.call( this, context ); - } ); - }, - /** - * Condense the field, make the callback - */ - condenseField: function( e, context ) { - context.config.beforeCondense.call( context.data.$field, context ); - context.data.$field - .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 - */ - configure: function( context, property, value ) { - // Validate creation using fallback values - switch( property ) { - default: - context.config[property] = value; - break; - } - } - -}; -$.fn.expandableField = function() { - - // Multi-context fields - var returnValue = null; - var args = arguments; - - $( this ).each( function() { - - /* Construction / Loading */ - - var context = $( this ).data( 'expandableField-context' ); - if ( context == null ) { - context = { - config: { - // callback function for before collapse - 'beforeCondense': function( context ) {}, - // callback function for before expand - 'beforeExpand': function( context ) {}, - // callback function for after collapse - 'afterCondense': function( context ) {}, - // callback function for after expand - 'afterExpand': function( context ) {}, - // Whether the field should expand to the left or the right -- defaults to left - 'expandToLeft': true - } - }; +( function ( $ ) { + + $.expandableField = { + /** + * Expand the field, make the callback + */ + expandField: function ( e, context ) { + context.config.beforeExpand.call( context.data.$field, context ); + context.data.$field + .animate( { 'width': context.data.expandedWidth }, 'fast', function () { + context.config.afterExpand.call( this, context ); + } ); + }, + /** + * Condense the field, make the callback + */ + condenseField: function ( e, context ) { + context.config.beforeCondense.call( context.data.$field, context ); + context.data.$field + .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 + */ + configure: function ( context, property, value ) { + // TODO: Validate creation using fallback values + context.config[property] = value; } - - /* API */ - // Handle various calling styles - if ( args.length > 0 ) { - if ( typeof args[0] == 'object' ) { - // Apply set of properties - for ( var key in args[0] ) { - $.expandableField.configure( context, key, args[0][key] ); - } - } else if ( typeof args[0] == 'string' ) { - if ( args.length > 1 ) { - // Set property values - $.expandableField.configure( context, args[0], args[1] ); - } else if ( returnValue == null ) { - // 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]] ); + + }; + + $.fn.expandableField = function () { + + // Multi-context fields + var returnValue, + args = arguments; + + $( this ).each( function () { + var key, context; + + /* Construction / Loading */ + + context = $( this ).data( 'expandableField-context' ); + + // TODO: Do we need to check both null and undefined? + if ( context === undefined || context === null ) { + context = { + config: { + // callback function for before collapse + beforeCondense: function ( context ) {}, + + // callback function for before expand + beforeExpand: function ( context ) {}, + + // callback function for after collapse + afterCondense: function ( context ) {}, + + // callback function for after expand + afterExpand: function ( context ) {}, + + // Whether the field should expand to the left or the right -- defaults to left + expandToLeft: true + } + }; + } + + /* API */ + // Handle various calling styles + if ( args.length > 0 ) { + if ( typeof args[0] === 'object' ) { + // Apply set of properties + for ( key in args[0] ) { + $.expandableField.configure( context, key, args[0][key] ); + } + } else if ( typeof args[0] === 'string' ) { + if ( args.length > 1 ) { + // Set property values + $.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]] ); + } } } - } - - /* Initialization */ - - if ( typeof context.data == 'undefined' ) { - context.data = { - // The width of the field in it's condensed state - 'condensedWidth': $( this ).width(), - // The width of the field in it's expanded state - 'expandedWidth': $( this ).width() * 2, - // Reference to the field - '$field': $( this ) - }; - - $( this ) - .addClass( 'expandableField' ) - .focus( function( e ) { - $.expandableField.expandField( e, context ); - } ) - .delayedBind( 250, 'blur', function( e ) { - $.expandableField.condenseField( e, context ); - } ); - } - // Store the context for next time - $( this ).data( 'expandableField-context', context ); - } ); - return returnValue !== null ? returnValue : $(this); -}; -} )( jQuery ); + /* Initialization */ + + if ( context.data === undefined ) { + context.data = { + // The width of the field in it's condensed state + condensedWidth: $( this ).width(), + + // The width of the field in it's expanded state + expandedWidth: $( this ).width() * 2, + + // Reference to the field + $field: $( this ) + }; + + $( this ) + .addClass( 'expandableField' ) + .focus( function ( e ) { + $.expandableField.expandField( e, context ); + } ) + .delayedBind( 250, 'blur', function ( e ) { + $.expandableField.condenseField( e, context ); + } ); + } + // Store the context for next time + $( this ).data( 'expandableField-context', context ); + } ); + return returnValue !== undefined ? returnValue : $(this); + }; + +}( jQuery ) ); diff --git a/resources/jquery/jquery.form.js b/resources/jquery/jquery.form.js index fdcdd15a..13e9a55c 100644 --- a/resources/jquery/jquery.form.js +++ b/resources/jquery/jquery.form.js @@ -1,557 +1,677 @@ /*! * jQuery Form Plugin - * version: 2.84 (12-AUG-2011) + * version: 3.14 (30-JUL-2012) * @requires jQuery v1.3.2 or later * * Examples and documentation at: http://malsup.com/jquery/form/ + * Project repository: https://github.com/malsup/form * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html + * http://malsup.github.com/mit-license.txt + * http://malsup.github.com/gpl-license-v2.txt */ +/*global ActiveXObject alert */ ;(function($) { +"use strict"; /* - Usage Note: - ----------- - Do not use both ajaxSubmit and ajaxForm on the same form. These - functions are intended to be exclusive. Use ajaxSubmit if you want - to bind your own submit handler to the form. For example, - - $(document).ready(function() { - $('#myForm').bind('submit', function(e) { - e.preventDefault(); // <-- important - $(this).ajaxSubmit({ - target: '#output' - }); - }); - }); - - Use ajaxForm when you want the plugin to manage all the event binding - for you. For example, - - $(document).ready(function() { - $('#myForm').ajaxForm({ - target: '#output' - }); - }); - - When using ajaxForm, the ajaxSubmit function will be invoked for you - at the appropriate time. + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are mutually exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').on('submit', function(e) { + e.preventDefault(); // <-- important + $(this).ajaxSubmit({ + target: '#output' + }); + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + You can also use ajaxForm with delegation (requires jQuery v1.7+), so the + form does not have to exist when you invoke ajaxForm: + + $('#myForm').ajaxForm({ + delegation: true, + target: '#output' + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. */ /** + * Feature detection + */ +var feature = {}; +feature.fileapi = $("<input type='file'/>").get(0).files !== undefined; +feature.formdata = window.FormData !== undefined; + +/** * ajaxSubmit() provides a mechanism for immediately submitting * an HTML form using AJAX. */ $.fn.ajaxSubmit = function(options) { - // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) - if (!this.length) { - log('ajaxSubmit: skipping submit process - no element selected'); - return this; - } - - var method, action, url, $form = this; - - if (typeof options == 'function') { - options = { success: options }; - } - - method = this.attr('method'); - action = this.attr('action'); - url = (typeof action === 'string') ? $.trim(action) : ''; - url = url || window.location.href || ''; - if (url) { - // clean url (don't include hash vaue) - url = (url.match(/^([^#]+)/)||[])[1]; - } - - options = $.extend(true, { - url: url, - success: $.ajaxSettings.success, - type: method || 'GET', - iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' - }, options); - - // hook for manipulating the form data before it is extracted; - // convenient for use with rich editors like tinyMCE or FCKEditor - var veto = {}; - this.trigger('form-pre-serialize', [this, options, veto]); - if (veto.veto) { - log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); - return this; - } - - // provide opportunity to alter form data before it is serialized - if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { - log('ajaxSubmit: submit aborted via beforeSerialize callback'); - return this; - } - - var n,v,a = this.formToArray(options.semantic); - if (options.data) { - options.extraData = options.data; - for (n in options.data) { - if( $.isArray(options.data[n]) ) { - for (var k in options.data[n]) { - a.push( { name: n, value: options.data[n][k] } ); - } - } - else { - v = options.data[n]; - v = $.isFunction(v) ? v() : v; // if value is fn, invoke it - a.push( { name: n, value: v } ); - } - } - } - - // give pre-submit callback an opportunity to abort the submit - if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { - log('ajaxSubmit: submit aborted via beforeSubmit callback'); - return this; - } - - // fire vetoable 'validate' event - this.trigger('form-submit-validate', [a, this, options, veto]); - if (veto.veto) { - log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); - return this; - } - - var q = $.param(a); - - if (options.type.toUpperCase() == 'GET') { - options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; - options.data = null; // data is null for 'get' - } - else { - options.data = q; // data is the query string for 'post' - } - - var callbacks = []; - if (options.resetForm) { - callbacks.push(function() { $form.resetForm(); }); - } - if (options.clearForm) { - callbacks.push(function() { $form.clearForm(); }); - } - - // perform a load on the target only if dataType is not provided - if (!options.dataType && options.target) { - var oldSuccess = options.success || function(){}; - callbacks.push(function(data) { - var fn = options.replaceTarget ? 'replaceWith' : 'html'; - $(options.target)[fn](data).each(oldSuccess, arguments); - }); - } - else if (options.success) { - callbacks.push(options.success); - } - - options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg - var context = options.context || options; // jQuery 1.4+ supports scope context - for (var i=0, max=callbacks.length; i < max; i++) { - callbacks[i].apply(context, [data, status, xhr || $form, $form]); - } - }; - - // are there files to upload? - var fileInputs = $('input:file', this).length > 0; - var mp = 'multipart/form-data'; - var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); - - // options.iframe allows user to force iframe mode - // 06-NOV-09: now defaulting to iframe mode if file input is detected - if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { - // hack to fix Safari hang (thanks to Tim Molendijk for this) - // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d - if (options.closeKeepAlive) { - $.get(options.closeKeepAlive, function() { fileUpload(a); }); - } - else { - fileUpload(a); - } - } - else { - // IE7 massage (see issue 57) - if ($.browser.msie && method == 'get') { - var ieMeth = $form[0].getAttribute('method'); - if (typeof ieMeth === 'string') - options.type = ieMeth; - } - $.ajax(options); - } - - // fire 'notify' event - this.trigger('form-submit-notify', [this, options]); - return this; - - - // private function for handling file uploads (hat tip to YAHOO!) - function fileUpload(a) { - var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; + /*jshint scripturl:true */ + + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + var method, action, url, $form = this; + + if (typeof options == 'function') { + options = { success: options }; + } + + method = this.attr('method'); + action = this.attr('action'); + url = (typeof action === 'string') ? $.trim(action) : ''; + url = url || window.location.href || ''; + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + + options = $.extend(true, { + url: url, + success: $.ajaxSettings.success, + type: method || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var traditional = options.traditional; + if ( traditional === undefined ) { + traditional = $.ajaxSettings.traditional; + } + + var elements = []; + var qx, a = this.formToArray(options.semantic, elements); + if (options.data) { + options.extraData = options.data; + qx = $.param(options.data, traditional); + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a, traditional); + if (qx) { + q = ( q ? (q + '&' + qx) : qx ); + } + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else { + options.data = q; // data is the query string for 'post' + } + + var callbacks = []; + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(options.includeHidden); }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } + else if (options.success) { + callbacks.push(options.success); + } + + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || this ; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + // are there files to upload? + var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113) + var hasFileInputs = fileInputs.length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + var fileAPI = feature.fileapi && feature.formdata; + log("fileAPI :" + fileAPI); + var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (options.iframe || shouldUseFrame)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, function() { + fileUploadIframe(a); + }); + } + else { + fileUploadIframe(a); + } + } + else if ((hasFileInputs || multipart) && fileAPI) { + fileUploadXhr(a); + } + else { + $.ajax(options); + } + + // clear element array + for (var k=0; k < elements.length; k++) + elements[k] = null; + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) + function fileUploadXhr(a) { + var formdata = new FormData(); + + for (var i=0; i < a.length; i++) { + formdata.append(a[i].name, a[i].value); + } + + if (options.extraData) { + for (var p in options.extraData) + if (options.extraData.hasOwnProperty(p)) + formdata.append(p, options.extraData[p]); + } + + options.data = null; + + var s = $.extend(true, {}, $.ajaxSettings, options, { + contentType: false, + processData: false, + cache: false, + type: 'POST' + }); + + if (options.uploadProgress) { + // workaround because jqXHR does not expose upload property + s.xhr = function() { + var xhr = jQuery.ajaxSettings.xhr(); + if (xhr.upload) { + xhr.upload.onprogress = function(event) { + var percent = 0; + var position = event.loaded || event.position; /*event.position is deprecated*/ + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil(position / total * 100); + } + options.uploadProgress(event, position, total, percent); + }; + } + return xhr; + }; + } + + s.data = null; + var beforeSend = s.beforeSend; + s.beforeSend = function(xhr, o) { + o.data = formdata; + if(beforeSend) + beforeSend.call(this, xhr, o); + }; + $.ajax(s); + } + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUploadIframe(a) { + var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; var useProp = !!$.fn.prop; + if ($(':input[name=submit],:input[id=submit]', form).length) { + // if there is an input with a name or id of 'submit' then we won't be + // able to invoke the submit fn on the form (at least not x-browser) + alert('Error: Form elements must not have name or id of "submit".'); + return; + } + if (a) { - // ensure that every serialized input is still enabled - for (i=0; i < a.length; i++) { - el = $(form[a[i].name]); - el[ useProp ? 'prop' : 'attr' ]('disabled', false); - } - } - - if ($(':input[name=submit],:input[id=submit]', form).length) { - // if there is an input with a name or id of 'submit' then we won't be - // able to invoke the submit fn on the form (at least not x-browser) - alert('Error: Form elements must not have name or id of "submit".'); - return; - } - - s = $.extend(true, {}, $.ajaxSettings, options); - s.context = s.context || s; - id = 'jqFormIO' + (new Date().getTime()); - if (s.iframeTarget) { - $io = $(s.iframeTarget); - n = $io.attr('name'); - if (n == null) - $io.attr('name', id); - else - id = n; - } - else { - $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />'); - $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); - } - io = $io[0]; - - - xhr = { // mock object - aborted: 0, - responseText: null, - responseXML: null, - status: 0, - statusText: 'n/a', - getAllResponseHeaders: function() {}, - getResponseHeader: function() {}, - setRequestHeader: function() {}, - abort: function(status) { - var e = (status === 'timeout' ? 'timeout' : 'aborted'); - log('aborting upload... ' + e); - this.aborted = 1; - $io.attr('src', s.iframeSrc); // abort op in progress - xhr.error = e; - s.error && s.error.call(s.context, xhr, e, status); - g && $.event.trigger("ajaxError", [xhr, s, e]); - s.complete && s.complete.call(s.context, xhr, e); - } - }; - - g = s.global; - // trigger ajax global events so that activity/block indicators work like normal - if (g && ! $.active++) { - $.event.trigger("ajaxStart"); - } - if (g) { - $.event.trigger("ajaxSend", [xhr, s]); - } - - if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { - if (s.global) { - $.active--; - } - return; - } - if (xhr.aborted) { - return; - } - - // add submitting element to data if we know it - sub = form.clk; - if (sub) { - n = sub.name; - if (n && !sub.disabled) { - s.extraData = s.extraData || {}; - s.extraData[n] = sub.value; - if (sub.type == "image") { - s.extraData[n+'.x'] = form.clk_x; - s.extraData[n+'.y'] = form.clk_y; - } - } - } - - var CLIENT_TIMEOUT_ABORT = 1; - var SERVER_ABORT = 2; - - function getDoc(frame) { - var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document; - return doc; - } - - // take a breath so that pending repaints get some cpu time before the upload starts - function doSubmit() { - // make sure form attrs are set - var t = $form.attr('target'), a = $form.attr('action'); - - // update form attrs in IE friendly way - form.setAttribute('target',id); - if (!method) { - form.setAttribute('method', 'POST'); - } - if (a != s.url) { - form.setAttribute('action', s.url); - } - - // ie borks in some cases when setting encoding - if (! s.skipEncodingOverride && (!method || /post/i.test(method))) { - $form.attr({ - encoding: 'multipart/form-data', - enctype: 'multipart/form-data' - }); - } - - // support timout - if (s.timeout) { - timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout); - } - - // look for server aborts - function checkState() { - try { - var state = getDoc(io).readyState; - log('state = ' + state); - if (state.toLowerCase() == 'uninitialized') - setTimeout(checkState,50); - } - catch(e) { - log('Server abort: ' , e, ' (', e.name, ')'); - cb(SERVER_ABORT); - timeoutHandle && clearTimeout(timeoutHandle); - timeoutHandle = undefined; - } - } - - // add "extra" data to form if provided in options - var extraInputs = []; - try { - if (s.extraData) { - for (var n in s.extraData) { - extraInputs.push( - $('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n]) - .appendTo(form)[0]); - } - } - - if (!s.iframeTarget) { - // add iframe to doc and submit the form - $io.appendTo('body'); - io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false); - } - setTimeout(checkState,15); - form.submit(); - } - finally { - // reset attrs and remove "extra" input elements - form.setAttribute('action',a); - if(t) { - form.setAttribute('target', t); - } else { - $form.removeAttr('target'); - } - $(extraInputs).remove(); - } - } - - if (s.forceSync) { - doSubmit(); - } - else { - setTimeout(doSubmit, 10); // this lets dom updates render - } - - var data, doc, domCheckCount = 50, callbackProcessed; - - function cb(e) { - if (xhr.aborted || callbackProcessed) { - return; - } - try { - doc = getDoc(io); - } - catch(ex) { - log('cannot access response document: ', ex); - e = SERVER_ABORT; - } - if (e === CLIENT_TIMEOUT_ABORT && xhr) { - xhr.abort('timeout'); - return; - } - else if (e == SERVER_ABORT && xhr) { - xhr.abort('server abort'); - return; - } - - if (!doc || doc.location.href == s.iframeSrc) { - // response not received yet - if (!timedOut) - return; - } - io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false); - - var status = 'success', errMsg; - try { - if (timedOut) { - throw 'timeout'; - } - - var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); - log('isXml='+isXml); - if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) { - if (--domCheckCount) { - // in some browsers (Opera) the iframe DOM is not always traversable when - // the onload callback fires, so we loop a bit to accommodate - log('requeing onLoad callback, DOM not available'); - setTimeout(cb, 250); - return; - } - // let this fall through because server response could be an empty document - //log('Could not access iframe DOM after mutiple tries.'); - //throw 'DOMException: not available'; - } - - //log('response detected'); + // ensure that every serialized input is still enabled + for (i=0; i < elements.length; i++) { + el = $(elements[i]); + if ( useProp ) + el.prop('disabled', false); + else + el.removeAttr('disabled'); + } + } + + s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + id = 'jqFormIO' + (new Date().getTime()); + if (s.iframeTarget) { + $io = $(s.iframeTarget); + n = $io.attr('name'); + if (!n) + $io.attr('name', id); + else + id = n; + } + else { + $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />'); + $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); + } + io = $io[0]; + + + xhr = { // mock object + aborted: 0, + responseText: null, + responseXML: null, + status: 0, + statusText: 'n/a', + getAllResponseHeaders: function() {}, + getResponseHeader: function() {}, + setRequestHeader: function() {}, + abort: function(status) { + var e = (status === 'timeout' ? 'timeout' : 'aborted'); + log('aborting upload... ' + e); + this.aborted = 1; + // #214 + if (io.contentWindow.document.execCommand) { + try { // #214 + io.contentWindow.document.execCommand('Stop'); + } catch(ignore) {} + } + $io.attr('src', s.iframeSrc); // abort op in progress + xhr.error = e; + if (s.error) + s.error.call(s.context, xhr, e, status); + if (g) + $.event.trigger("ajaxError", [xhr, s, e]); + if (s.complete) + s.complete.call(s.context, xhr, e); + } + }; + + g = s.global; + // trigger ajax global events so that activity/block indicators work like normal + if (g && 0 === $.active++) { + $.event.trigger("ajaxStart"); + } + if (g) { + $.event.trigger("ajaxSend", [xhr, s]); + } + + if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { + if (s.global) { + $.active--; + } + return; + } + if (xhr.aborted) { + return; + } + + // add submitting element to data if we know it + sub = form.clk; + if (sub) { + n = sub.name; + if (n && !sub.disabled) { + s.extraData = s.extraData || {}; + s.extraData[n] = sub.value; + if (sub.type == "image") { + s.extraData[n+'.x'] = form.clk_x; + s.extraData[n+'.y'] = form.clk_y; + } + } + } + + var CLIENT_TIMEOUT_ABORT = 1; + var SERVER_ABORT = 2; + + function getDoc(frame) { + var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document; + return doc; + } + + // Rails CSRF hack (thanks to Yvan Barthelemy) + var csrf_token = $('meta[name=csrf-token]').attr('content'); + var csrf_param = $('meta[name=csrf-param]').attr('content'); + if (csrf_param && csrf_token) { + s.extraData = s.extraData || {}; + s.extraData[csrf_param] = csrf_token; + } + + // take a breath so that pending repaints get some cpu time before the upload starts + function doSubmit() { + // make sure form attrs are set + var t = $form.attr('target'), a = $form.attr('action'); + + // update form attrs in IE friendly way + form.setAttribute('target',id); + if (!method) { + form.setAttribute('method', 'POST'); + } + if (a != s.url) { + form.setAttribute('action', s.url); + } + + // ie borks in some cases when setting encoding + if (! s.skipEncodingOverride && (!method || /post/i.test(method))) { + $form.attr({ + encoding: 'multipart/form-data', + enctype: 'multipart/form-data' + }); + } + + // support timout + if (s.timeout) { + timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout); + } + + // look for server aborts + function checkState() { + try { + var state = getDoc(io).readyState; + log('state = ' + state); + if (state && state.toLowerCase() == 'uninitialized') + setTimeout(checkState,50); + } + catch(e) { + log('Server abort: ' , e, ' (', e.name, ')'); + cb(SERVER_ABORT); + if (timeoutHandle) + clearTimeout(timeoutHandle); + timeoutHandle = undefined; + } + } + + // add "extra" data to form if provided in options + var extraInputs = []; + try { + if (s.extraData) { + for (var n in s.extraData) { + if (s.extraData.hasOwnProperty(n)) { + // if using the $.param format that allows for multiple values with the same name + if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) { + extraInputs.push( + $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value) + .appendTo(form)[0]); + } else { + extraInputs.push( + $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n]) + .appendTo(form)[0]); + } + } + } + } + + if (!s.iframeTarget) { + // add iframe to doc and submit the form + $io.appendTo('body'); + if (io.attachEvent) + io.attachEvent('onload', cb); + else + io.addEventListener('load', cb, false); + } + setTimeout(checkState,15); + form.submit(); + } + finally { + // reset attrs and remove "extra" input elements + form.setAttribute('action',a); + if(t) { + form.setAttribute('target', t); + } else { + $form.removeAttr('target'); + } + $(extraInputs).remove(); + } + } + + if (s.forceSync) { + doSubmit(); + } + else { + setTimeout(doSubmit, 10); // this lets dom updates render + } + + var data, doc, domCheckCount = 50, callbackProcessed; + + function cb(e) { + if (xhr.aborted || callbackProcessed) { + return; + } + try { + doc = getDoc(io); + } + catch(ex) { + log('cannot access response document: ', ex); + e = SERVER_ABORT; + } + if (e === CLIENT_TIMEOUT_ABORT && xhr) { + xhr.abort('timeout'); + return; + } + else if (e == SERVER_ABORT && xhr) { + xhr.abort('server abort'); + return; + } + + if (!doc || doc.location.href == s.iframeSrc) { + // response not received yet + if (!timedOut) + return; + } + if (io.detachEvent) + io.detachEvent('onload', cb); + else + io.removeEventListener('load', cb, false); + + var status = 'success', errMsg; + try { + if (timedOut) { + throw 'timeout'; + } + + var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); + log('isXml='+isXml); + if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) { + if (--domCheckCount) { + // in some browsers (Opera) the iframe DOM is not always traversable when + // the onload callback fires, so we loop a bit to accommodate + log('requeing onLoad callback, DOM not available'); + setTimeout(cb, 250); + return; + } + // let this fall through because server response could be an empty document + //log('Could not access iframe DOM after mutiple tries.'); + //throw 'DOMException: not available'; + } + + //log('response detected'); var docRoot = doc.body ? doc.body : doc.documentElement; xhr.responseText = docRoot ? docRoot.innerHTML : null; - xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; - if (isXml) - s.dataType = 'xml'; - xhr.getResponseHeader = function(header){ - var headers = {'content-type': s.dataType}; - return headers[header]; - }; + xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; + if (isXml) + s.dataType = 'xml'; + xhr.getResponseHeader = function(header){ + var headers = {'content-type': s.dataType}; + return headers[header]; + }; // support for XHR 'status' & 'statusText' emulation : if (docRoot) { xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status; xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText; } - var dt = s.dataType || ''; - var scr = /(json|script|text)/.test(dt.toLowerCase()); - if (scr || s.textarea) { - // see if user embedded response in textarea - var ta = doc.getElementsByTagName('textarea')[0]; - if (ta) { - xhr.responseText = ta.value; + var dt = (s.dataType || '').toLowerCase(); + var scr = /(json|script|text)/.test(dt); + if (scr || s.textarea) { + // see if user embedded response in textarea + var ta = doc.getElementsByTagName('textarea')[0]; + if (ta) { + xhr.responseText = ta.value; // support for XHR 'status' & 'statusText' emulation : xhr.status = Number( ta.getAttribute('status') ) || xhr.status; xhr.statusText = ta.getAttribute('statusText') || xhr.statusText; - } - else if (scr) { - // account for browsers injecting pre around json response - var pre = doc.getElementsByTagName('pre')[0]; - var b = doc.getElementsByTagName('body')[0]; - if (pre) { - xhr.responseText = pre.textContent ? pre.textContent : pre.innerHTML; - } - else if (b) { - xhr.responseText = b.innerHTML; - } - } - } - else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { - xhr.responseXML = toXml(xhr.responseText); - } + } + else if (scr) { + // account for browsers injecting pre around json response + var pre = doc.getElementsByTagName('pre')[0]; + var b = doc.getElementsByTagName('body')[0]; + if (pre) { + xhr.responseText = pre.textContent ? pre.textContent : pre.innerText; + } + else if (b) { + xhr.responseText = b.textContent ? b.textContent : b.innerText; + } + } + } + else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) { + xhr.responseXML = toXml(xhr.responseText); + } try { - data = httpData(xhr, s.dataType, s); + data = httpData(xhr, dt, s); } catch (e) { status = 'parsererror'; xhr.error = errMsg = (e || status); } - } - catch (e) { - log('error caught: ',e); - status = 'error'; + } + catch (e) { + log('error caught: ',e); + status = 'error'; xhr.error = errMsg = (e || status); - } + } - if (xhr.aborted) { - log('upload aborted'); - status = null; - } + if (xhr.aborted) { + log('upload aborted'); + status = null; + } if (xhr.status) { // we've set xhr.status status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error'; } - // ordering of these callbacks/triggers is odd, but that's how $.ajax does it - if (status === 'success') { - s.success && s.success.call(s.context, data, 'success', xhr); - g && $.event.trigger("ajaxSuccess", [xhr, s]); - } + // ordering of these callbacks/triggers is odd, but that's how $.ajax does it + if (status === 'success') { + if (s.success) + s.success.call(s.context, data, 'success', xhr); + if (g) + $.event.trigger("ajaxSuccess", [xhr, s]); + } else if (status) { - if (errMsg == undefined) - errMsg = xhr.statusText; - s.error && s.error.call(s.context, xhr, status, errMsg); - g && $.event.trigger("ajaxError", [xhr, s, errMsg]); - } - - g && $.event.trigger("ajaxComplete", [xhr, s]); - - if (g && ! --$.active) { - $.event.trigger("ajaxStop"); - } - - s.complete && s.complete.call(s.context, xhr, status); - - callbackProcessed = true; - if (s.timeout) - clearTimeout(timeoutHandle); - - // clean up - setTimeout(function() { - if (!s.iframeTarget) - $io.remove(); - xhr.responseXML = null; - }, 100); - } - - var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+) - if (window.ActiveXObject) { - doc = new ActiveXObject('Microsoft.XMLDOM'); - doc.async = 'false'; - doc.loadXML(s); - } - else { - doc = (new DOMParser()).parseFromString(s, 'text/xml'); - } - return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null; - }; - var parseJSON = $.parseJSON || function(s) { - return window['eval']('(' + s + ')'); - }; - - var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4 - - var ct = xhr.getResponseHeader('content-type') || '', - xml = type === 'xml' || !type && ct.indexOf('xml') >= 0, - data = xml ? xhr.responseXML : xhr.responseText; - - if (xml && data.documentElement.nodeName === 'parsererror') { - $.error && $.error('parsererror'); - } - if (s && s.dataFilter) { - data = s.dataFilter(data, type); - } - if (typeof data === 'string') { - if (type === 'json' || !type && ct.indexOf('json') >= 0) { - data = parseJSON(data); - } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) { - $.globalEval(data); - } - } - return data; - }; - } + if (errMsg === undefined) + errMsg = xhr.statusText; + if (s.error) + s.error.call(s.context, xhr, status, errMsg); + if (g) + $.event.trigger("ajaxError", [xhr, s, errMsg]); + } + + if (g) + $.event.trigger("ajaxComplete", [xhr, s]); + + if (g && ! --$.active) { + $.event.trigger("ajaxStop"); + } + + if (s.complete) + s.complete.call(s.context, xhr, status); + + callbackProcessed = true; + if (s.timeout) + clearTimeout(timeoutHandle); + + // clean up + setTimeout(function() { + if (!s.iframeTarget) + $io.remove(); + xhr.responseXML = null; + }, 100); + } + + var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+) + if (window.ActiveXObject) { + doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.async = 'false'; + doc.loadXML(s); + } + else { + doc = (new DOMParser()).parseFromString(s, 'text/xml'); + } + return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null; + }; + var parseJSON = $.parseJSON || function(s) { + /*jslint evil:true */ + return window['eval']('(' + s + ')'); + }; + + var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4 + + var ct = xhr.getResponseHeader('content-type') || '', + xml = type === 'xml' || !type && ct.indexOf('xml') >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if (xml && data.documentElement.nodeName === 'parsererror') { + if ($.error) + $.error('parsererror'); + } + if (s && s.dataFilter) { + data = s.dataFilter(data, type); + } + if (typeof data === 'string') { + if (type === 'json' || !type && ct.indexOf('json') >= 0) { + data = parseJSON(data); + } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) { + $.globalEval(data); + } + } + return data; + }; + } }; /** @@ -560,9 +680,9 @@ $.fn.ajaxSubmit = function(options) { * The advantages of using this method instead of ajaxSubmit() are: * * 1: This method will include coordinates for <input type="image" /> elements (if the element - * is used to submit the form). + * is used to submit the form). * 2. This method will include the submit element's name/value data (for the element that was - * used to submit the form). + * used to submit the form). * 3. This method binds the submit() method to the form for you. * * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely @@ -570,60 +690,83 @@ $.fn.ajaxSubmit = function(options) { * the form itself. */ $.fn.ajaxForm = function(options) { - // in jQuery 1.3+ we can fix mistakes with the ready state - if (this.length === 0) { - var o = { s: this.selector, c: this.context }; - if (!$.isReady && o.s) { - log('DOM not ready, queuing ajaxForm'); - $(function() { - $(o.s,o.c).ajaxForm(options); - }); - return this; - } - // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() - log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); - return this; - } - - return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) { - if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed - e.preventDefault(); - $(this).ajaxSubmit(options); - } - }).bind('click.form-plugin', function(e) { - var target = e.target; - var $el = $(target); - if (!($el.is(":submit,input:image"))) { - // is this a child element of the submit el? (ex: a span within a button) - var t = $el.closest(':submit'); - if (t.length == 0) { - return; - } - target = t[0]; - } - var form = this; - form.clk = target; - if (target.type == 'image') { - if (e.offsetX != undefined) { - form.clk_x = e.offsetX; - form.clk_y = e.offsetY; - } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin - var offset = $el.offset(); - form.clk_x = e.pageX - offset.left; - form.clk_y = e.pageY - offset.top; - } else { - form.clk_x = e.pageX - target.offsetLeft; - form.clk_y = e.pageY - target.offsetTop; - } - } - // clear form vars - setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100); - }); + options = options || {}; + options.delegation = options.delegation && $.isFunction($.fn.on); + + // in jQuery 1.3+ we can fix mistakes with the ready state + if (!options.delegation && this.length === 0) { + var o = { s: this.selector, c: this.context }; + if (!$.isReady && o.s) { + log('DOM not ready, queuing ajaxForm'); + $(function() { + $(o.s,o.c).ajaxForm(options); + }); + return this; + } + // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() + log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); + return this; + } + + if ( options.delegation ) { + $(document) + .off('submit.form-plugin', this.selector, doAjaxSubmit) + .off('click.form-plugin', this.selector, captureSubmittingElement) + .on('submit.form-plugin', this.selector, options, doAjaxSubmit) + .on('click.form-plugin', this.selector, options, captureSubmittingElement); + return this; + } + + return this.ajaxFormUnbind() + .bind('submit.form-plugin', options, doAjaxSubmit) + .bind('click.form-plugin', options, captureSubmittingElement); }; +// private event handlers +function doAjaxSubmit(e) { + /*jshint validthis:true */ + var options = e.data; + if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed + e.preventDefault(); + $(this).ajaxSubmit(options); + } +} + +function captureSubmittingElement(e) { + /*jshint validthis:true */ + var target = e.target; + var $el = $(target); + if (!($el.is(":submit,input:image"))) { + // is this a child element of the submit el? (ex: a span within a button) + var t = $el.closest(':submit'); + if (t.length === 0) { + return; + } + target = t[0]; + } + var form = this; + form.clk = target; + if (target.type == 'image') { + if (e.offsetX !== undefined) { + form.clk_x = e.offsetX; + form.clk_y = e.offsetY; + } else if (typeof $.fn.offset == 'function') { + var offset = $el.offset(); + form.clk_x = e.pageX - offset.left; + form.clk_y = e.pageY - offset.top; + } else { + form.clk_x = e.pageX - target.offsetLeft; + form.clk_y = e.pageY - target.offsetTop; + } + } + // clear form vars + setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100); +} + + // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm $.fn.ajaxFormUnbind = function() { - return this.unbind('submit.form-plugin click.form-plugin'); + return this.unbind('submit.form-plugin click.form-plugin'); }; /** @@ -637,56 +780,74 @@ $.fn.ajaxFormUnbind = function() { * It is this array that is passed to pre-submit callback functions provided to the * ajaxSubmit() and ajaxForm() methods. */ -$.fn.formToArray = function(semantic) { - var a = []; - if (this.length === 0) { - return a; - } - - var form = this[0]; - var els = semantic ? form.getElementsByTagName('*') : form.elements; - if (!els) { - return a; - } - - var i,j,n,v,el,max,jmax; - for(i=0, max=els.length; i < max; i++) { - el = els[i]; - n = el.name; - if (!n) { - continue; - } - - if (semantic && form.clk && el.type == "image") { - // handle image inputs on the fly when semantic == true - if(!el.disabled && form.clk == el) { - a.push({name: n, value: $(el).val()}); - a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); - } - continue; - } - - v = $.fieldValue(el, true); - if (v && v.constructor == Array) { - for(j=0, jmax=v.length; j < jmax; j++) { - a.push({name: n, value: v[j]}); - } - } - else if (v !== null && typeof v != 'undefined') { - a.push({name: n, value: v}); - } - } - - if (!semantic && form.clk) { - // input type=='image' are not found in elements array! handle it here - var $input = $(form.clk), input = $input[0]; - n = input.name; - if (n && !input.disabled && input.type == 'image') { - a.push({name: n, value: $input.val()}); - a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); - } - } - return a; +$.fn.formToArray = function(semantic, elements) { + var a = []; + if (this.length === 0) { + return a; + } + + var form = this[0]; + var els = semantic ? form.getElementsByTagName('*') : form.elements; + if (!els) { + return a; + } + + var i,j,n,v,el,max,jmax; + for(i=0, max=els.length; i < max; i++) { + el = els[i]; + n = el.name; + if (!n) { + continue; + } + + if (semantic && form.clk && el.type == "image") { + // handle image inputs on the fly when semantic == true + if(!el.disabled && form.clk == el) { + a.push({name: n, value: $(el).val(), type: el.type }); + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); + } + continue; + } + + v = $.fieldValue(el, true); + if (v && v.constructor == Array) { + if (elements) + elements.push(el); + for(j=0, jmax=v.length; j < jmax; j++) { + a.push({name: n, value: v[j]}); + } + } + else if (feature.fileapi && el.type == 'file' && !el.disabled) { + if (elements) + elements.push(el); + var files = el.files; + if (files.length) { + for (j=0; j < files.length; j++) { + a.push({name: n, value: files[j], type: el.type}); + } + } + else { + // #180 + a.push({ name: n, value: '', type: el.type }); + } + } + else if (v !== null && typeof v != 'undefined') { + if (elements) + elements.push(el); + a.push({name: n, value: v, type: el.type, required: el.required}); + } + } + + if (!semantic && form.clk) { + // input type=='image' are not found in elements array! handle it here + var $input = $(form.clk), input = $input[0]; + n = input.name; + if (n && !input.disabled && input.type == 'image') { + a.push({name: n, value: $input.val()}); + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); + } + } + return a; }; /** @@ -694,8 +855,8 @@ $.fn.formToArray = function(semantic) { * in the format: name1=value1&name2=value2 */ $.fn.formSerialize = function(semantic) { - //hand off to jQuery.param for proper encoding - return $.param(this.formToArray(semantic)); + //hand off to jQuery.param for proper encoding + return $.param(this.formToArray(semantic)); }; /** @@ -703,36 +864,36 @@ $.fn.formSerialize = function(semantic) { * This method will return a string in the format: name1=value1&name2=value2 */ $.fn.fieldSerialize = function(successful) { - var a = []; - this.each(function() { - var n = this.name; - if (!n) { - return; - } - var v = $.fieldValue(this, successful); - if (v && v.constructor == Array) { - for (var i=0,max=v.length; i < max; i++) { - a.push({name: n, value: v[i]}); - } - } - else if (v !== null && typeof v != 'undefined') { - a.push({name: this.name, value: v}); - } - }); - //hand off to jQuery.param for proper encoding - return $.param(a); + var a = []; + this.each(function() { + var n = this.name; + if (!n) { + return; + } + var v = $.fieldValue(this, successful); + if (v && v.constructor == Array) { + for (var i=0,max=v.length; i < max; i++) { + a.push({name: n, value: v[i]}); + } + } + else if (v !== null && typeof v != 'undefined') { + a.push({name: this.name, value: v}); + } + }); + //hand off to jQuery.param for proper encoding + return $.param(a); }; /** * Returns the value(s) of the element in the matched set. For example, consider the following form: * * <form><fieldset> - * <input name="A" type="text" /> - * <input name="A" type="text" /> - * <input name="B" type="checkbox" value="B1" /> - * <input name="B" type="checkbox" value="B2"/> - * <input name="C" type="radio" value="C1" /> - * <input name="C" type="radio" value="C2" /> + * <input name="A" type="text" /> + * <input name="A" type="text" /> + * <input name="B" type="checkbox" value="B1" /> + * <input name="B" type="checkbox" value="B2"/> + * <input name="C" type="radio" value="C1" /> + * <input name="C" type="radio" value="C2" /> * </fieldset></form> * * var v = $(':text').fieldValue(); @@ -759,60 +920,63 @@ $.fn.fieldSerialize = function(successful) { * for each element is returned. * * Note: This method *always* returns an array. If no valid value can be determined the - * array will be empty, otherwise it will contain one or more values. + * array will be empty, otherwise it will contain one or more values. */ $.fn.fieldValue = function(successful) { - for (var val=[], i=0, max=this.length; i < max; i++) { - var el = this[i]; - var v = $.fieldValue(el, successful); - if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { - continue; - } - v.constructor == Array ? $.merge(val, v) : val.push(v); - } - return val; + for (var val=[], i=0, max=this.length; i < max; i++) { + var el = this[i]; + var v = $.fieldValue(el, successful); + if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { + continue; + } + if (v.constructor == Array) + $.merge(val, v); + else + val.push(v); + } + return val; }; /** * Returns the value of the field element. */ $.fieldValue = function(el, successful) { - var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); - if (successful === undefined) { - successful = true; - } - - if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || - (t == 'checkbox' || t == 'radio') && !el.checked || - (t == 'submit' || t == 'image') && el.form && el.form.clk != el || - tag == 'select' && el.selectedIndex == -1)) { - return null; - } - - if (tag == 'select') { - var index = el.selectedIndex; - if (index < 0) { - return null; - } - var a = [], ops = el.options; - var one = (t == 'select-one'); - var max = (one ? index+1 : ops.length); - for(var i=(one ? index : 0); i < max; i++) { - var op = ops[i]; - if (op.selected) { - var v = op.value; - if (!v) { // extra pain for IE... - v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; - } - if (one) { - return v; - } - a.push(v); - } - } - return a; - } - return $(el).val(); + var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); + if (successful === undefined) { + successful = true; + } + + if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || + (t == 'checkbox' || t == 'radio') && !el.checked || + (t == 'submit' || t == 'image') && el.form && el.form.clk != el || + tag == 'select' && el.selectedIndex == -1)) { + return null; + } + + if (tag == 'select') { + var index = el.selectedIndex; + if (index < 0) { + return null; + } + var a = [], ops = el.options; + var one = (t == 'select-one'); + var max = (one ? index+1 : ops.length); + for(var i=(one ? index : 0); i < max; i++) { + var op = ops[i]; + if (op.selected) { + var v = op.value; + if (!v) { // extra pain for IE... + v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; + } + if (one) { + return v; + } + a.push(v); + } + } + return a; + } + return $(el).val(); }; /** @@ -823,54 +987,63 @@ $.fieldValue = function(el, successful) { * - inputs of type submit, button, reset, and hidden will *not* be effected * - button elements will *not* be effected */ -$.fn.clearForm = function() { - return this.each(function() { - $('input,select,textarea', this).clearFields(); - }); +$.fn.clearForm = function(includeHidden) { + return this.each(function() { + $('input,select,textarea', this).clearFields(includeHidden); + }); }; /** * Clears the selected form elements. */ -$.fn.clearFields = $.fn.clearInputs = function() { - var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list - return this.each(function() { - var t = this.type, tag = this.tagName.toLowerCase(); - if (re.test(t) || tag == 'textarea') { - this.value = ''; - } - else if (t == 'checkbox' || t == 'radio') { - this.checked = false; - } - else if (tag == 'select') { - this.selectedIndex = -1; - } - }); +$.fn.clearFields = $.fn.clearInputs = function(includeHidden) { + var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list + return this.each(function() { + var t = this.type, tag = this.tagName.toLowerCase(); + if (re.test(t) || tag == 'textarea') { + this.value = ''; + } + else if (t == 'checkbox' || t == 'radio') { + this.checked = false; + } + else if (tag == 'select') { + this.selectedIndex = -1; + } + else if (includeHidden) { + // includeHidden can be the value true, or it can be a selector string + // indicating a special test; for example: + // $('#myForm').clearForm('.special:hidden') + // the above would clean hidden inputs that have the class of 'special' + if ( (includeHidden === true && /hidden/.test(t)) || + (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) + this.value = ''; + } + }); }; /** * Resets the form data. Causes all form elements to be reset to their original value. */ $.fn.resetForm = function() { - return this.each(function() { - // guard against an input with the name of 'reset' - // note that IE reports the reset function as an 'object' - if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) { - this.reset(); - } - }); + return this.each(function() { + // guard against an input with the name of 'reset' + // note that IE reports the reset function as an 'object' + if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) { + this.reset(); + } + }); }; /** * Enables or disables any matching elements. */ $.fn.enable = function(b) { - if (b === undefined) { - b = true; - } - return this.each(function() { - this.disabled = !b; - }); + if (b === undefined) { + b = true; + } + return this.each(function() { + this.disabled = !b; + }); }; /** @@ -878,34 +1051,39 @@ $.fn.enable = function(b) { * selects/deselects and matching option elements. */ $.fn.selected = function(select) { - if (select === undefined) { - select = true; - } - return this.each(function() { - var t = this.type; - if (t == 'checkbox' || t == 'radio') { - this.checked = select; - } - else if (this.tagName.toLowerCase() == 'option') { - var $sel = $(this).parent('select'); - if (select && $sel[0] && $sel[0].type == 'select-one') { - // deselect all other options - $sel.find('option').selected(false); - } - this.selected = select; - } - }); + if (select === undefined) { + select = true; + } + return this.each(function() { + var t = this.type; + if (t == 'checkbox' || t == 'radio') { + this.checked = select; + } + else if (this.tagName.toLowerCase() == 'option') { + var $sel = $(this).parent('select'); + if (select && $sel[0] && $sel[0].type == 'select-one') { + // deselect all other options + $sel.find('option').selected(false); + } + this.selected = select; + } + }); }; +// expose debug var +$.fn.ajaxSubmit.debug = false; + // helper fn for console logging function log() { - var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); - if (window.console && window.console.log) { - window.console.log(msg); - } - else if (window.opera && window.opera.postError) { - window.opera.postError(msg); - } -}; + if (!$.fn.ajaxSubmit.debug) + return; + var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); + if (window.console && window.console.log) { + window.console.log(msg); + } + else if (window.opera && window.opera.postError) { + window.opera.postError(msg); + } +} })(jQuery); diff --git a/resources/jquery/jquery.getAttrs.js b/resources/jquery/jquery.getAttrs.js index c05012d9..25b806b6 100644 --- a/resources/jquery/jquery.getAttrs.js +++ b/resources/jquery/jquery.getAttrs.js @@ -3,8 +3,8 @@ * * @author Timo Tijhof, 2011 */ -jQuery.fn.getAttrs = function( all ) { - var map = this[0].attributes, +jQuery.fn.getAttrs = function ( all ) { + var map = this[0].attributes, attrs = {}, len = map.length, i, v; diff --git a/resources/jquery/jquery.highlightText.js b/resources/jquery/jquery.highlightText.js index 44ac56e1..f720e07f 100644 --- a/resources/jquery/jquery.highlightText.js +++ b/resources/jquery/jquery.highlightText.js @@ -1,64 +1,69 @@ /** - * Plugin that highlights matched word partials in a given element - * TODO: add a function for restoring the previous text - * TODO: accept mappings for converting shortcuts like WP: to Wikipedia: + * Plugin that highlights matched word partials in a given element. + * TODO: Add a function for restoring the previous text. + * TODO: Accept mappings for converting shortcuts like WP: to Wikipedia:. */ -( function( $ ) { +( function ( $ ) { -$.highlightText = { - - // Split our pattern string at spaces and run our highlight function on the results - splitAndHighlight: function( node, pat ) { - var patArray = pat.split(" "); - for ( var i = 0; i < patArray.length; i++ ) { - if ( patArray[i].length == 0 ) continue; - $.highlightText.innerHighlight( node, patArray[i] ); - } - return node; - }, - // scans a node looking for the pattern and wraps a span around each match - innerHighlight: function( node, pat ) { - // if this is a text node - if ( node.nodeType == 3 ) { - // 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 occurence of our pattern and store the starting position - var match = node.data.match( new RegExp( "(^|\\s)" + $.escapeRE( pat ), "i" ) ); - if ( match ) { - var pos = match.index + match[1].length; // include length of any matched spaces - // create the span wrapper for the matched text - var spannode = document.createElement( 'span' ); - spannode.className = 'highlight'; - // shave off the characters preceding the matched text - var middlebit = node.splitText( pos ); - // shave off any unmatched text off the end - middlebit.splitText( pat.length ); - // clone for appending to our span - var middleclone = middlebit.cloneNode( true ); - // append the matched text node to the span - spannode.appendChild( middleclone ); - // replace the matched node, with our span-wrapped clone of the matched node - middlebit.parentNode.replaceChild( spannode, middlebit ); + $.highlightText = { + + // Split our pattern string at spaces and run our highlight function on the results + splitAndHighlight: function ( node, pat ) { + var i, + patArray = pat.split( ' ' ); + for ( i = 0; i < patArray.length; i++ ) { + if ( patArray[i].length === 0 ) { + continue; + } + $.highlightText.innerHighlight( node, patArray[i] ); } - // if this is an element with childnodes, and not a script, style or an element we created - } else if ( node.nodeType == 1 && node.childNodes && !/(script|style)/i.test( node.tagName ) - && !( node.tagName.toLowerCase() == 'span' && node.className.match( /\bhighlight/ ) ) ) { - for ( var i = 0; i < node.childNodes.length; ++i ) { - // call the highlight function for each child node - $.highlightText.innerHighlight( node.childNodes[i], pat ); + return node; + }, + + // 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 ) { + // 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" ) ); + if ( match ) { + 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'; + // shave off the characters preceding the matched text + middlebit = node.splitText( pos ); + // shave off any unmatched text off the end + middlebit.splitText( pat.length ); + // clone for appending to our span + middleclone = middlebit.cloneNode( true ); + // append the matched text node to the span + spannode.appendChild( middleclone ); + // 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 && node.childNodes && !/(script|style)/i.test( node.tagName ) + && !( node.tagName.toLowerCase() === 'span' && node.className.match( /\bhighlight/ ) ) ) { + for ( i = 0; i < node.childNodes.length; ++i ) { + // call the highlight function for each child node + $.highlightText.innerHighlight( node.childNodes[i], pat ); + } } } - } -}; + }; -$.fn.highlightText = function( matchString ) { - return $( this ).each( function() { - var $this = $( this ); - $this.data( 'highlightText', { originalText: $this.text() } ); - $.highlightText.splitAndHighlight( this, matchString ); - } ); -}; + $.fn.highlightText = function ( matchString ) { + return this.each( function () { + var $el = $( this ); + $el.data( 'highlightText', { originalText: $el.text() } ); + $.highlightText.splitAndHighlight( this, matchString ); + } ); + }; -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.jStorage.js b/resources/jquery/jquery.jStorage.js new file mode 100644 index 00000000..95959cf7 --- /dev/null +++ b/resources/jquery/jquery.jStorage.js @@ -0,0 +1,532 @@ +/* + * ----------------------------- JSTORAGE ------------------------------------- + * Simple local storage wrapper to save data on the browser side, supporting + * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ + * + * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com + * Project homepage: www.jstorage.info + * + * Taken from Github with slight modifications by Hoo man + * https://raw.github.com/andris9/jStorage/master/jstorage.js + * + * Licensed under MIT-style license: + * + * 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 SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * $.jStorage + * + * USAGE: + * + * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then + * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed. + * (jQuery-JSON needs to be loaded BEFORE jStorage!) + * + * Methods: + * + * -set(key, value[, options]) + * $.jStorage.set(key, value) -> saves a value + * + * -get(key[, default]) + * value = $.jStorage.get(key [, default]) -> + * retrieves value if key exists, or default if it doesn't + * + * -deleteKey(key) + * $.jStorage.deleteKey(key) -> removes a key from the storage + * + * -flush() + * $.jStorage.flush() -> clears the cache + * + * -storageObj() + * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage + * + * -storageSize() + * $.jStorage.storageSize() -> returns the size of the storage in bytes + * + * -index() + * $.jStorage.index() -> returns the used keys as an array + * + * -storageAvailable() + * $.jStorage.storageAvailable() -> returns true if storage is available + * + * -reInit() + * $.jStorage.reInit() -> reloads the data from browser storage + * + * <value> can be any JSON-able value, including objects and arrays. + * + **/ + +(function($){ + if(!$ || !($.toJSON || Object.toJSON || window.JSON)){ + throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!"); + } + + var + /* This is the object, that holds the cached values */ + _storage = {}, + + /* Actual browser storage (localStorage or globalStorage['domain']) */ + _storage_service = {jStorage:"{}"}, + + /* DOM element for older IE versions, holds userData behavior */ + _storage_elm = null, + + /* How much space does the storage take */ + _storage_size = 0, + + /* function to encode objects to JSON strings */ + json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)), + + /* function to decode objects from JSON strings */ + json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){ + return String(str).evalJSON(); + }, + + /* which backend is currently used */ + _backend = false, + + /* Next check for TTL */ + _ttl_timeout, + + /** + * XML encoding and decoding as XML nodes can't be JSON'ized + * XML nodes are encoded and decoded if the node is the value to be saved + * but not if it's as a property of another object + * Eg. - + * $.jStorage.set("key", xmlNode); // IS OK + * $.jStorage.set("key", {xml: xmlNode}); // NOT OK + */ + _XMLService = { + + /** + * Validates a XML node to be XML + * based on jQuery.isXML function + */ + isXML: function(elm){ + var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; + }, + + /** + * Encodes a XML node to string + * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ + */ + encode: function(xmlNode) { + if(!this.isXML(xmlNode)){ + return false; + } + try{ // Mozilla, Webkit, Opera + return new XMLSerializer().serializeToString(xmlNode); + }catch(E1) { + try { // IE + return xmlNode.xml; + }catch(E2){} + } + return false; + }, + + /** + * Decodes a XML node from string + * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ + */ + decode: function(xmlString){ + var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) || + (window.ActiveXObject && function(_xmlString) { + var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); + xml_doc.async = 'false'; + xml_doc.loadXML(_xmlString); + return xml_doc; + }), + resultXML; + if(!dom_parser){ + return false; + } + resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml'); + return this.isXML(resultXML)?resultXML:false; + } + }; + + ////////////////////////// PRIVATE METHODS //////////////////////// + + /** + * Initialization function. Detects if the browser supports DOM Storage + * or userData behavior and behaves accordingly. + * @returns undefined + */ + function _init(){ + /* Check if browser supports localStorage */ + var localStorageReallyWorks = false; + if("localStorage" in window){ + try { + window.localStorage.setItem('_tmptest', 'tmpval'); + localStorageReallyWorks = true; + window.localStorage.removeItem('_tmptest'); + } catch(BogusQuotaExceededErrorOnIos5) { + // Thanks be to iOS5 Private Browsing mode which throws + // QUOTA_EXCEEDED_ERRROR DOM Exception 22. + } + } + if(localStorageReallyWorks){ + try { + if(window.localStorage) { + _storage_service = window.localStorage; + _backend = "localStorage"; + } + } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */} + } + /* Check if browser supports globalStorage */ + else if("globalStorage" in window){ + try { + if(window.globalStorage) { + _storage_service = window.globalStorage[window.location.hostname]; + _backend = "globalStorage"; + } + } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */} + } + /* Check if browser supports userData behavior */ + else { + _storage_elm = document.createElement('link'); + if(_storage_elm.addBehavior){ + + /* Use a DOM element to act as userData storage */ + _storage_elm.style.behavior = 'url(#default#userData)'; + + /* userData element needs to be inserted into the DOM! */ + document.getElementsByTagName('head')[0].appendChild(_storage_elm); + + _storage_elm.load("jStorage"); + var data = "{}"; + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + _storage_service.jStorage = data; + _backend = "userDataBehavior"; + }else{ + _storage_elm = null; + return; + } + } + + _load_storage(); + + // remove dead keys + _handleTTL(); + } + + /** + * Loads the data from the storage based on the supported mechanism + * @returns undefined + */ + function _load_storage(){ + /* if jStorage string is retrieved, then decode it */ + if(_storage_service.jStorage){ + try{ + _storage = json_decode(String(_storage_service.jStorage)); + }catch(E6){_storage_service.jStorage = "{}";} + }else{ + _storage_service.jStorage = "{}"; + } + _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; + } + + /** + * This functions provides the "save" mechanism to store the jStorage object + * @returns undefined + */ + function _save(){ + try{ + _storage_service.jStorage = json_encode(_storage); + // If userData is used as the storage engine, additional + if(_storage_elm) { + _storage_elm.setAttribute("jStorage",_storage_service.jStorage); + _storage_elm.save("jStorage"); + } + _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; + }catch(E7){/* probably cache is full, nothing is saved this way*/} + } + + /** + * Function checks if a key is set and is string or numberic + */ + function _checkKey(key){ + if(!key || (typeof key !== "string" && typeof key !== "number")){ + throw new TypeError('Key name must be string or numeric'); + } + if(key === "__jstorage_meta"){ + throw new TypeError('Reserved key name'); + } + return true; + } + + /** + * Removes expired keys + */ + function _handleTTL(){ + var curtime, i, TTL, nextExpire = Infinity, changed = false; + + clearTimeout(_ttl_timeout); + + if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL !== "object"){ + // nothing to do here + return; + } + + curtime = +new Date(); + TTL = _storage.__jstorage_meta.TTL; + for(i in TTL){ + if(TTL.hasOwnProperty(i)){ + if(TTL[i] <= curtime){ + delete TTL[i]; + delete _storage[i]; + changed = true; + }else if(TTL[i] < nextExpire){ + nextExpire = TTL[i]; + } + } + } + + // set next check + if(nextExpire != Infinity){ + _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime); + } + + // save changes + if(changed){ + _save(); + } + } + + ////////////////////////// PUBLIC INTERFACE ///////////////////////// + + $.jStorage = { + /* Version number */ + version: "0.1.7.0", + + /** + * Sets a key's value. + * + * @param {String} key - Key to set. If this value is not set or not + * a string an exception is raised. + * @param {Mixed} value - Value to set. This can be any value that is JSON + * compatible (Numbers, Strings, Objects etc.). + * @param {Object} [options] - possible options to use + * @param {Number} [options.TTL] - optional TTL value + * @returns the used value + */ + set: function(key, value, options){ + _checkKey(key); + + options = options || {}; + + if(_XMLService.isXML(value)){ + value = {_is_xml:true,xml:_XMLService.encode(value)}; + }else if(typeof value === "function"){ + value = null; // functions can't be saved! + }else if(value && typeof value === "object"){ + // clone the object before saving to _storage tree + value = json_decode(json_encode(value)); + } + _storage[key] = value; + + if(!isNaN(options.TTL)){ + this.setTTL(key, options.TTL); + // also handles saving + }else{ + _save(); + } + return value; + }, + + /** + * Looks up a key in cache + * + * @param {String} key - Key to look up. + * @param {mixed} def - Default value to return, if key didn't exist. + * @returns the key value, default value or <null> + */ + get: function(key, def){ + _checkKey(key); + if(key in _storage){ + if(_storage[key] && typeof _storage[key] === "object" && + _storage[key]._is_xml && + _storage[key]._is_xml){ + return _XMLService.decode(_storage[key].xml); + }else{ + return _storage[key]; + } + } + return typeof(def) === 'undefined' ? null : def; + }, + + /** + * Deletes a key from cache. + * + * @param {String} key - Key to delete. + * @returns true if key existed or false if it didn't + */ + deleteKey: function(key){ + _checkKey(key); + if(key in _storage){ + delete _storage[key]; + // remove from TTL list + if(_storage.__jstorage_meta && + typeof _storage.__jstorage_meta.TTL === "object" && + key in _storage.__jstorage_meta.TTL){ + delete _storage.__jstorage_meta.TTL[key]; + } + _save(); + return true; + } + return false; + }, + + /** + * Sets a TTL for a key, or remove it if ttl value is 0 or below + * + * @param {String} key - key to set the TTL for + * @param {Number} ttl - TTL timeout in milliseconds + * @returns true if key existed or false if it didn't + */ + setTTL: function(key, ttl){ + var curtime = +new Date(); + _checkKey(key); + ttl = Number(ttl) || 0; + if(key in _storage){ + + if(!_storage.__jstorage_meta){ + _storage.__jstorage_meta = {}; + } + if(!_storage.__jstorage_meta.TTL){ + _storage.__jstorage_meta.TTL = {}; + } + + // Set TTL value for the key + if(ttl>0){ + _storage.__jstorage_meta.TTL[key] = curtime + ttl; + }else{ + delete _storage.__jstorage_meta.TTL[key]; + } + + _save(); + + _handleTTL(); + return true; + } + return false; + }, + + /** + * Deletes everything in cache. + * + * @return true + */ + flush: function(){ + _storage = {}; + _save(); + return true; + }, + + /** + * Returns a read-only copy of _storage + * + * @returns Object + */ + storageObj: function(){ + function F() {} + F.prototype = _storage; + return new F(); + }, + + /** + * Returns an index of all used keys as an array + * ['key1', 'key2',..'keyN'] + * + * @returns Array + */ + index: function(){ + var index = [], i; + for(i in _storage){ + if(_storage.hasOwnProperty(i) && i !== "__jstorage_meta"){ + index.push(i); + } + } + return index; + }, + + /** + * How much space in bytes does the storage take? + * + * @returns Number + */ + storageSize: function(){ + return _storage_size; + }, + + /** + * Which backend is currently in use? + * + * @returns String + */ + currentBackend: function(){ + return _backend; + }, + + /** + * Test if storage is available + * + * @returns Boolean + */ + storageAvailable: function(){ + return !!_backend; + }, + + /** + * Reloads the data from browser storage + * + * @returns undefined + */ + reInit: function(){ + var new_storage_elm, data; + if(_storage_elm && _storage_elm.addBehavior){ + new_storage_elm = document.createElement('link'); + + _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm); + _storage_elm = new_storage_elm; + + /* Use a DOM element to act as userData storage */ + _storage_elm.style.behavior = 'url(#default#userData)'; + + /* userData element needs to be inserted into the DOM! */ + document.getElementsByTagName('head')[0].appendChild(_storage_elm); + + _storage_elm.load("jStorage"); + data = "{}"; + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + _storage_service.jStorage = data; + _backend = "userDataBehavior"; + } + + _load_storage(); + } + }; + + // Initialize jStorage + _init(); + +})(window.$ || window.jQuery); diff --git a/resources/jquery/jquery.js b/resources/jquery/jquery.js index 8ccd0ea7..d4f3bb38 100644 --- a/resources/jquery/jquery.js +++ b/resources/jquery/jquery.js @@ -1,31 +1,28 @@ /*! - * jQuery JavaScript Library v1.7.1 + * jQuery JavaScript Library v1.8.2 * http://jquery.com/ * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * * Includes Sizzle.js * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. * - * Date: Mon Nov 21 21:11:03 2011 -0500 + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time) */ (function( window, undefined ) { +var + // A central reference to the root jQuery(document) + rootjQuery, -// Use the correct document accordingly with window argument (sandbox) -var document = window.document, - navigator = window.navigator, - location = window.location; -var jQuery = (function() { + // The deferred used on DOM ready + readyList, -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + navigator = window.navigator, // Map over jQuery in case of overwrite _jQuery = window.jQuery, @@ -33,63 +30,64 @@ var jQuery = function( selector, context ) { // Map over the $ in case of overwrite _$ = window.$, - // A central reference to the root jQuery(document) - rootjQuery, + // Save a reference to some core methods + core_push = Array.prototype.push, + core_slice = Array.prototype.slice, + core_indexOf = Array.prototype.indexOf, + core_toString = Object.prototype.toString, + core_hasOwn = Object.prototype.hasOwnProperty, + core_trim = String.prototype.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, - // A simple way to check for HTML strings or ID strings - // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + // Used for matching numbers + core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source, + + // Used for detecting and trimming whitespace + core_rnotwhite = /\S/, + core_rspace = /\s+/, - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, + // A simple way to check for HTML strings + // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) + rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g, // Matches dashed string for camelizing - rdashAlpha = /-([a-z]|[0-9])/ig, rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); }, - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // The deferred used on DOM ready - readyList, - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, + // The ready event handler and self cleanup method + DOMContentLoaded = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + } else if ( document.readyState === "complete" ) { + // we're here because readyState === "complete" in oldIE + // which is good enough for us to call the dom ready! + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }, // [[Class]] -> type pairs class2type = {}; @@ -99,7 +97,7 @@ jQuery.fn = jQuery.prototype = { init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; - // Handle $(""), $(null), or $(undefined) + // Handle $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } @@ -111,55 +109,33 @@ jQuery.fn = jQuery.prototype = { return this; } - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = selector; - this.length = 1; - return this; - } - // Handle HTML strings if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { - match = quickExpr.exec( selector ); + match = rquickExpr.exec( selector ); } - // Verify a match, and that no context was specified for #id + // Match html or make sure no context is specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; - doc = ( context ? context.ownerDocument || context : document ); + doc = ( context && context.nodeType ? context.ownerDocument || context : document ); - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + // scripts is true for back-compat + selector = jQuery.parseHTML( match[1], doc, true ); + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + this.attr.call( selector, context, true ); } return jQuery.merge( this, selector ); - // HANDLE: $("#id") + // HANDLE: $(#id) } else { elem = document.getElementById( match[2] ); @@ -210,7 +186,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.7.1", + jquery: "1.8.2", // The default length of a jQuery object is 0 length: 0, @@ -221,7 +197,7 @@ jQuery.fn = jQuery.prototype = { }, toArray: function() { - return slice.call( this, 0 ); + return core_slice.call( this ); }, // Get the Nth element in the matched element set OR @@ -239,15 +215,9 @@ jQuery.fn = jQuery.prototype = { // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - } else { - jQuery.merge( ret, elems ); - } + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; @@ -272,11 +242,8 @@ jQuery.fn = jQuery.prototype = { }, ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - // Add the callback - readyList.add( fn ); + jQuery.ready.promise().done( fn ); return this; }, @@ -297,8 +264,8 @@ jQuery.fn = jQuery.prototype = { }, slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); + return this.pushStack( core_slice.apply( this, arguments ), + "slice", core_slice.call(arguments).join(",") ); }, map: function( callback ) { @@ -313,7 +280,7 @@ jQuery.fn = jQuery.prototype = { // For internal use only. // Behaves like an Array's method, not like a jQuery method. - push: push, + push: core_push, sort: [].sort, splice: [].splice }; @@ -416,73 +383,31 @@ jQuery.extend({ // Handle when the DOM is ready ready: function( wait ) { - // Either a released hold or an DOMready/load event and not yet ready - if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.fireWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).off( "ready" ); - } - } - }, - bindReady: function() { - if ( readyList ) { + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } - readyList = jQuery.Callbacks( "once memory" ); - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + // Remember that the DOM is ready + jQuery.isReady = true; - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", DOMContentLoaded ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } - try { - toplevel = window.frameElement == null; - } catch(e) {} + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); } }, @@ -497,9 +422,8 @@ jQuery.extend({ return jQuery.type(obj) === "array"; }, - // A crude way of determining if an object is a window isWindow: function( obj ) { - return obj && typeof obj === "object" && "setInterval" in obj; + return obj != null && obj == obj.window; }, isNumeric: function( obj ) { @@ -509,7 +433,7 @@ jQuery.extend({ type: function( obj ) { return obj == null ? String( obj ) : - class2type[ toString.call(obj) ] || "object"; + class2type[ core_toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { @@ -523,8 +447,8 @@ jQuery.extend({ try { // Not own constructor property must be Object if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { @@ -538,11 +462,12 @@ jQuery.extend({ var key; for ( key in obj ) {} - return key === undefined || hasOwn.call( obj, key ); + return key === undefined || core_hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { - for ( var name in obj ) { + var name; + for ( name in obj ) { return false; } return true; @@ -552,8 +477,32 @@ jQuery.extend({ throw new Error( msg ); }, + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // scripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, scripts ) { + var parsed; + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + scripts = context; + context = 0; + } + context = context || document; + + // Single tag + if ( (parsed = rsingleTag.exec( data )) ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] ); + return jQuery.merge( [], + (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes ); + }, + parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { + if ( !data || typeof data !== "string") { return null; } @@ -580,6 +529,9 @@ jQuery.extend({ // Cross-browser xml parsing parseXML: function( data ) { var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } try { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); @@ -604,7 +556,7 @@ jQuery.extend({ // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { - if ( data && rnotwhite.test( data ) ) { + if ( data && core_rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox @@ -621,25 +573,26 @@ jQuery.extend({ }, nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }, // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction( object ); + each: function( obj, callback, args ) { + var name, + i = 0, + length = obj.length, + isObj = length === undefined || jQuery.isFunction( obj ); if ( args ) { if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { + for ( name in obj ) { + if ( callback.apply( obj[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { + if ( callback.apply( obj[ i++ ], args ) === false ) { break; } } @@ -648,71 +601,72 @@ jQuery.extend({ // A special, fast, case for the most common use of each } else { if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + for ( name in obj ) { + if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { - if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) { break; } } } } - return object; + return obj; }, // Use native String.trim function wherever possible - trim: trim ? + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? function( text ) { return text == null ? "" : - trim.call( text ); + core_trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + ( text + "" ).replace( rtrim, "" ); }, // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; + makeArray: function( arr, results ) { + var type, + ret = results || []; - if ( array != null ) { + if ( arr != null ) { // The window, strings (and functions) also have 'length' // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type( array ); + type = jQuery.type( arr ); - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); + if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) { + core_push.call( ret, arr ); } else { - jQuery.merge( ret, array ); + jQuery.merge( ret, arr ); } } return ret; }, - inArray: function( elem, array, i ) { + inArray: function( elem, arr, i ) { var len; - if ( array ) { - if ( indexOf ) { - return indexOf.call( array, elem, i ); + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); } - len = array.length; + len = arr.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays - if ( i in array && array[ i ] === elem ) { + if ( i in arr && arr[ i ] === elem ) { return i; } } @@ -722,11 +676,12 @@ jQuery.extend({ }, merge: function( first, second ) { - var i = first.length, + var l = second.length, + i = first.length, j = 0; - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { first[ i++ ] = second[ j ]; } @@ -742,12 +697,15 @@ jQuery.extend({ }, grep: function( elems, callback, inv ) { - var ret = [], retVal; + var retVal, + ret = [], + i = 0, + length = elems.length; inv = !!inv; // Go through the array, only saving the items // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { + for ( ; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); @@ -759,7 +717,8 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { - var value, key, ret = [], + var value, key, + ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays @@ -796,8 +755,10 @@ jQuery.extend({ // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { + var tmp, args, proxy; + if ( typeof context === "string" ) { - var tmp = fn[ context ]; + tmp = fn[ context ]; context = fn; fn = tmp; } @@ -809,178 +770,162 @@ jQuery.extend({ } // Simulated bind - var args = slice.call( arguments, 2 ), - proxy = function() { - return fn.apply( context, args.concat( slice.call( arguments ) ) ); - }; + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context, args.concat( core_slice.call( arguments ) ) ); + }; // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + proxy.guid = fn.guid = fn.guid || jQuery.guid++; return proxy; }, - // Mutifunctional method to get and set values to a collection + // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function - access: function( elems, key, value, exec, fn, pass ) { - var length = elems.length; + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - jQuery.access( elems, k, key[k], exec, fn, value ); + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); } - return elems; - } + chainable = 1; - // Setting one attribute - if ( value !== undefined ) { + // Sets one value + } else if ( value !== undefined ) { // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } } - return elems; + chainable = 1; } - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; }, now: function() { return ( new Date() ).getTime(); - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); + } +}); - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { - return { browser: match[1] || "", version: match[2] || "0" }; - }, + readyList = jQuery.Deferred(); - sub: function() { - function jQuerySub( selector, context ) { - return new jQuerySub.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySub, this ); - jQuerySub.superclass = this; - jQuerySub.fn = jQuerySub.prototype = this(); - jQuerySub.fn.constructor = jQuerySub; - jQuerySub.sub = this.sub; - jQuerySub.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { - context = jQuerySub( context ); - } + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready, 1 ); - return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); - }; - jQuerySub.fn.init.prototype = jQuerySub.fn; - var rootjQuerySub = jQuerySub(document); - return jQuerySub; - }, + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - browser: {} -}); + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} + // If IE and not a frame + // continually check to see if the document is ready + var top = false; -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} -// All jQuery objects should point back to these -rootjQuery = jQuery(document); + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); + // and execute any waiting functions + jQuery.ready(); + } + })(); + } } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; } + return readyList.promise( obj ); +}; - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -return jQuery; - -})(); - - -// String to Object flags format cache -var flagsCache = {}; +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); -// Convert String-formatted flags into Object-formatted ones and store in cache -function createFlags( flags ) { - var object = flagsCache[ flags ] = {}, - i, length; - flags = flags.split( /\s+/ ); - for ( i = 0, length = flags.length; i < length; i++ ) { - object[ flags[i] ] = true; - } +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.split( core_rspace ), function( _, flag ) { + object[ flag ] = true; + }); return object; } /* * Create a callback list using the following parameters: * - * flags: an optional list of space-separated flags that will change how - * the callback list behaves + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * - * Possible flags: + * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * @@ -993,18 +938,18 @@ function createFlags( flags ) { * stopOnFalse: interrupt callings when a callback returns false * */ -jQuery.Callbacks = function( flags ) { +jQuery.Callbacks = function( options ) { - // Convert flags from String-formatted to Object-formatted + // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) + var // Last fire value (for non-forgettable lists) memory, + // Flag to know if list was already fired + fired, // Flag to know if list is currently firing firing, // First callback to fire (used internally by add and fireWith) @@ -1013,52 +958,34 @@ jQuery.Callbacks = function( flags ) { firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, - // Add one or several callbacks to the list - add = function( args ) { - var i, - length, - elem, - type, - actual; - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - // Inspect recursively - add( elem ); - } else if ( type === "function" ) { - // Add if not in unique mode and callback is not in - if ( !flags.unique || !self.has( elem ) ) { - list.push( elem ); - } - } - } - }, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], // Fire callbacks - fire = function( context, args ) { - args = args || []; - memory = !flags.memory || [ context, args ]; - firing = true; + fire = function( data ) { + memory = options.memory && data; + fired = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; + firing = true; for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { - memory = true; // Mark as halted + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add break; } } firing = false; if ( list ) { - if ( !flags.once ) { - if ( stack && stack.length ) { - memory = stack.shift(); - self.fireWith( memory[ 0 ], memory[ 1 ] ); + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); } - } else if ( memory === true ) { - self.disable(); - } else { + } else if ( memory ) { list = []; + } else { + self.disable(); } } }, @@ -1067,18 +994,28 @@ jQuery.Callbacks = function( flags ) { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { - var length = list.length; - add( arguments ); + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) { + list.push( arg ); + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if ( memory && memory !== true ) { - firingStart = length; - fire( memory[ 0 ], memory[ 1 ] ); + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); } } return this; @@ -1086,46 +1023,27 @@ jQuery.Callbacks = function( flags ) { // Remove a callback from the list remove: function() { if ( list ) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for ( ; argIndex < argLength ; argIndex++ ) { - for ( var i = 0; i < list.length; i++ ) { - if ( args[ argIndex ] === list[ i ] ) { - // Handle firingIndex and firingLength - if ( firing ) { - if ( i <= firingLength ) { - firingLength--; - if ( i <= firingIndex ) { - firingIndex--; - } - } + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; } - // Remove the element - list.splice( i--, 1 ); - // If we have some unicity property then - // we only need to do this once - if ( flags.unique ) { - break; + if ( index <= firingIndex ) { + firingIndex--; } } } - } + }); } return this; }, // Control if a given callback is in the list has: function( fn ) { - if ( list ) { - var i = 0, - length = list.length; - for ( ; i < length; i++ ) { - if ( fn === list[ i ] ) { - return true; - } - } - } - return false; + return jQuery.inArray( fn, list ) > -1; }, // Remove all callbacks from the list empty: function() { @@ -1144,7 +1062,7 @@ jQuery.Callbacks = function( flags ) { // Lock the list in its current state lock: function() { stack = undefined; - if ( !memory || memory === true ) { + if ( !memory ) { self.disable(); } return this; @@ -1155,13 +1073,13 @@ jQuery.Callbacks = function( flags ) { }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { - if ( stack ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { if ( firing ) { - if ( !flags.once ) { - stack.push( [ context, args ] ); - } - } else if ( !( flags.once && memory ) ) { - fire( context, args ); + stack.push( args ); + } else { + fire( args ); } } return this; @@ -1173,104 +1091,91 @@ jQuery.Callbacks = function( flags ) { }, // To know if the callbacks have already been called at least once fired: function() { - return !!memory; + return !!fired; } }; return self; }; - - - - -var // Static reference to slice - sliceDeferred = [].slice; - jQuery.extend({ Deferred: function( func ) { - var doneList = jQuery.Callbacks( "once memory" ), - failList = jQuery.Callbacks( "once memory" ), - progressList = jQuery.Callbacks( "memory" ), + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList - }, promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - state: function() { return state; }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); - return this; - }, always: function() { - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + deferred.done( arguments ).fail( arguments ); return this; }, - pipe: function( fnDone, fnFail, fnProgress ) { + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; return jQuery.Deferred(function( newDefer ) { - jQuery.each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ], - progress: [ fnProgress, "notify" ] - }, function( handler, data ) { - var fn = data[ 0 ], - action = data[ 1 ], - returned; - if ( jQuery.isFunction( fn ) ) { - deferred[ handler ](function() { - returned = fn.apply( this, arguments ); + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ]( jQuery.isFunction( fn ) ? + function() { + var returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } - }); - } else { - deferred[ handler ]( newDefer[ action ] ); - } + } : + newDefer[ action ] + ); }); + fns = null; }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { - if ( obj == null ) { - obj = promise; - } else { - for ( var key in promise ) { - obj[ key ] = promise[ key ]; - } - } - return obj; + return obj != null ? jQuery.extend( obj, promise ) : promise; } }, - deferred = promise.promise({}), - key; + deferred = {}; - for ( key in lists ) { - deferred[ key ] = lists[ key ].fire; - deferred[ key + "With" ] = lists[ key ].fireWith; - } + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] = list.fire + deferred[ tuple[0] ] = list.fire; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); - // Handle state - deferred.done( function() { - state = "resolved"; - }, failList.disable, progressList.lock ).fail( function() { - state = "rejected"; - }, doneList.disable, progressList.lock ); + // Make the deferred a promise + promise.promise( deferred ); // Call given func if any if ( func ) { @@ -1282,52 +1187,57 @@ jQuery.extend({ }, // Deferred helper - when: function( firstParam ) { - var args = sliceDeferred.call( arguments, 0 ), - i = 0, - length = args.length, - pValues = new Array( length ), - count = length, - pCount = length, - deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? - firstParam : - jQuery.Deferred(), - promise = deferred.promise(); - function resolveFunc( i ) { - return function( value ) { - args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( deferred, args ); - } - }; - } - function progressFunc( i ) { - return function( value ) { - pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - deferred.notifyWith( promise, pValues ); - }; - } + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); for ( ; i < length; i++ ) { - if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); } else { - --count; + --remaining; } } - if ( !count ) { - deferred.resolveWith( deferred, args ); - } - } else if ( deferred !== firstParam ) { - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } - return promise; - } -}); - - + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + return deferred.promise(); + } +}); jQuery.support = (function() { var support, @@ -1336,32 +1246,30 @@ jQuery.support = (function() { select, opt, input, - marginDiv, fragment, - tds, - events, eventName, i, isSupported, - div = document.createElement( "div" ), - documentElement = document.documentElement; + clickFn, + div = document.createElement("div"); // Preliminary tests - div.setAttribute("className", "t"); - div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + div.setAttribute( "className", "t" ); + div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>"; - all = div.getElementsByTagName( "*" ); - a = div.getElementsByTagName( "a" )[ 0 ]; + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + a.style.cssText = "top:1px;float:left;opacity:.5"; // Can't get basic test support - if ( !all || !all.length || !a ) { + if ( !all || !all.length ) { return {}; } // First batch of supports tests - select = document.createElement( "select" ); + select = document.createElement("select"); opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName( "input" )[ 0 ]; + input = div.getElementsByTagName("input")[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used @@ -1386,7 +1294,7 @@ jQuery.support = (function() { // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55/.test( a.style.opacity ), + opacity: /^0.5/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) @@ -1411,6 +1319,9 @@ jQuery.support = (function() { // Where outerHTML is undefined, this still works html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: ( document.compatMode === "CSS1Compat" ), + // Will be defined later submitBubbles: true, changeBubbles: true, @@ -1419,7 +1330,9 @@ jQuery.support = (function() { noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, - reliableMarginRight: true + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false }; // Make sure checked status is properly cloned @@ -1440,22 +1353,27 @@ jQuery.support = (function() { } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", function() { + div.attachEvent( "onclick", clickFn = function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); - div.cloneNode( true ).fireEvent( "onclick" ); + div.cloneNode( true ).fireEvent("onclick"); + div.detachEvent( "onclick", clickFn ); } // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; - input.setAttribute("type", "radio"); + input.setAttribute( "type", "radio" ); support.radioValue = input.value === "t"; - input.setAttribute("checked", "checked"); + input.setAttribute( "checked", "checked" ); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); @@ -1470,23 +1388,6 @@ jQuery.support = (function() { fragment.removeChild( input ); fragment.appendChild( div ); - div.innerHTML = ""; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( window.getComputedStyle ) { - marginDiv = document.createElement( "div" ); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.style.width = "2px"; - div.appendChild( marginDiv ); - support.reliableMarginRight = - ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; - } - // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems @@ -1494,10 +1395,10 @@ jQuery.support = (function() { // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { - for( i in { - submit: 1, - change: 1, - focusin: 1 + for ( i in { + submit: true, + change: true, + focusin: true }) { eventName = "on" + i; isSupported = ( eventName in div ); @@ -1509,15 +1410,10 @@ jQuery.support = (function() { } } - fragment.removeChild( div ); - - // Null elements to avoid leaks in IE - fragment = select = opt = marginDiv = div = input = null; - // Run tests that need a body at doc ready jQuery(function() { - var container, outer, inner, table, td, offsetSupport, - conMarginTop, ptlm, vb, style, html, + var container, div, tds, marginDiv, + divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;", body = document.getElementsByTagName("body")[0]; if ( !body ) { @@ -1525,16 +1421,8 @@ jQuery.support = (function() { return; } - conMarginTop = 1; - ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; - vb = "visibility:hidden;border:0;"; - style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; - html = "<div " + style + "><div></div></div>" + - "<table " + style + " cellpadding='0' cellspacing='0'>" + - "<tr><td></td></tr></table>"; - container = document.createElement("div"); - container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px"; body.insertBefore( container, body.firstChild ); // Construct the test element @@ -1548,8 +1436,9 @@ jQuery.support = (function() { // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) - div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; - tds = div.getElementsByTagName( "td" ); + div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; @@ -1559,71 +1448,72 @@ jQuery.support = (function() { // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - // Figure out if the W3C box model works as expected + // Check box-sizing and margin behavior div.innerHTML = ""; - div.style.width = div.style.paddingLeft = "1px"; - jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // NOTE: To any future maintainer, we've window.getComputedStyle + // because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = document.createElement("div"); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "<div style='width:4px;'></div>"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); - } - - div.style.cssText = ptlm + vb; - div.innerHTML = html; - - outer = div.firstChild; - inner = outer.firstChild; - td = outer.nextSibling.firstChild.firstChild; - - offsetSupport = { - doesNotAddBorder: ( inner.offsetTop !== 5 ), - doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) - }; - - inner.style.position = "fixed"; - inner.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); - inner.style.position = inner.style.top = ""; - - outer.style.overflow = "hidden"; - outer.style.position = "relative"; + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "<div></div>"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); - offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + container.style.zoom = 1; + } + // Null elements to avoid leaks in IE body.removeChild( container ); - div = container = null; - - jQuery.extend( support, offsetSupport ); + container = div = tds = marginDiv = null; }); + // Null elements to avoid leaks in IE + fragment.removeChild( div ); + all = a = select = opt = input = fragment = div = null; + return support; })(); - - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/, +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; jQuery.extend({ cache: {}, - // Please use with caution + deletedIds: [], + + // Remove at next major release (1.9/2.0) uuid: 0, // Unique for each copy of jQuery on the page @@ -1649,7 +1539,7 @@ jQuery.extend({ return; } - var privateCache, thisCache, ret, + var thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", @@ -1663,12 +1553,11 @@ jQuery.extend({ // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, - isEvents = name === "events"; + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all - if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { return; } @@ -1676,7 +1565,7 @@ jQuery.extend({ // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { - elem[ internalKey ] = id = ++jQuery.uuid; + elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; } else { id = internalKey; } @@ -1702,7 +1591,7 @@ jQuery.extend({ } } - privateCache = thisCache = cache[ id ]; + thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined @@ -1719,12 +1608,6 @@ jQuery.extend({ thisCache[ jQuery.camelCase( name ) ] = data; } - // Users should not attempt to inspect the internal events object using jQuery.data, - // it is undocumented and subject to change. But does anyone listen? No. - if ( isEvents && !thisCache[ name ] ) { - return privateCache.events; - } - // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { @@ -1752,16 +1635,11 @@ jQuery.extend({ var thisCache, i, l, - // Reference to internal data cache key - internalKey = jQuery.expando, - isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ internalKey ] : internalKey; + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; // If there is already no cache entry for this object, there is no // purpose in continuing @@ -1788,7 +1666,7 @@ jQuery.extend({ if ( name in thisCache ) { name = [ name ]; } else { - name = name.split( " " ); + name = name.split(" "); } } } @@ -1811,35 +1689,23 @@ jQuery.extend({ // Don't destroy the parent cache unless the internal data object // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { + if ( !isEmptyDataObject( cache[ id ] ) ) { return; } } - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - // Ensure that `cache` is not a window object #10080 - if ( jQuery.support.deleteExpando || !cache.setInterval ) { + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { delete cache[ id ]; + + // When all else fails, null } else { cache[ id ] = null; } - - // We destroyed the cache and need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - if ( isNode ) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( jQuery.support.deleteExpando ) { - delete elem[ internalKey ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( internalKey ); - } else { - elem[ internalKey ] = null; - } - } }, // For internal use only. @@ -1849,76 +1715,79 @@ jQuery.extend({ // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; } }); jQuery.fn.extend({ data: function( key, value ) { - var parts, attr, name, + var parts, part, attr, name, l, + elem = this[0], + i = 0, data = null; - if ( typeof key === "undefined" ) { + // Gets all values + if ( key === undefined ) { if ( this.length ) { - data = jQuery.data( this[0] ); + data = jQuery.data( elem ); - if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { - attr = this[0].attributes; - for ( var i = 0, l = attr.length; i < l; i++ ) { + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { name = attr[i].name; - if ( name.indexOf( "data-" ) === 0 ) { + if ( !name.indexOf( "data-" ) ) { name = jQuery.camelCase( name.substring(5) ); - dataAttr( this[0], name, data[ name ] ); + dataAttr( elem, name, data[ name ] ); } } - jQuery._data( this[0], "parsedAttrs", true ); + jQuery._data( elem, "parsedAttrs", true ); } } return data; + } - } else if ( typeof key === "object" ) { + // Sets multiple values + if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } - parts = key.split("."); + parts = key.split( ".", 2 ); parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; - if ( value === undefined ) { - data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + return jQuery.access( this, function( value ) { - // Try to fetch any internally stored data first - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - data = dataAttr( this[0], key, data ); - } + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } - } else { - return this.each(function() { - var self = jQuery( this ), - args = [ parts[0], value ]; + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); - self.triggerHandler( "setData" + parts[1] + "!", args ); + self.triggerHandler( "setData" + part, parts ); jQuery.data( this, key, value ); - self.triggerHandler( "changeData" + parts[1] + "!", args ); + self.triggerHandler( "changeData" + part, parts ); }); - } + }, null, value, arguments.length > 1, null, false ); }, removeData: function( key ) { @@ -1942,8 +1811,9 @@ function dataAttr( elem, key, data ) { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : - jQuery.isNumeric( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} @@ -1960,7 +1830,8 @@ function dataAttr( elem, key, data ) { // checks a cache object for emptiness function isEmptyDataObject( obj ) { - for ( var name in obj ) { + var name; + for ( name in obj ) { // if the public data object is empty, the private is still empty if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { @@ -1973,73 +1844,23 @@ function isEmptyDataObject( obj ) { return true; } - - - - -function handleQueueMarkDefer( elem, type, src ) { - var deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - defer = jQuery._data( elem, deferDataKey ); - if ( defer && - ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && - ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { - // Give room for hard-coded callbacks to fire first - // and eventually mark/queue something else on the element - setTimeout( function() { - if ( !jQuery._data( elem, queueDataKey ) && - !jQuery._data( elem, markDataKey ) ) { - jQuery.removeData( elem, deferDataKey, true ); - defer.fire(); - } - }, 0 ); - } -} - jQuery.extend({ - - _mark: function( elem, type ) { - if ( elem ) { - type = ( type || "fx" ) + "mark"; - jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); - } - }, - - _unmark: function( force, elem, type ) { - if ( force !== true ) { - type = elem; - elem = force; - force = false; - } - if ( elem ) { - type = type || "fx"; - var key = type + "mark", - count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); - if ( count ) { - jQuery._data( elem, key, count ); - } else { - jQuery.removeData( elem, key, true ); - handleQueueMarkDefer( elem, type, "mark" ); - } - } - }, - queue: function( elem, type, data ) { - var q; + var queue; + if ( elem ) { type = ( type || "fx" ) + "queue"; - q = jQuery._data( elem, type ); + queue = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { - if ( !q || jQuery.isArray(data) ) { - q = jQuery._data( elem, type, jQuery.makeArray(data) ); + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { - q.push( data ); + queue.push( data ); } } - return q || []; + return queue || []; } }, @@ -2047,51 +1868,75 @@ jQuery.extend({ type = type || "fx"; var queue = jQuery.queue( elem, type ), + startLength = queue.length, fn = queue.shift(), - hooks = {}; + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); + startLength--; } if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } - jQuery._data( elem, type + ".run", hooks ); - fn.call( elem, function() { - jQuery.dequeue( elem, type ); - }, hooks ); + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); } - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue " + type + ".run", true ); - handleQueueMarkDefer( elem, type, "queue" ); + if ( !startLength && hooks ) { + hooks.empty.fire(); } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery.removeData( elem, type + "queue", true ); + jQuery.removeData( elem, key, true ); + }) + }); } }); jQuery.fn.extend({ queue: function( type, data ) { + var setter = 2; + if ( typeof type !== "string" ) { data = type; type = "fx"; + setter--; } - if ( data === undefined ) { + if ( arguments.length < setter ) { return jQuery.queue( this[0], type ); } - return this.each(function() { - var queue = jQuery.queue( this, type, data ); - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); }, dequeue: function( type ) { return this.each(function() { @@ -2116,55 +1961,47 @@ jQuery.fn.extend({ }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) - promise: function( type, object ) { + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + if ( typeof type !== "string" ) { - object = type; + obj = type; type = undefined; } type = type || "fx"; - var defer = jQuery.Deferred(), - elements = this, - i = elements.length, - count = 1, - deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - tmp; - function resolve() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - } + while( i-- ) { - if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || - ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || - jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && - jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { count++; - tmp.add( resolve ); + tmp.empty.add( resolve ); } } resolve(); - return defer.promise(); + return defer.promise( obj ); } }); - - - - -var rclass = /[\n\t\r]/g, - rspace = /\s+/, +var nodeHook, boolHook, fixSpecified, + rclass = /[\t\r\n]/g, rreturn = /\r/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, + rclickable = /^a(?:rea|)$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - nodeHook, boolHook, fixSpecified; + getSetAttribute = jQuery.support.getSetAttribute; jQuery.fn.extend({ attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { @@ -2174,7 +2011,7 @@ jQuery.fn.extend({ }, prop: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.prop ); + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { @@ -2199,7 +2036,7 @@ jQuery.fn.extend({ } if ( value && typeof value === "string" ) { - classNames = value.split( rspace ); + classNames = value.split( core_rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; @@ -2212,7 +2049,7 @@ jQuery.fn.extend({ setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { - if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) { setClass += classNames[ c ] + " "; } } @@ -2226,31 +2063,30 @@ jQuery.fn.extend({ }, removeClass: function( value ) { - var classNames, i, l, elem, className, c, cl; + var removes, className, elem, c, cl, i, l; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } - if ( (value && typeof value === "string") || value === undefined ) { - classNames = ( value || "" ).split( rspace ); + removes = ( value || "" ).split( core_rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - className = (" " + elem.className + " ").replace( rclass, " " ); - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[ c ] + " ", " "); - } - elem.className = jQuery.trim( className ); - } else { - elem.className = ""; + className = (" " + elem.className + " ").replace( rclass, " " ); + + // loop over each item in the removal list + for ( c = 0, cl = removes.length; c < cl; c++ ) { + // Remove until there is nothing to remove, + while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) { + className = className.replace( " " + removes[ c ] + " " , " " ); + } } + elem.className = value ? jQuery.trim( className ) : ""; } } } @@ -2275,10 +2111,10 @@ jQuery.fn.extend({ i = 0, self = jQuery( this ), state = stateVal, - classNames = value.split( rspace ); + classNames = value.split( core_rspace ); while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list + // check each className given, space separated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } @@ -2300,7 +2136,7 @@ jQuery.fn.extend({ i = 0, l = this.length; for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { return true; } } @@ -2314,7 +2150,7 @@ jQuery.fn.extend({ if ( !arguments.length ) { if ( elem ) { - hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; @@ -2335,7 +2171,8 @@ jQuery.fn.extend({ isFunction = jQuery.isFunction( value ); return this.each(function( i ) { - var self = jQuery(this), val; + var val, + self = jQuery(this); if ( this.nodeType !== 1 ) { return; @@ -2358,7 +2195,7 @@ jQuery.fn.extend({ }); } - hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { @@ -2437,16 +2274,8 @@ jQuery.extend({ } }, - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, + // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9 + attrFn: {}, attr: function( elem, name, value, pass ) { var ret, hooks, notxml, @@ -2457,7 +2286,7 @@ jQuery.extend({ return; } - if ( pass && name in jQuery.attrFn ) { + if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) { return jQuery( elem )[ name ]( value ); } @@ -2485,7 +2314,7 @@ jQuery.extend({ return ret; } else { - elem.setAttribute( name, "" + value ); + elem.setAttribute( name, value + "" ); return value; } @@ -2504,25 +2333,29 @@ jQuery.extend({ }, removeAttr: function( elem, value ) { - var propName, attrNames, name, l, + var propName, attrNames, name, isBool, i = 0; if ( value && elem.nodeType === 1 ) { - attrNames = value.toLowerCase().split( rspace ); - l = attrNames.length; - for ( ; i < l; i++ ) { + attrNames = value.split( core_rspace ); + + for ( ; i < attrNames.length; i++ ) { name = attrNames[ i ]; if ( name ) { propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); // See #9699 for explanation of this approach (setting first, then removal) - jQuery.attr( elem, name, "" ); + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } elem.removeAttribute( getSetAttribute ? name : propName ); // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && propName in elem ) { + if ( isBool && propName in elem ) { elem[ propName ] = false; } } @@ -2637,9 +2470,6 @@ jQuery.extend({ } }); -// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) -jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; - // Hook for boolean attributes boolHook = { get: function( elem, name ) { @@ -2676,7 +2506,8 @@ if ( !getSetAttribute ) { fixSpecified = { name: true, - id: true + id: true, + coords: true }; // Use this for any attribute in IE6/7 @@ -2685,8 +2516,8 @@ if ( !getSetAttribute ) { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); - return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? - ret.nodeValue : + return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ? + ret.value : undefined; }, set: function( elem, value, name ) { @@ -2696,13 +2527,10 @@ if ( !getSetAttribute ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } - return ( ret.nodeValue = value + "" ); + return ( ret.value = value + "" ); } }; - // Apply the nodeHook to tabindex - jQuery.attrHooks.tabindex.set = nodeHook.set; - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { @@ -2750,7 +2578,7 @@ if ( !jQuery.support.style ) { return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { - return ( elem.style.cssText = "" + value ); + return ( elem.style.cssText = value + "" ); } }; } @@ -2800,35 +2628,12 @@ jQuery.each([ "radio", "checkbox" ], function() { } }); }); - - - - var rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, - rhoverHack = /\bhover(\.\S+)?\b/, + rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, + rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, - quickParse = function( selector ) { - var quick = rquickIs.exec( selector ); - if ( quick ) { - // 0 1 2 3 - // [ _, tag, id, class ] - quick[1] = ( quick[1] || "" ).toLowerCase(); - quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); - } - return quick; - }, - quickIs = function( elem, m ) { - var attrs = elem.attributes || {}; - return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || (attrs.id || {}).value === m[2]) && - (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) - ); - }, hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; @@ -2843,7 +2648,7 @@ jQuery.event = { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, - handleObjIn, quick, handlers, special; + handleObjIn, handlers, special; // Don't attach events to noData or text/comment nodes (allow plain objects tho) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { @@ -2854,6 +2659,7 @@ jQuery.event = { if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; + selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, used to find/remove it later @@ -2905,7 +2711,7 @@ jQuery.event = { handler: handler, guid: handler.guid, selector: selector, - quick: quickParse( selector ), + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn ); @@ -2955,9 +2761,9 @@ jQuery.event = { // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { - var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - t, tns, type, origType, namespaces, origCount, - j, events, special, handle, eventType, handleObj; + var t, tns, type, origType, namespaces, origCount, + j, events, special, eventType, handleObj, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); if ( !elemData || !(events = elemData.events) ) { return; @@ -2982,7 +2788,7 @@ jQuery.event = { type = ( selector? special.delegateType : special.bindType ) || type; eventType = events[ type ] || []; origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; // Remove matching events for ( j = 0; j < eventType.length; j++ ) { @@ -3006,7 +2812,7 @@ jQuery.event = { // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( eventType.length === 0 && origCount !== eventType.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } @@ -3016,14 +2822,11 @@ jQuery.event = { // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { - handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } + delete elemData.handle; // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete - jQuery.removeData( elem, [ "events", "handle" ], true ); + jQuery.removeData( elem, "events", true ); } }, @@ -3042,9 +2845,9 @@ jQuery.event = { } // Event object or event type - var type = event.type || event, - namespaces = [], - cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, + type = event.type || event, + namespaces = []; // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { @@ -3082,7 +2885,7 @@ jQuery.event = { event.isTrigger = true; event.exclusive = exclusive; event.namespace = namespaces.join( "." ); - event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // Handle a global trigger @@ -3121,14 +2924,13 @@ jQuery.event = { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; - old = null; - for ( ; cur; cur = cur.parentNode ) { + for ( old = elem; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( old && old === elem.ownerDocument ) { + if ( old === (elem.ownerDocument || document) ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } @@ -3145,7 +2947,7 @@ jQuery.event = { } // Note that this is a bare JS function and not a jQuery handler handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); } } @@ -3190,45 +2992,50 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); - var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related, + handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), delegateCount = handlers.delegateCount, - args = [].slice.call( arguments, 0 ), + args = core_slice.call( arguments ), run_all = !event.exclusive && !event.namespace, - handlerQueue = [], - i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = []; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; event.delegateTarget = this; - // Determine handlers that should run if there are delegated events - // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) - if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } - // Pregenerate a single jQuery object for reuse with .is() - jqcur = jQuery(this); - jqcur.context = this.ownerDocument || this; + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - selMatch = {}; - matches = []; - jqcur[0] = cur; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - sel = handleObj.selector; - - if ( selMatch[ sel ] === undefined ) { - selMatch[ sel ] = ( - handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) - ); + + // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + selMatch = {}; + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } } - if ( selMatch[ sel ] ) { - matches.push( handleObj ); + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); } } - if ( matches.length ) { - handlerQueue.push({ elem: cur, matches: matches }); - } } } @@ -3266,6 +3073,11 @@ jQuery.event = { } } + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + return event.result; }, @@ -3348,20 +3160,13 @@ jQuery.event = { event.target = event.target.parentNode; } - // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) - if ( event.metaKey === undefined ) { - event.metaKey = event.ctrlKey; - } + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) + event.metaKey = !!event.metaKey; return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady - }, - load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true @@ -3424,8 +3229,17 @@ jQuery.removeEvent = document.removeEventListener ? } } : function( elem, type, handle ) { + var name = "on" + type; + if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 – + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === "undefined" ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); } }; @@ -3522,11 +3336,11 @@ jQuery.each({ bindType: fix, handle: function( event ) { - var target = this, + var ret, + target = this, related = event.relatedTarget, handleObj = event.handleObj, - selector = handleObj.selector, - ret; + selector = handleObj.selector; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window @@ -3555,19 +3369,26 @@ if ( !jQuery.support.submitBubbles ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !form._submit_attached ) { + if ( form && !jQuery._data( form, "_submit_attached" ) ) { jQuery.event.add( form, "submit._submit", function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } + event._submit_bubble = true; }); - form._submit_attached = true; + jQuery._data( form, "_submit_attached", true ); } }); // return undefined since we don't need an event listener }, + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + teardown: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { @@ -3600,8 +3421,9 @@ if ( !jQuery.support.changeBubbles ) { jQuery.event.add( this, "click._change", function( event ) { if ( this._just_changed && !event.isTrigger ) { this._just_changed = false; - jQuery.event.simulate( "change", this, event, true ); } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); }); } return false; @@ -3610,13 +3432,13 @@ if ( !jQuery.support.changeBubbles ) { jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; - if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { jQuery.event.add( elem, "change._change", function( event ) { if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { jQuery.event.simulate( "change", this.parentNode, event, true ); } }); - elem._change_attached = true; + jQuery._data( elem, "_change_attached", true ); } }); }, @@ -3633,7 +3455,7 @@ if ( !jQuery.support.changeBubbles ) { teardown: function() { jQuery.event.remove( this, "._change" ); - return rformElems.test( this.nodeName ); + return !rformElems.test( this.nodeName ); } }; } @@ -3671,9 +3493,9 @@ jQuery.fn.extend({ // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { + if ( typeof selector !== "string" ) { // && selector != null // ( types-Object, data ) - data = selector; + data = data || selector; selector = undefined; } for ( type in types ) { @@ -3719,14 +3541,15 @@ jQuery.fn.extend({ }); }, one: function( types, selector, data, fn ) { - return this.on.call( this, types, selector, data, fn, 1 ); + return this.on( types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { + var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event - var handleObj = types.handleObj; + handleObj = types.handleObj; jQuery( types.delegateTarget ).off( - handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); @@ -3734,7 +3557,7 @@ jQuery.fn.extend({ } if ( typeof types === "object" ) { // ( types-object [, selector] ) - for ( var type in types ) { + for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; @@ -3773,7 +3596,7 @@ jQuery.fn.extend({ }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) - return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); }, trigger: function( type, data ) { @@ -3834,10 +3657,6 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl this.trigger( name ); }; - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } - if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } @@ -3846,1468 +3665,1683 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); - - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - expando = "sizcache" + (Math.random() + '').replace('.', ''), - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rReturn = /\r\n/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context, seed ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set, seed ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set, i, len, match, type, left; - - if ( !expr ) { - return []; - } - - for ( i = 0, len = Expr.order.length; i < len; i++ ) { - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - type, found, item, filter, left, - i, pass, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - filter = Expr.filter[ type ]; - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - pass = not ^ found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Utility function for retreiving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -var getText = Sizzle.getText = function( elem ) { - var i, node, - nodeType = elem.nodeType, - ret = ""; - - if ( nodeType ) { - if ( nodeType === 1 || nodeType === 9 ) { - // Use textContent || innerText for elements - if ( typeof elem.textContent === 'string' ) { - return elem.textContent; - } else if ( typeof elem.innerText === 'string' ) { - // Replace IE's carriage returns - return elem.innerText.replace( rReturn, '' ); - } else { - // Traverse it's children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - } else { - - // If no nodeType, this is expected to be an array - for ( i = 0; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - if ( node.nodeType !== 8 ) { - ret += getText( node ); - } - } - } - return ret; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - var attr = elem.getAttribute( "type" ), type = elem.type; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); - }, - - radio: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && "button" === elem.type || name === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - }, - - focus: function( elem ) { - return elem === elem.ownerDocument.activeElement; - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var first, last, - doneName, parent, cache, - count, diff, - type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - first = match[2]; - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - doneName = match[0]; - parent = elem.parentNode; - - if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { - count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent[ expando ] = doneName; - } - - diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Sizzle.attr ? - Sizzle.attr( elem, name ) : - Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - !type && Sizzle.attr ? - result != null : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = "<a name='" + id + "'/>"; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = "<a href='#'></a>"; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "<p class='TEST'></p>"; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - - if ( matches ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9 fails this) - var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - var ret = matches.call( node, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || !disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9, so check for that - node.document && node.document.nodeType !== 11 ) { - return ret; - } - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "<div class='test e'></div><div class='test'></div>"; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem[ expando ] = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem[ expando ] = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context, seed ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet, seed ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE +/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+ assertGetIdNotName,
+ Expr,
+ getText,
+ isXML,
+ contains,
+ compile,
+ sortOrder,
+ hasDuplicate,
+ outermostContext,
+
+ baseHasDuplicate = true,
+ strundefined = "undefined",
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ Token = String,
+ document = window.document,
+ docElem = document.documentElement,
+ dirruns = 0,
+ done = 0,
+ pop = [].pop,
+ push = [].push,
+ slice = [].slice,
+ // Use a stripped-down indexOf if a native one is unavailable
+ indexOf = [].indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // Augment a function for special use by Sizzle
+ markFunction = function( fn, value ) {
+ fn[ expando ] = value == null || value;
+ return fn;
+ },
+
+ createCache = function() {
+ var cache = {},
+ keys = [];
+
+ return markFunction(function( key, value ) {
+ // Only keep the most recent entries
+ if ( keys.push( key ) > Expr.cacheLength ) {
+ delete cache[ keys.shift() ];
+ }
+
+ return (cache[ key ] = value);
+ }, cache );
+ },
+
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments not in parens/brackets,
+ // then attribute selectors and non-pseudos (denoted by :),
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
+
+ // For matchExpr.POS and matchExpr.needsContext
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
+ rpseudo = new RegExp( pseudos ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rnot = /^:not/,
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "POS": new RegExp( pos, "i" ),
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ // Support
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // release memory in IE
+ div = null;
+ }
+ },
+
+ // Check if getElementsByTagName("*") returns only elements
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = "<a href='#'></a>";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = "<select></select>";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
+ if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
+ return false;
+ }
+
+ // Safari 3.2 caches class attributes and doesn't catch changes
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length === 2;
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length === 2 +
+ // buggy browsers will return more than the correct 0
+ document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ });
+
+// If slice is not available, provide a backup
+try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+ slice = function( i ) {
+ var elem,
+ results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
+}
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+// Returns a function to use in pseudos for input types
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for buttons
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for positionals
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+};
+
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+contains = Sizzle.contains = docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return b && !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+Sizzle.attr = function( elem, name ) {
+ var val,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( (val = Expr.attrHandle[ name ]) ) {
+ return val( elem );
+ }
+ if ( xml || assertAttributes ) {
+ return elem.getAttribute( name );
+ }
+ val = elem.getAttributeNode( name );
+ return val ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ val.specified ? val.value : null :
+ null;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ // IE6/7 return a modified href
+ attrHandle: assertHrefNotNormalized ?
+ {} :
+ {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ },
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ },
+
+ "NAME": assertUsableName && function( tag, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ },
+
+ "CLASS": assertUsableClassName && function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var unquoted, excess;
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ if ( match[3] ) {
+ match[2] = match[3];
+ } else if ( (unquoted = match[4]) ) {
+ // Only check arguments that contain a pseudo
+ if ( rpseudo.test(unquoted) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ unquoted = unquoted.slice( 0, excess );
+ match[0] = match[0].slice( 0, excess );
+ }
+ match[2] = unquoted;
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ expando ][ className ];
+ if ( !pattern ) {
+ pattern = classCache( className, new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)") );
+ }
+ return function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ };
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem, context ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ return function( elem ) {
+ var node, diff,
+ parent = elem.parentNode;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ if ( parent ) {
+ diff = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ diff++;
+ if ( elem === node ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset (or cast to NaN), then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ };
+ }
+
+ return function( elem ) {
+ var node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputPseudo("radio"),
+ "checkbox": createInputPseudo("checkbox"),
+ "file": createInputPseudo("file"),
+ "password": createInputPseudo("password"),
+ "image": createInputPseudo("image"),
+
+ "submit": createButtonPseudo("submit"),
+ "reset": createButtonPseudo("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ },
+
+ // Positional types
+ "first": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 0; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 1; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+function siblingCheck( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+}
+
+sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ } :
+ function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+// Always assume the presence of duplicates if sort doesn't
+// pass them to our comparison function (as in Google Chrome).
+[0, 0].sort( sortOrder );
+baseHasDuplicate = !hasDuplicate;
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ i = 1;
+
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type, soFar, groups, preFilters,
+ cached = tokenCache[ expando ][ selector ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ soFar = soFar.slice( match[0].length );
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+
+ // Cast descendant combinators to space
+ matched.type = match[0].replace( rtrim, " " );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ // The last two arguments here are (context, xml) for backCompat
+ (match = preFilters[ type ]( match, document, true ))) ) {
+
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+ matched.type = type;
+ matched.matches = match;
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && combinator.dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( !xml ) {
+ var cache,
+ dirkey = dirruns + " " + doneName + " ",
+ cachedkey = dirkey + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context, xml ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( matcher( elem, context, xml ) ) {
+ return elem;
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ // Positional selectors apply to seed elements, so it is invalid to follow them with relative ones
+ if ( seed && postFinder ) {
+ return;
+ }
+
+ var i, elem, postFilterIn,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ postFilterIn = condense( matcherOut, postMap );
+ postFilter( postFilterIn, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = postFilterIn.length;
+ while ( i-- ) {
+ if ( (elem = postFilterIn[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ // Keep seed and results synchronized
+ if ( seed ) {
+ // Ignore postFinder because it can't coexist with seed
+ i = preFilter && matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ seed[ preMap[i] ] = !(results[ preMap[i] ] = elem);
+ }
+ }
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ // The concatenated values are (context, xml) for backCompat
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && tokens.join("")
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Nested matchers should use non-integer dirruns
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = superMatcher.el;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++superMatcher.el;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ superMatcher.el = 0;
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ expando ][ selector ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results, seed ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results, seed );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed, xml ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector ),
+ j = match.length;
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && !xml &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( rbackslash, "" ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context,
+ xml
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && tokens.join("");
+ if ( !selector ) {
+ push.apply( results, slice.call( seed, 0 ) );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ xml,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+}
+
+if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+
+ // qSa(:focus) reports false when true (Chrome 21),
+ // A support test would require too much code (would include document ready)
+ rbuggyQSA = [":focus"],
+
+ // matchesSelector(:focus) reports false when true (Chrome 21),
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [ ":active", ":focus" ],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explictly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "<select><option selected=''></option></select>";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "<p test=''></p>";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = "<input type='hidden'/>";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ // rbuggyQSA always contains :focus, so no need for a length check
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ var groups, i,
+ old = true,
+ nid = expando,
+ newContext = context,
+ newSelector = context.nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + groups[i].join("");
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ newSelector
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( "!=", pseudos );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active and :focus, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+}
+
+// Deprecated
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Back-compat
+function setFilters() {}
+Expr.filters = setFilters.prototype = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
// Override sizzle attribute retrieval Sizzle.attr = jQuery.attr; -Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; +jQuery.expr[":"] = jQuery.expr.pseudos; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; - - -})(); - - +
+
+})( window );
var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS, + rneedsContext = jQuery.expr.match.needsContext, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, @@ -5318,8 +5352,8 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { - var self = this, - i, l; + var i, l, length, n, r, ret, + self = this; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { @@ -5331,8 +5365,7 @@ jQuery.fn.extend({ }); } - var ret = this.pushStack( "", "find", selector ), - length, n, r; + ret = this.pushStack( "", "find", selector ); for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; @@ -5355,9 +5388,12 @@ jQuery.fn.extend({ }, has: function( target ) { - var targets = jQuery( target ); + var i, + targets = jQuery( target, this ), + len = targets.length; + return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { + for ( i = 0; i < len; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } @@ -5374,57 +5410,34 @@ jQuery.fn.extend({ }, is: function( selector ) { - return !!selector && ( + return !!selector && ( typeof selector === "string" ? - // If this is a positional selector, check membership in the returned set + // If this is a positional/relative selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". - POS.test( selector ) ? + rneedsContext.test( selector ) ? jQuery( selector, this.context ).index( this[0] ) >= 0 : jQuery.filter( selector, this ).length > 0 : this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - // Array (deprecated as of jQuery 1.7) - if ( jQuery.isArray( selectors ) ) { - var level = 1; - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( i = 0; i < selectors.length; i++ ) { - - if ( jQuery( cur ).is( selectors[ i ] ) ) { - ret.push({ selector: selectors[ i ], elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - - return ret; - } - - // String - var pos = POS.test( selectors ) || typeof selectors !== "string" ? + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; - for ( i = 0, l = this.length; i < l; i++ ) { + for ( ; i < l; i++ ) { cur = this[i]; - while ( cur ) { + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { - break; - } } + cur = cur.parentNode; } } @@ -5464,17 +5477,29 @@ jQuery.fn.extend({ jQuery.unique( all ) ); }, - andSelf: function() { - return this.add( this.prevObject ); + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); } }); +jQuery.fn.andSelf = jQuery.fn.addBack; + // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; @@ -5487,10 +5512,10 @@ jQuery.each({ return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); + return sibling( elem, "nextSibling" ); }, prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); + return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); @@ -5505,7 +5530,7 @@ jQuery.each({ return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); @@ -5513,7 +5538,7 @@ jQuery.each({ contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); + jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { @@ -5529,11 +5554,11 @@ jQuery.each({ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + if ( this.length > 1 && rparentsprev.test( name ) ) { ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + return this.pushStack( ret, name, core_slice.call( arguments ).join(",") ); }; }); @@ -5561,19 +5586,6 @@ jQuery.extend({ return matched; }, - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - sibling: function( n, elem ) { var r = []; @@ -5621,10 +5633,6 @@ function winnow( elements, qualifier, keep ) { return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; }); } - - - - function createSafeFragment( document ) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment(); @@ -5639,21 +5647,22 @@ function createSafeFragment( document ) { return safeFrag; } -var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, rtagName = /<([\w:]+)/, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, - rnoInnerhtml = /<(?:script|style)/i, + rnoInnerhtml = /<(?:script|style|link)/i, rnocache = /<(?:script|object|embed|option|style)/i, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"), + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rcheckableType = /^(?:checkbox|radio)$/, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, - rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, + rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], @@ -5664,32 +5673,26 @@ var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|fig area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }, - safeFragment = createSafeFragment( document ); + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; -// IE can't serialize <link> and <script> tags normally +// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, +// unless wrapped in a div with non-breaking characters in front of it. if ( !jQuery.support.htmlSerialize ) { - wrapMap._default = [ 1, "div<div>", "</div>" ]; + wrapMap._default = [ 1, "X<div>", "</div>" ]; } jQuery.fn.extend({ - text: function( text ) { - if ( jQuery.isFunction(text) ) { - return this.each(function(i) { - var self = jQuery( this ); - - self.text( text.call(this, i, self.text()) ); - }); - } - - if ( typeof text !== "object" && text !== undefined ) { - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - } - - return jQuery.text( this ); + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); }, wrapAll: function( html ) { @@ -5759,7 +5762,7 @@ jQuery.fn.extend({ append: function() { return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { this.appendChild( elem ); } }); @@ -5767,39 +5770,44 @@ jQuery.fn.extend({ prepend: function() { return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { this.insertBefore( elem, this.firstChild ); } }); }, before: function() { - if ( this[0] && this[0].parentNode ) { + if ( !isDisconnected( this[0] ) ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this ); }); - } else if ( arguments.length ) { + } + + if ( arguments.length ) { var set = jQuery.clean( arguments ); - set.push.apply( set, this.toArray() ); - return this.pushStack( set, "before", arguments ); + return this.pushStack( jQuery.merge( set, this ), "before", this.selector ); } }, after: function() { - if ( this[0] && this[0].parentNode ) { + if ( !isDisconnected( this[0] ) ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this.nextSibling ); }); - } else if ( arguments.length ) { - var set = this.pushStack( this, "after", arguments ); - set.push.apply( set, jQuery.clean(arguments) ); - return set; + } + + if ( arguments.length ) { + var set = jQuery.clean( arguments ); + return this.pushStack( jQuery.merge( this, set ), "after", this.selector ); } }, // keepData is for internal use only--do not document remove: function( selector, keepData ) { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); @@ -5816,7 +5824,10 @@ jQuery.fn.extend({ }, empty: function() { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); @@ -5841,48 +5852,49 @@ jQuery.fn.extend({ }, html: function( value ) { - if ( value === undefined ) { - return this[0] && this[0].nodeType === 1 ? - this[0].innerHTML.replace(rinlinejQuery, "") : - null; - - // See if we can take a shortcut and just use innerHTML - } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && - !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; - value = value.replace(rxhtmlTag, "<$1></$2>"); + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } - try { - for ( var i = 0, l = this.length; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - if ( this[i].nodeType === 1 ) { - jQuery.cleanData( this[i].getElementsByTagName("*") ); - this[i].innerHTML = value; - } - } + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { - // If using innerHTML throws an exception, use the fallback method - } catch(e) { - this.empty().append( value ); - } + value = value.replace( rxhtmlTag, "<$1></$2>" ); - } else if ( jQuery.isFunction( value ) ) { - this.each(function(i){ - var self = jQuery( this ); + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName( "*" ) ); + elem.innerHTML = value; + } + } - self.html( value.call(this, i, self.html()) ); - }); + elem = 0; - } else { - this.empty().append( value ); - } + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } - return this; + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); }, replaceWith: function( value ) { - if ( this[0] && this[0].parentNode ) { + if ( !isDisconnected( this[0] ) ) { // Make sure that the elements are removed from the DOM before they are inserted // this can help fix replacing a parent with child elements if ( jQuery.isFunction( value ) ) { @@ -5908,11 +5920,11 @@ jQuery.fn.extend({ jQuery(parent).append( value ); } }); - } else { - return this.length ? - this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : - this; } + + return this.length ? + this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : + this; }, detach: function( selector ) { @@ -5920,68 +5932,84 @@ jQuery.fn.extend({ }, domManip: function( args, table, callback ) { - var results, first, fragment, parent, + + // Flatten any nested arrays + args = [].concat.apply( [], args ); + + var results, first, fragment, iNoClone, + i = 0, value = args[0], - scripts = []; + scripts = [], + l = this.length; // We can't cloneNode fragments that contain checked, in WebKit - if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) { return this.each(function() { - jQuery(this).domManip( args, table, callback, true ); + jQuery(this).domManip( args, table, callback ); }); } if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); - args[0] = value.call(this, i, table ? self.html() : undefined); + args[0] = value.call( this, i, table ? self.html() : undefined ); self.domManip( args, table, callback ); }); } if ( this[0] ) { - parent = value && value.parentNode; - - // If we're in a fragment, just use that instead of building a new one - if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { - results = { fragment: parent }; - - } else { - results = jQuery.buildFragment( args, this, scripts ); - } - + results = jQuery.buildFragment( args, this, scripts ); fragment = results.fragment; + first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { - first = fragment = fragment.firstChild; - } else { - first = fragment.firstChild; + fragment = first; } if ( first ) { table = table && jQuery.nodeName( first, "tr" ); - for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + // Fragments from the fragment cache must always be cloned and never used in place. + for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) { callback.call( - table ? - root(this[i], first) : + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : this[i], - // Make sure that we do not leak memory by inadvertently discarding - // the original fragment (which might have attached data) instead of - // using it; in addition, use the original fragment object for the last - // item instead of first because it can end up being emptied incorrectly - // in certain situations (Bug #8070). - // Fragments from the fragment cache must always be cloned and never used - // in place. - results.cacheable || ( l > 1 && i < lastIndex ) ? - jQuery.clone( fragment, true, true ) : - fragment + i === iNoClone ? + fragment : + jQuery.clone( fragment, true, true ) ); } } + // Fix #11809: Avoid leaking memory + fragment = first = null; + if ( scripts.length ) { - jQuery.each( scripts, evalScript ); + jQuery.each( scripts, function( i, elem ) { + if ( elem.src ) { + if ( jQuery.ajax ) { + jQuery.ajax({ + url: elem.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.error("no ajax"); + } + } else { + jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + }); } } @@ -5989,11 +6017,8 @@ jQuery.fn.extend({ } }); -function root( elem, cur ) { - return jQuery.nodeName(elem, "table") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); } function cloneCopyEvent( src, dest ) { @@ -6013,7 +6038,7 @@ function cloneCopyEvent( src, dest ) { for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); + jQuery.event.add( dest, type, events[ type ][ i ] ); } } } @@ -6046,19 +6071,27 @@ function cloneFixAttributes( src, dest ) { nodeName = dest.nodeName.toLowerCase(); - // IE6-8 fail to clone children inside object elements that use - // the proprietary classid attribute value (rather than the type - // attribute) to identify the type of content to display if ( nodeName === "object" ) { - dest.outerHTML = src.outerHTML; + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } - } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { // IE6-8 fails to persist the checked state of a cloned checkbox // or radio button. Worse, IE6-7 fail to give the cloned element // a checked appearance if the defaultChecked value isn't also set - if ( src.checked ) { - dest.defaultChecked = dest.checked = src.checked; - } + + dest.defaultChecked = dest.checked = src.checked; // IE6-7 get confused and end up setting the value of a cloned // checkbox/radio button to an empty string instead of "on" @@ -6075,6 +6108,10 @@ function cloneFixAttributes( src, dest ) { // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; + + // IE blanks contents when cloning scripts + } else if ( nodeName === "script" && dest.text !== src.text ) { + dest.text = src.text; } // Event data gets referenced instead of copied if the expando @@ -6082,49 +6119,42 @@ function cloneFixAttributes( src, dest ) { dest.removeAttribute( jQuery.expando ); } -jQuery.buildFragment = function( args, nodes, scripts ) { - var fragment, cacheable, cacheresults, doc, - first = args[ 0 ]; - - // nodes may contain either an explicit document object, - // a jQuery collection or context object. - // If nodes[0] contains a valid object to assign to doc - if ( nodes && nodes[0] ) { - doc = nodes[0].ownerDocument || nodes[0]; - } +jQuery.buildFragment = function( args, context, scripts ) { + var fragment, cacheable, cachehit, + first = args[ 0 ]; - // Ensure that an attr object doesn't incorrectly stand in as a document object - // Chrome and Firefox seem to allow this to occur and will throw exception - // Fixes #8950 - if ( !doc.createDocumentFragment ) { - doc = document; - } + // Set context from what may come in as undefined or a jQuery collection or a node + // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 & + // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception + context = context || document; + context = !context.nodeType && context[0] || context; + context = context.ownerDocument || context; // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 - if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && + if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document && first.charAt(0) === "<" && !rnocache.test( first ) && (jQuery.support.checkClone || !rchecked.test( first )) && (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { + // Mark cacheable and look for a hit cacheable = true; - - cacheresults = jQuery.fragments[ first ]; - if ( cacheresults && cacheresults !== 1 ) { - fragment = cacheresults; - } + fragment = jQuery.fragments[ first ]; + cachehit = fragment !== undefined; } if ( !fragment ) { - fragment = doc.createDocumentFragment(); - jQuery.clean( args, doc, fragment, scripts ); - } + fragment = context.createDocumentFragment(); + jQuery.clean( args, context, fragment, scripts ); - if ( cacheable ) { - jQuery.fragments[ first ] = cacheresults ? fragment : 1; + // Update the cache, but only store false + // unless this is a second parsing of the same content + if ( cacheable ) { + jQuery.fragments[ first ] = cachehit && fragment; + } } return { fragment: fragment, cacheable: cacheable }; @@ -6140,17 +6170,19 @@ jQuery.each({ replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { - var ret = [], + var elems, + i = 0, + ret = [], insert = jQuery( selector ), + l = insert.length, parent = this.length === 1 && this[0].parentNode; - if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) { insert[ original ]( this[0] ); return this; - } else { - for ( var i = 0, l = insert.length; i < l; i++ ) { - var elems = ( i > 0 ? this.clone(true) : this ).get(); + for ( ; i < l; i++ ) { + elems = ( i > 0 ? this.clone(true) : this ).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } @@ -6174,39 +6206,26 @@ function getAll( elem ) { // Used in clean, fixes the defaultChecked property function fixDefaultChecked( elem ) { - if ( elem.type === "checkbox" || elem.type === "radio" ) { + if ( rcheckableType.test( elem.type ) ) { elem.defaultChecked = elem.checked; } } -// Finds all inputs and passes them to fixDefaultChecked -function findInputs( elem ) { - var nodeName = ( elem.nodeName || "" ).toLowerCase(); - if ( nodeName === "input" ) { - fixDefaultChecked( elem ); - // Skip scripts, get other children - } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { - jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); - } -} - -// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js -function shimCloneNode( elem ) { - var div = document.createElement( "div" ); - safeFragment.appendChild( div ); - - div.innerHTML = elem.outerHTML; - return div.firstChild; -} jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { var srcElements, destElements, i, - // IE<=8 does not properly clone detached, unknown element nodes - clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? - elem.cloneNode( true ) : - shimCloneNode( elem ); + clone; + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { @@ -6254,18 +6273,17 @@ jQuery.extend({ }, clean: function( elems, context, fragment, scripts ) { - var checkScriptType; - - context = context || document; + var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags, + safe = context === document && safeFragment, + ret = []; - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) { - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + // Ensure that context is a document + if ( !context || typeof context.createDocumentFragment === "undefined" ) { + context = document; } - var ret = [], j; - - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + // Use the already-created safe fragment if context permits + for ( i = 0; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; } @@ -6279,25 +6297,18 @@ jQuery.extend({ if ( !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } else { + // Ensure a safe container in which to render the html + safe = safe || createSafeFragment( context ); + div = context.createElement("div"); + safe.appendChild( div ); + // Fix "XHTML"-style tags in all browsers elem = elem.replace(rxhtmlTag, "<$1></$2>"); - // Trim whitespace, otherwise indexOf won't work as expected - var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), - wrap = wrapMap[ tag ] || wrapMap._default, - depth = wrap[0], - div = context.createElement("div"); - - // Append wrapper element to unknown element safe doc fragment - if ( context === document ) { - // Use the fragment we've already created for this document - safeFragment.appendChild( div ); - } else { - // Use a fragment created with the owner document - createSafeFragment( context ).appendChild( div ); - } - // Go to html and back, then peel off extra wrappers + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + depth = wrap[0]; div.innerHTML = wrap[1] + elem + wrap[2]; // Move to the right depth @@ -6309,7 +6320,7 @@ jQuery.extend({ if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious <tbody> - var hasBody = rtbody.test(elem), + hasBody = rtbody.test(elem); tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : @@ -6331,44 +6342,63 @@ jQuery.extend({ } elem = div.childNodes; - } - } - // Resets defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - var len; - if ( !jQuery.support.appendChecked ) { - if ( elem[0] && typeof (len = elem.length) === "number" ) { - for ( j = 0; j < len; j++ ) { - findInputs( elem[j] ); - } - } else { - findInputs( elem ); + // Take out of fragment container (we need a fresh div each time) + div.parentNode.removeChild( div ); } } if ( elem.nodeType ) { ret.push( elem ); } else { - ret = jQuery.merge( ret, elem ); + jQuery.merge( ret, elem ); + } + } + + // Fix #11356: Clear elements from safeFragment + if ( div ) { + elem = div = safe = null; + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + for ( i = 0; (elem = ret[i]) != null; i++ ) { + if ( jQuery.nodeName( elem, "input" ) ) { + fixDefaultChecked( elem ); + } else if ( typeof elem.getElementsByTagName !== "undefined" ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } } } + // Append elements to a provided document fragment if ( fragment ) { - checkScriptType = function( elem ) { - return !elem.type || rscriptType.test( elem.type ); + // Special handling of each script element + handleScript = function( elem ) { + // Check if we consider it executable + if ( !elem.type || rscriptType.test( elem.type ) ) { + // Detach the script and store it in the scripts array (if provided) or the fragment + // Return truthy to indicate that it has been handled + return scripts ? + scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : + fragment.appendChild( elem ); + } }; - for ( i = 0; ret[i]; i++ ) { - if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { - scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); - } else { - if ( ret[i].nodeType === 1 ) { - var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); + for ( i = 0; (elem = ret[i]) != null; i++ ) { + // Check if we're done after handling an executable script + if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { + // Append to fragment and handle embedded scripts + fragment.appendChild( elem ); + if ( typeof elem.getElementsByTagName !== "undefined" ) { + // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration + jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); + // Splice the scripts into ret after their former ancestor and advance our index beyond them ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); + i += jsTags.length; } - fragment.appendChild( ret[i] ); } } } @@ -6376,99 +6406,253 @@ jQuery.extend({ return ret; }, - cleanData: function( elems ) { - var data, id, + cleanData: function( elems, /* internal */ acceptData ) { + var data, id, elem, type, + i = 0, + internalKey = jQuery.expando, cache = jQuery.cache, - special = jQuery.event.special, - deleteExpando = jQuery.support.deleteExpando; + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { - continue; - } + for ( ; (elem = elems[i]) != null; i++ ) { - id = elem[ jQuery.expando ]; + if ( acceptData || jQuery.acceptData( elem ) ) { - if ( id ) { - data = cache[ id ]; + id = elem[ internalKey ]; + data = id && cache[ id ]; - if ( data && data.events ) { - for ( var type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } } } - // Null the DOM reference to avoid IE6/7/8 leak (#7054) - if ( data.handle ) { - data.handle.elem = null; - } - } + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { - if ( deleteExpando ) { - delete elem[ jQuery.expando ]; + delete cache[ id ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); - delete cache[ id ]; + } else { + elem[ internalKey ] = null; + } + + jQuery.deletedIds.push( id ); + } + } } } } }); +// Limit scope pollution from any deprecated API +(function() { + +var matched, browser; + +// Use of jQuery.browser is frowned upon. +// More details: http://api.jquery.com/jQuery.browser +// jQuery.uaMatch maintained for back-compat +jQuery.uaMatch = function( ua ) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; +}; -function evalScript( i, elem ) { - if ( elem.src ) { - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - } else { - jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); - } +matched = jQuery.uaMatch( navigator.userAgent ); +browser = {}; - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } +if ( matched.browser ) { + browser[ matched.browser ] = true; + browser.version = matched.version; } +// Chrome is Webkit, but Webkit is also Safari. +if ( browser.chrome ) { + browser.webkit = true; +} else if ( browser.webkit ) { + browser.safari = true; +} +jQuery.browser = browser; +jQuery.sub = function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; +}; -var ralpha = /alpha\([^)]*\)/i, +})(); +var curCSS, iframe, iframeDoc, + ralpha = /alpha\([^)]*\)/i, ropacity = /opacity=([^)]*)/, - // fixed for IE9, see #8346 - rupper = /([A-Z]|^ms)/g, - rnumpx = /^-?\d+(?:px)?$/i, - rnum = /^-?\d/, - rrelNum = /^([\-+])=([\-+.\de]+)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ), + elemdisplay = {}, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssWidth = [ "Left", "Right" ], - cssHeight = [ "Top", "Bottom" ], - curCSS, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, - getComputedStyle, - currentStyle; + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ], -jQuery.fn.css = function( name, value ) { - // Setting 'undefined' is a no-op - if ( arguments.length === 2 && value === undefined ) { - return this; + eventsToggle = jQuery.fn.toggle; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; } - return jQuery.access( this, name, value, true, function( elem, name, value ) { - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }); -}; + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var elem, display, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + values[ index ] = jQuery._data( elem, "olddisplay" ); + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && elem.style.display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + display = curCSS( elem, "display" ); + + if ( !values[ index ] && display !== "none" ) { + jQuery._data( elem, "olddisplay", display ); + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state, fn2 ) { + var bool = typeof state === "boolean"; + + if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) { + return eventsToggle.apply( this, arguments ); + } + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); jQuery.extend({ // Add in style property hooks for overriding the default @@ -6478,11 +6662,9 @@ jQuery.extend({ get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity - var ret = curCSS( elem, "opacity", "opacity" ); + var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; - } else { - return elem.style.opacity; } } } @@ -6515,10 +6697,15 @@ jQuery.extend({ } // Make sure that we're working with the right name - var ret, type, origName = jQuery.camelCase( name ), - style = elem.style, hooks = jQuery.cssHooks[ origName ]; + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; - name = jQuery.cssProps[ origName ] || origName; + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { @@ -6526,7 +6713,7 @@ jQuery.extend({ // convert relative number strings (+= or -=) to relative numbers. #7345 if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) ); + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); // Fixes bug #9237 type = "number"; } @@ -6542,7 +6729,7 @@ jQuery.extend({ } // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { // Wrapped to prevent IE from throwing errors when 'invalid' values are provided // Fixes bug #5509 try { @@ -6561,81 +6748,299 @@ jQuery.extend({ } }, - css: function( elem, name, extra ) { - var ret, hooks; + css: function( elem, name, numeric, extra ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); // Make sure that we're working with the right name - name = jQuery.camelCase( name ); - hooks = jQuery.cssHooks[ name ]; - name = jQuery.cssProps[ name ] || name; + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); - // cssFloat needs a special treatment - if ( name === "cssFloat" ) { - name = "float"; - } + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { - return ret; + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } // Otherwise, if a way to get the computed value exists, use that - } else if ( curCSS ) { - return curCSS( elem, name ); + if ( val === undefined ) { + val = curCSS( elem, name ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( numeric || extra !== undefined ) { + num = parseFloat( val ); + return numeric || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; }, // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { - var old = {}; + var ret, name, + old = {}; // Remember the old values, and insert the new ones - for ( var name in options ) { + for ( name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } - callback.call( elem ); + ret = callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } + + return ret; } }); -// DEPRECATED, Use jQuery.css() instead -jQuery.curCSS = jQuery.css; +// NOTE: To any future maintainer, we've window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + curCSS = function( elem, name ) { + var ret, width, minWidth, maxWidth, + computed = window.getComputedStyle( elem, null ), + style = elem.style; + + if ( computed ) { + + ret = computed[ name ]; + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + curCSS = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + // we use jQuery.css instead of curCSS here + // because of the reliableMarginRight CSS hook! + val += jQuery.css( elem, extra + cssExpand[ i ], true ); + } + + // From this point on we use curCSS for maximum performance (relevant in animations) + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } else { + // at this point, extra isn't content, so add padding + val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + valueIsBorderBox = true, + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); -jQuery.each(["height", "width"], function( i, name ) { + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox + ) + ) + "px"; +} + + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + if ( elemdisplay[ nodeName ] ) { + return elemdisplay[ nodeName ]; + } + + var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ), + display = elem.css("display"); + elem.remove(); + + // If the simple way fails, + // get element's real default display by attaching it to a temp iframe + if ( display === "none" || display === "" ) { + // Use the already-created iframe if possible + iframe = document.body.appendChild( + iframe || jQuery.extend( document.createElement("iframe"), { + frameBorder: 0, + width: 0, + height: 0 + }) + ); + + // Create a cacheable copy of the iframe document on first call. + // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML + // document to it; WebKit & Firefox won't allow reusing the iframe document. + if ( !iframeDoc || !iframe.createElement ) { + iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; + iframeDoc.write("<!doctype html><html><body>"); + iframeDoc.close(); + } + + elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) ); + + display = curCSS( elem, "display" ); + document.body.removeChild( iframe ); + } + + // Store the correct default display + elemdisplay[ nodeName ] = display; + + return display; +} + +jQuery.each([ "height", "width" ], function( i, name ) { jQuery.cssHooks[ name ] = { get: function( elem, computed, extra ) { - var val; - if ( computed ) { - if ( elem.offsetWidth !== 0 ) { - return getWH( elem, name, extra ); - } else { - jQuery.swap( elem, cssShow, function() { - val = getWH( elem, name, extra ); + // certain elements can have dimension info if we invisibly show them + // however, it must have a current display style that would benefit from this + if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) { + return jQuery.swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); }); + } else { + return getWidthOrHeight( elem, name, extra ); } - - return val; } }, - set: function( elem, value ) { - if ( rnumpx.test( value ) ) { - // ignore negative width and height values #1599 - value = parseFloat( value ); - - if ( value >= 0 ) { - return value + "px"; - } - - } else { - return value; - } + set: function( elem, value, extra ) { + return setPositiveNumber( elem, value, extra ? + augmentWidthOrHeight( + elem, + name, + extra, + jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box" + ) : 0 + ); } }; }); @@ -6645,7 +7050,7 @@ if ( !jQuery.support.opacity ) { get: function( elem, computed ) { // IE uses filters for opacity return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? - ( parseFloat( RegExp.$1 ) / 100 ) + "" : + ( 0.01 * parseFloat( RegExp.$1 ) ) + "" : computed ? "1" : ""; }, @@ -6660,7 +7065,8 @@ if ( !jQuery.support.opacity ) { style.zoom = 1; // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 - if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { + if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" && + style.removeAttribute ) { // Setting style.filter to null, "" & " " still leave "filter:" in the cssText // if "filter:" is present at all, clearType is disabled, we want to avoid this @@ -6681,170 +7087,195 @@ if ( !jQuery.support.opacity ) { }; } +// These hooks cannot be added until DOM ready because the support test +// for it is not run until after DOM ready jQuery(function() { - // This hook cannot be added until DOM ready because the support test - // for it is not run until after DOM ready if ( !jQuery.support.reliableMarginRight ) { jQuery.cssHooks.marginRight = { get: function( elem, computed ) { // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right // Work around by temporarily setting element display to inline-block - var ret; - jQuery.swap( elem, { "display": "inline-block" }, function() { + return jQuery.swap( elem, { "display": "inline-block" }, function() { if ( computed ) { - ret = curCSS( elem, "margin-right", "marginRight" ); - } else { - ret = elem.style.marginRight; + return curCSS( elem, "marginRight" ); } }); - return ret; } }; } -}); -if ( document.defaultView && document.defaultView.getComputedStyle ) { - getComputedStyle = function( elem, name ) { - var ret, defaultView, computedStyle; + // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 + // getComputedStyle returns percent when specified for top/left/bottom/right + // rather than make the css module depend on the offset module, we just check for it here + if ( !jQuery.support.pixelPosition && jQuery.fn.position ) { + jQuery.each( [ "top", "left" ], function( i, prop ) { + jQuery.cssHooks[ prop ] = { + get: function( elem, computed ) { + if ( computed ) { + var ret = curCSS( elem, prop ); + // if curCSS returns percentage, fallback to offset + return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret; + } + } + }; + }); + } - name = name.replace( rupper, "-$1" ).toLowerCase(); +}); - if ( (defaultView = elem.ownerDocument.defaultView) && - (computedStyle = defaultView.getComputedStyle( elem, null )) ) { - ret = computedStyle.getPropertyValue( name ); - if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { - ret = jQuery.style( elem, name ); - } - } +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none"); + }; - return ret; + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); }; } -if ( document.documentElement.currentStyle ) { - currentStyle = function( elem, name ) { - var left, rsLeft, uncomputed, - ret = elem.currentStyle && elem.currentStyle[ name ], - style = elem.style; - - // Avoid setting ret to empty string here - // so we don't default to auto - if ( ret === null && style && (uncomputed = style[ name ]) ) { - ret = uncomputed; - } - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { +// These hooks are used by animate to expand properties +jQuery.each({ + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i, - // Remember the original values - left = style.left; - rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + // assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [ value ], + expanded = {}; - // Put in the new values to get a computed value out - if ( rsLeft ) { - elem.runtimeStyle.left = elem.currentStyle.left; + for ( i = 0; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } - style.left = name === "fontSize" ? "1em" : ( ret || 0 ); - ret = style.pixelLeft + "px"; - // Revert the changed values - style.left = left; - if ( rsLeft ) { - elem.runtimeStyle.left = rsLeft; - } + return expanded; } - - return ret === "" ? "auto" : ret; }; -} -curCSS = getComputedStyle || currentStyle; + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +}); +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rselectTextarea = /^(?:select|textarea)/i; -function getWH( elem, name, extra ) { +jQuery.fn.extend({ + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); - // Start with offset property - var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - which = name === "width" ? cssWidth : cssHeight, - i = 0, - len = which.length; + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); - if ( val > 0 ) { - if ( extra !== "border" ) { - for ( ; i < len; i++ ) { - if ( !extra ) { - val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; - } - if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; - } else { - val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; - } - } - } +//Serialize an array of form elements or a set of +//key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; - return val + "px"; + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; } - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, name ); - if ( val < 0 || val == null ) { - val = elem.style[ name ] || 0; - } - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); - // Add padding, border, margin - if ( extra ) { - for ( ; i < len; i++ ) { - val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; - if ( extra !== "padding" ) { - val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; - } - if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; - } + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); } } - return val + "px"; -} - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.hidden = function( elem ) { - var width = elem.offsetWidth, - height = elem.offsetHeight; + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); +}; - return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); - }; +function buildParams( prefix, obj, traditional, add ) { + var name; - jQuery.expr.filters.visible = function( elem ) { - return !jQuery.expr.filters.hidden( elem ); - }; -} + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); + } + }); + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} +var + // Document location + ajaxLocParts, + ajaxLocation, -var r20 = /%20/g, - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, rhash = /#.*$/, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL - rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, rquery = /\?/, rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, - rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, - rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, // Keep a copy of the old load method _load = jQuery.fn.load, @@ -6867,12 +7298,6 @@ var r20 = /%20/g, */ transports = {}, - // Document location - ajaxLocation, - - // Document location segments - ajaxLocParts, - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = ["*/"] + ["*"]; @@ -6902,14 +7327,12 @@ function addToPrefiltersOrTransports( structure ) { dataTypeExpression = "*"; } - if ( jQuery.isFunction( func ) ) { - var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), - i = 0, - length = dataTypes.length, - dataType, - list, - placeBefore; + var dataType, list, placeBefore, + dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ), + i = 0, + length = dataTypes.length; + if ( jQuery.isFunction( func ) ) { // For each dataType in the dataTypeExpression for ( ; i < length; i++ ) { dataType = dataTypes[ i ]; @@ -6936,11 +7359,11 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX inspected[ dataType ] = true; - var list = structure[ dataType ], + var selection, + list = structure[ dataType ], i = 0, length = list ? list.length : 0, - executeOnly = ( structure === prefilters ), - selection; + executeOnly = ( structure === prefilters ); for ( ; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jqXHR ); @@ -6983,109 +7406,75 @@ function ajaxExtend( target, src ) { } } -jQuery.fn.extend({ - load: function( url, params, callback ) { - if ( typeof url !== "string" && _load ) { - return _load.apply( this, arguments ); +jQuery.fn.load = function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + } - // Don't do a request if no elements are being requested - } else if ( !this.length ) { - return this; - } + // Don't do a request if no elements are being requested + if ( !this.length ) { + return this; + } - var off = url.indexOf( " " ); - if ( off >= 0 ) { - var selector = url.slice( off, url.length ); - url = url.slice( 0, off ); - } + var selector, type, response, + self = this, + off = url.indexOf(" "); + + if ( off >= 0 ) { + selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; - // Default to a GET request - var type = "GET"; + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } - // If the second parameter was provided - if ( params ) { - // If it's a function - if ( jQuery.isFunction( params ) ) { - // We assume that it's the callback - callback = params; - params = undefined; + // Request the remote document + jQuery.ajax({ + url: url, - // Otherwise, build a param string - } else if ( typeof params === "object" ) { - params = jQuery.param( params, jQuery.ajaxSettings.traditional ); - type = "POST"; + // if "type" variable is undefined, then "GET" method will be used + type: type, + dataType: "html", + data: params, + complete: function( jqXHR, status ) { + if ( callback ) { + self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); } } + }).done(function( responseText ) { - var self = this; + // Save response for use in complete callback + response = arguments; - // Request the remote document - jQuery.ajax({ - url: url, - type: type, - dataType: "html", - data: params, - // Complete callback (responseText is used internally) - complete: function( jqXHR, status, responseText ) { - // Store the response as specified by the jqXHR object - responseText = jqXHR.responseText; - // If successful, inject the HTML into all the matched elements - if ( jqXHR.isResolved() ) { - // #4825: Get the actual response in case - // a dataFilter is present in ajaxSettings - jqXHR.done(function( r ) { - responseText = r; - }); - // See if a selector was specified - self.html( selector ? - // Create a dummy div to hold the results - jQuery("<div>") - // inject the contents of the document in, removing the scripts - // to avoid any 'Permission Denied' errors in IE - .append(responseText.replace(rscript, "")) - - // Locate the specified elements - .find(selector) : - - // If not, just inject the full result - responseText ); - } + // See if a selector was specified + self.html( selector ? - if ( callback ) { - self.each( callback, [ responseText, status, jqXHR ] ); - } - } - }); + // Create a dummy div to hold the results + jQuery("<div>") - return this; - }, + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append( responseText.replace( rscript, "" ) ) - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, + // Locate the specified elements + .find( selector ) : - serializeArray: function() { - return this.map(function(){ - return this.elements ? jQuery.makeArray( this.elements ) : this; - }) - .filter(function(){ - return this.name && !this.disabled && - ( this.checked || rselectTextarea.test( this.nodeName ) || - rinput.test( this.type ) ); - }) - .map(function( i, elem ){ - var val = jQuery( this ).val(); + // If not, just inject the full result + responseText ); - return val == null ? - null : - jQuery.isArray( val ) ? - jQuery.map( val, function( val, i ){ - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }) : - { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }).get(); - } -}); + }); + + return this; +}; // Attach a bunch of functions for handling common AJAX events jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ @@ -7144,7 +7533,7 @@ jQuery.extend({ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, type: "GET", - contentType: "application/x-www-form-urlencoded", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", processData: true, async: true, /* @@ -7154,6 +7543,7 @@ jQuery.extend({ username: null, password: null, cache: null, + throws: false, traditional: false, headers: {}, */ @@ -7220,7 +7610,22 @@ jQuery.extend({ // Force options to be an object options = options || {}; - var // Create the final options object + var // ifModified key + ifModifiedKey, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, @@ -7235,26 +7640,13 @@ jQuery.extend({ completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, - // ifModified key - ifModifiedKey, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, - // Response headers - responseHeadersString, - responseHeaders, - // transport - transport, - // timeout handle - timeoutTimer, - // Cross-domain detection vars - parts, // The jqXHR state state = 0, - // To know if global events are to be dispatched - fireGlobals, - // Loop variable - i, + // Default abort message + strAbort = "canceled", // Fake xhr jqXHR = { @@ -7300,7 +7692,7 @@ jQuery.extend({ // Cancel the request abort: function( statusText ) { - statusText = statusText || "abort"; + statusText = statusText || strAbort; if ( transport ) { transport.abort( statusText ); } @@ -7313,6 +7705,8 @@ jQuery.extend({ // It is defined here because jslint complains if it is declared // at the end of the function (which would be more logical and readable) function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; // Called once if ( state === 2 ) { @@ -7337,13 +7731,10 @@ jQuery.extend({ // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; - var isSuccess, - success, - error, - statusText = nativeStatusText, - response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, - lastModified, - etag; + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } // If successful, handle type chaining if ( status >= 200 && status < 300 || status === 304 ) { @@ -7351,11 +7742,13 @@ jQuery.extend({ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { - if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { - jQuery.lastModified[ ifModifiedKey ] = lastModified; + modified = jqXHR.getResponseHeader("Last-Modified"); + if ( modified ) { + jQuery.lastModified[ ifModifiedKey ] = modified; } - if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { - jQuery.etag[ ifModifiedKey ] = etag; + modified = jqXHR.getResponseHeader("Etag"); + if ( modified ) { + jQuery.etag[ ifModifiedKey ] = modified; } } @@ -7368,15 +7761,11 @@ jQuery.extend({ // If we have data } else { - try { - success = ajaxConvert( s, response ); - statusText = "success"; - isSuccess = true; - } catch(e) { - // We have a parsererror - statusText = "parsererror"; - error = e; - } + isSuccess = ajaxConvert( s, response ); + statusText = isSuccess.state; + success = isSuccess.data; + error = isSuccess.error; + isSuccess = !error; } } else { // We extract error from statusText @@ -7392,7 +7781,7 @@ jQuery.extend({ // Set data for the fake xhr object jqXHR.status = status; - jqXHR.statusText = "" + ( nativeStatusText || statusText ); + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // Success/Error if ( isSuccess ) { @@ -7438,7 +7827,7 @@ jQuery.extend({ } } else { tmp = map[ jqXHR.status ]; - jqXHR.then( tmp, tmp ); + jqXHR.always( tmp ); } } return this; @@ -7450,16 +7839,13 @@ jQuery.extend({ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); // Extract dataTypes list - s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace ); - // Determine if a cross-domain request is in order + // A cross-domain request is in order when we have a protocol:host:port mismatch if ( s.crossDomain == null ) { - parts = rurl.exec( s.url.toLowerCase() ); - s.crossDomain = !!( parts && - ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || - ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != - ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) - ); + parts = rurl.exec( s.url.toLowerCase() ) || false; + s.crossDomain = parts && ( parts.join(":") + ( parts[ 3 ] ? "" : parts[ 1 ] === "http:" ? 80 : 443 ) ) !== + ( ajaxLocParts.join(":") + ( ajaxLocParts[ 3 ] ? "" : ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ); } // Convert data if not already a string @@ -7470,9 +7856,9 @@ jQuery.extend({ // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - // If request was aborted inside a prefiler, stop there + // If request was aborted inside a prefilter, stop there if ( state === 2 ) { - return false; + return jqXHR; } // We can fire global events as of now if asked to @@ -7545,12 +7931,14 @@ jQuery.extend({ // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { - // Abort if not done already - jqXHR.abort(); - return false; + // Abort if not done already and return + return jqXHR.abort(); } + // aborting is no longer a cancellation + strAbort = "abort"; + // Install callbacks on deferreds for ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] ); @@ -7592,77 +7980,6 @@ jQuery.extend({ return jqXHR; }, - // Serialize an array of form elements or a set of - // key/values into a query string - param: function( a, traditional ) { - var s = [], - add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction( value ) ? value() : value; - s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); - }; - - // Set traditional to true for jQuery <= 1.3.2 behavior. - if ( traditional === undefined ) { - traditional = jQuery.ajaxSettings.traditional; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - }); - - } else { - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( var prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ).replace( r20, "+" ); - } -}); - -function buildParams( prefix, obj, traditional, add ) { - if ( jQuery.isArray( obj ) ) { - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - // If array item is non-scalar (array or object), encode its - // numeric index to resolve deserialization ambiguity issues. - // Note that rack (as of 1.0.0) can't currently deserialize - // nested arrays properly, and attempting to do so may cause - // a server error. Possible fixes are to modify rack's - // deserialization algorithm or to provide an option or flag - // to force array serialization to be shallow. - buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); - } - }); - - } else if ( !traditional && obj != null && typeof obj === "object" ) { - // Serialize object item. - for ( var name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - // Serialize scalar item. - add( prefix, obj ); - } -} - -// This is still on the jQuery object... for now -// Want to move this to jQuery.ajax some day -jQuery.extend({ - // Counter for holding the number of active queries active: 0, @@ -7679,13 +7996,10 @@ jQuery.extend({ */ function ajaxHandleResponses( s, jqXHR, responses ) { - var contents = s.contents, + var ct, type, finalDataType, firstDataType, + contents = s.contents, dataTypes = s.dataTypes, - responseFields = s.responseFields, - ct, - type, - finalDataType, - firstDataType; + responseFields = s.responseFields; // Fill responseXXX fields for ( type in responseFields ) { @@ -7744,155 +8058,137 @@ function ajaxHandleResponses( s, jqXHR, responses ) { // Chain conversions given the request and the original response function ajaxConvert( s, response ) { + var conv, conv2, current, tmp, + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(), + prev = dataTypes[ 0 ], + converters = {}, + i = 0; + // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } - var dataTypes = s.dataTypes, - converters = {}, - i, - key, - length = dataTypes.length, - tmp, - // Current and previous dataTypes - current = dataTypes[ 0 ], - prev, - // Conversion expression - conversion, - // Conversion function - conv, - // Conversion functions (transitive conversion) - conv1, - conv2; - - // For each dataType in the chain - for ( i = 1; i < length; i++ ) { - - // Create converters map - // with lowercased keys - if ( i === 1 ) { - for ( key in s.converters ) { - if ( typeof key === "string" ) { - converters[ key.toLowerCase() ] = s.converters[ key ]; - } - } + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; } + } + + // Convert to each sequential dataType, tolerating list modification + for ( ; (current = dataTypes[++i]); ) { + + // There's only work to do if current dataType is non-auto + if ( current !== "*" ) { + + // Convert response if prev dataType is non-auto and differs from current + if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; - // Get the dataTypes - prev = current; - current = dataTypes[ i ]; - - // If current is auto dataType, update it to prev - if ( current === "*" ) { - current = prev; - // If no auto and dataTypes are actually different - } else if ( prev !== "*" && prev !== current ) { - - // Get the converter - conversion = prev + " " + current; - conv = converters[ conversion ] || converters[ "* " + current ]; - - // If there is no direct converter, search transitively - if ( !conv ) { - conv2 = undefined; - for ( conv1 in converters ) { - tmp = conv1.split( " " ); - if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { - conv2 = converters[ tmp[1] + " " + current ]; - if ( conv2 ) { - conv1 = converters[ conv1 ]; - if ( conv1 === true ) { - conv = conv2; - } else if ( conv2 === true ) { - conv = conv1; + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.splice( i--, 0, current ); + } + + break; } - break; } } } - } - // If we found no converter, dispatch an error - if ( !( conv || conv2 ) ) { - jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); - } - // If found converter is not an equivalence - if ( conv !== true ) { - // Convert with 1 or 2 converters accordingly - response = conv ? conv( response ) : conv2( conv1(response) ); - } - } - } - return response; -} + // Apply converter (if not an equivalence) + if ( conv !== true ) { + // Unless errors are allowed to bubble, catch and return them + if ( conv && s["throws"] ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; + } + } + } + } + // Update prev for next iteration + prev = current; + } + } -var jsc = jQuery.now(), - jsre = /(\=)\?(&|$)|\?\?/i; + return { state: "success", data: response }; +} +var oldCallbacks = [], + rquestion = /\?/, + rjsonp = /(=)\?(?=&|$)|\?\?/, + nonce = jQuery.now(); // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { - return jQuery.expando + "_" + ( jsc++ ); + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + this[ callback ] = true; + return callback; } }); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { - var inspectData = s.contentType === "application/x-www-form-urlencoded" && - ( typeof s.data === "string" ); - - if ( s.dataTypes[ 0 ] === "jsonp" || - s.jsonp !== false && ( jsre.test( s.url ) || - inspectData && jsre.test( s.data ) ) ) { - - var responseContainer, - jsonpCallback = s.jsonpCallback = - jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, - previous = window[ jsonpCallback ], - url = s.url, - data = s.data, - replace = "$1" + jsonpCallback + "$2"; - - if ( s.jsonp !== false ) { - url = url.replace( jsre, replace ); - if ( s.url === url ) { - if ( inspectData ) { - data = data.replace( jsre, replace ); - } - if ( s.data === data ) { - // Add callback manually - url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; - } - } + var callbackName, overwritten, responseContainer, + data = s.data, + url = s.url, + hasCallback = s.jsonp !== false, + replaceInUrl = hasCallback && rjsonp.test( url ), + replaceInData = hasCallback && !replaceInUrl && typeof data === "string" && + !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && + rjsonp.test( data ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + overwritten = window[ callbackName ]; + + // Insert callback into url or form data + if ( replaceInUrl ) { + s.url = url.replace( rjsonp, "$1" + callbackName ); + } else if ( replaceInData ) { + s.data = data.replace( rjsonp, "$1" + callbackName ); + } else if ( hasCallback ) { + s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; } - s.url = url; - s.data = data; - - // Install callback - window[ jsonpCallback ] = function( response ) { - responseContainer = [ response ]; - }; - - // Clean-up function - jqXHR.always(function() { - // Set callback back to previous value - window[ jsonpCallback ] = previous; - // Call if it was a function and we have a response - if ( responseContainer && jQuery.isFunction( previous ) ) { - window[ jsonpCallback ]( responseContainer[ 0 ] ); - } - }); - // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if ( !responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); + jQuery.error( callbackName + " was not called" ); } return responseContainer[ 0 ]; }; @@ -7900,14 +8196,37 @@ jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { // force json dataType s.dataTypes[ 0 ] = "json"; - // Delegate to script - return "script"; - } -}); + // Install callback + window[ callbackName ] = function() { + responseContainer = arguments; + }; + // Clean-up function (fires after converters) + jqXHR.always(function() { + // Restore preexisting value + window[ callbackName ] = overwritten; + + // Save back as free + if ( s[ callbackName ] ) { + // make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // save the callback name for future use + oldCallbacks.push( callbackName ); + } + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + responseContainer = overwritten = undefined; + }); + // Delegate to script + return "script"; + } +}); // Install script dataType jQuery.ajaxSetup({ accepts: { @@ -7993,19 +8312,15 @@ jQuery.ajaxTransport( "script", function(s) { }; } }); - - - - -var // #5280: Internet Explorer will keep connections alive if we don't abort on unload +var xhrCallbacks, + // #5280: Internet Explorer will keep connections alive if we don't abort on unload xhrOnUnloadAbort = window.ActiveXObject ? function() { // Abort all pending requests for ( var key in xhrCallbacks ) { xhrCallbacks[ key ]( 0, 1 ); } } : false, - xhrId = 0, - xhrCallbacks; + xhrId = 0; // Functions to create xhrs function createStandardXHR() { @@ -8056,9 +8371,8 @@ if ( jQuery.support.ajax ) { send: function( headers, complete ) { // Get a new xhr - var xhr = s.xhr(), - handle, - i; + var handle, i, + xhr = s.xhr(); // Open the socket // Passing null username, generates a login popup on Opera (#2865) @@ -8111,7 +8425,7 @@ if ( jQuery.support.ajax ) { xml; // Firefox throws exceptions when accessing properties - // of an xhr when a network error occured + // of an xhr when a network error occurred // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) try { @@ -8145,7 +8459,13 @@ if ( jQuery.support.ajax ) { if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } - responses.text = xhr.responseText; + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + try { + responses.text = xhr.responseText; + } catch( _ ) { + } // Firefox throws an exception when accessing // statusText for faulty cross-domain requests @@ -8181,11 +8501,13 @@ if ( jQuery.support.ajax ) { } }; - // if we're in sync mode or it's in cache - // and has been retrieved directly (IE6 & IE7) - // we need to manually fire the callback - if ( !s.async || xhr.readyState === 4 ) { + if ( !s.async ) { + // if we're in sync mode we fire the callback callback(); + } else if ( xhr.readyState === 4 ) { + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + setTimeout( callback, 0 ); } else { handle = ++xhrId; if ( xhrOnUnloadAbort ) { @@ -8211,264 +8533,494 @@ if ( jQuery.support.ajax ) { } }); } - - - - -var elemdisplay = {}, - iframe, iframeDoc, +var fxNow, timerId, rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, - timerId, - fxAttrs = [ - // height animations - [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], - // width animations - [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], - // opacity animations - [ "opacity" ] - ], - fxNow; - -jQuery.fn.extend({ - show: function( speed, easing, callback ) { - var elem, display; - - if ( speed || speed === 0 ) { - return this.animate( genFx("show", 3), speed, easing, callback ); - - } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - elem = this[ i ]; + rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), + rrun = /queueHooks$/, + animationPrefilters = [ defaultPrefilter ], + tweeners = { + "*": [function( prop, value ) { + var end, unit, + tween = this.createTween( prop, value ), + parts = rfxnum.exec( value ), + target = tween.cur(), + start = +target || 0, + scale = 1, + maxIterations = 20; + + if ( parts ) { + end = +parts[2]; + unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" && start ) { + // Iteratively approximate from a nonzero starting point + // Prefer the current property, because this process will be trivial if it uses the same units + // Fallback to end or a simple constant + start = jQuery.css( tween.elem, prop, true ) || end || 1; + + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); + } + + tween.unit = unit; + tween.start = start; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + } + return tween; + }] + }; - if ( elem.style ) { - display = elem.style.display; +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout(function() { + fxNow = undefined; + }, 0 ); + return ( fxNow = jQuery.now() ); +} - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { - display = elem.style.display = ""; - } +function createTweens( animation, props ) { + jQuery.each( props, function( prop, value ) { + var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( collection[ index ].call( animation, prop, value ) ) { - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( display === "" && jQuery.css(elem, "display") === "none" ) { - jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) ); - } - } + // we're done with this property + return; } + } + }); +} - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - elem = this[ i ]; +function Animation( elem, properties, options ) { + var result, + index = 0, + tweenerIndex = 0, + length = animationPrefilters.length, + deferred = jQuery.Deferred().always( function() { + // don't match elem in the :animated selector + delete tick.elem; + }), + tick = function() { + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + percent = 1 - ( remaining / animation.duration || 0 ), + index = 0, + length = animation.tweens.length; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ]); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { specialEasing: {} }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end, easing ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + // if we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; - if ( elem.style ) { - display = elem.style.display; + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( 1 ); + } - if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; - } + // resolve when we played the last frame + // otherwise, reject + if ( gotoEnd ) { + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); } + return this; } + }), + props = animation.props; - return this; + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length ; index++ ) { + result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + return result; } - }, + } - hide: function( speed, easing, callback ) { - if ( speed || speed === 0 ) { - return this.animate( genFx("hide", 3), speed, easing, callback); + createTweens( animation, props ); - } else { - var elem, display, - i = 0, - j = this.length; + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } - for ( ; i < j; i++ ) { - elem = this[i]; - if ( elem.style ) { - display = jQuery.css( elem, "display" ); + jQuery.fx.timer( + jQuery.extend( tick, { + anim: animation, + queue: animation.opts.queue, + elem: elem + }) + ); - if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { - jQuery._data( elem, "olddisplay", display ); - } - } - } + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} - // Set the display of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - if ( this[i].style ) { - this[i].style.display = "none"; - } - } +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; - return this; + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; } - }, - // Save the old toggle function - _toggle: jQuery.fn.toggle, + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } - toggle: function( fn, fn2, callback ) { - var bool = typeof fn === "boolean"; + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; - if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { - this._toggle.apply( this, arguments ); + // not quite $.extend, this wont overwrite keys already present. + // also - reusing 'index' from above because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} - } else if ( fn == null || bool ) { - this.each(function() { - var state = bool ? fn : jQuery(this).is(":hidden"); - jQuery(this)[ state ? "show" : "hide" ](); - }); +jQuery.Animation = jQuery.extend( Animation, { + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; } else { - this.animate(genFx("toggle", 3), fn, fn2, callback); + props = props.split(" "); } - return this; - }, + var prop, + index = 0, + length = props.length; - fadeTo: function( speed, to, easing, callback ) { - return this.filter(":hidden").css("opacity", 0).show().end() - .animate({opacity: to}, speed, easing, callback); + for ( ; index < length ; index++ ) { + prop = props[ index ]; + tweeners[ prop ] = tweeners[ prop ] || []; + tweeners[ prop ].unshift( callback ); + } }, - animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed( speed, easing, callback ); + prefilter: function( callback, prepend ) { + if ( prepend ) { + animationPrefilters.unshift( callback ); + } else { + animationPrefilters.push( callback ); + } + } +}); - if ( jQuery.isEmptyObject( prop ) ) { - return this.each( optall.complete, [ false ] ); +function defaultPrefilter( elem, props, opts ) { + var index, prop, value, length, dataShow, tween, hooks, oldfire, + anim = this, + style = elem.style, + orig = {}, + handled = [], + hidden = elem.nodeType && isHidden( elem ); + + // handle queue: false promises + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; } + hooks.unqueued++; + + anim.always(function() { + // doing this makes sure that the complete handler will be called + // before this completes + anim.always(function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + }); + }); + } + + // height/width overflow pass + if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - // Do not change referenced properties as per-property easing will be lost - prop = jQuery.extend( {}, prop ); + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( elem, "display" ) === "inline" && + jQuery.css( elem, "float" ) === "none" ) { - function doAnimation() { - // XXX 'this' does not always have a nodeName when running the - // test suite + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { + style.display = "inline-block"; - if ( optall.queue === false ) { - jQuery._mark( this ); + } else { + style.zoom = 1; } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + if ( !jQuery.support.shrinkWrapBlocks ) { + anim.done(function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + }); + } + } - var opt = jQuery.extend( {}, optall ), - isElement = this.nodeType === 1, - hidden = isElement && jQuery(this).is(":hidden"), - name, val, p, e, - parts, start, end, unit, - method; - // will store per property easing and be used to determine when an animation is complete - opt.animatedProperties = {}; + // show/hide pass + for ( index in props ) { + value = props[ index ]; + if ( rfxtypes.exec( value ) ) { + delete props[ index ]; + if ( value === ( hidden ? "hide" : "show" ) ) { + continue; + } + handled.push( index ); + } + } - for ( p in prop ) { + length = handled.length; + if ( length ) { + dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( hidden ) { + jQuery( elem ).show(); + } else { + anim.done(function() { + jQuery( elem ).hide(); + }); + } + anim.done(function() { + var prop; + jQuery.removeData( elem, "fxshow", true ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + }); + for ( index = 0 ; index < length ; index++ ) { + prop = handled[ index ]; + tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); + orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); - // property name normalization - name = jQuery.camelCase( p ); - if ( p !== name ) { - prop[ name ] = prop[ p ]; - delete prop[ p ]; + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = tween.start; + if ( hidden ) { + tween.end = tween.start; + tween.start = prop === "width" || prop === "height" ? 1 : 0; } + } + } + } +} - val = prop[ name ]; +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; - // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) - if ( jQuery.isArray( val ) ) { - opt.animatedProperties[ name ] = val[ 1 ]; - val = prop[ name ] = val[ 0 ]; - } else { - opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; - } +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || "swing"; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; - if ( val === "hide" && hidden || val === "show" && !hidden ) { - return opt.complete.call( this ); - } + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } - if ( isElement && ( name === "height" || name === "width" ) ) { - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE does not - // change the overflow attribute when overflowX and - // overflowY are set to the same value - opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height animated - if ( jQuery.css( this, "display" ) === "inline" && - jQuery.css( this, "float" ) === "none" ) { +Tween.prototype.init.prototype = Tween.prototype; - // inline-level elements accept inline-block; - // block-level elements need to be inline with layout - if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { - this.style.display = "inline-block"; +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; - } else { - this.style.zoom = 1; - } - } - } + if ( tween.elem[ tween.prop ] != null && + (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { + return tween.elem[ tween.prop ]; } - if ( opt.overflow != null ) { - this.style.overflow = "hidden"; + // passing any value as a 4th parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails + // so, simple values such as "10px" are parsed to Float. + // complex values such as "rotate(1rad)" are returned as is. + result = jQuery.css( tween.elem, tween.prop, false, "" ); + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + // use step hook for back compat - use cssHook if its there - use .style if its + // available and use plain properties where available + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; } + } + } +}; - for ( p in prop ) { - e = new jQuery.fx( this, opt, p ); - val = prop[ p ]; +// Remove in 2.0 - this supports IE8's panic based approach +// to setting things on disconnected nodes - if ( rfxtypes.test( val ) ) { +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; - // Tracks whether to show or hide based on private - // data attached to the element - method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); - if ( method ) { - jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); - e[ method ](); - } else { - e[ val ](); - } +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" || + // special check for .toggle( handler, handler, ... ) + ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +}); - } else { - parts = rfxnum.exec( val ); - start = e.cur(); - - if ( parts ) { - end = parseFloat( parts[2] ); - unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); - - // We need to compute starting value - if ( unit !== "px" ) { - jQuery.style( this, p, (end || 1) + unit); - start = ( (end || 1) / e.cur() ) * start; - jQuery.style( this, p, start + unit); - } +jQuery.fn.extend({ + fadeTo: function( speed, to, easing, callback ) { - // If a +=/-= token was provided, we're doing a relative animation - if ( parts[1] ) { - end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; - } + // show any hidden elements after setting opacity to 0 + return this.filter( isHidden ).css( "opacity", 0 ).show() - e.custom( start, end, unit ); + // animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - } else { - e.custom( start, val, "" ); - } + // Empty animations resolve immediately + if ( empty ) { + anim.stop( true ); } - } - - // For JS strict compliance - return true; - } + }; - return optall.queue === false ? + return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, - stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; @@ -8479,42 +9031,27 @@ jQuery.fn.extend({ } return this.each(function() { - var index, - hadTimers = false, + var dequeue = true, + index = type != null && type + "queueHooks", timers = jQuery.timers, data = jQuery._data( this ); - // clear marker counters if we know they won't be - if ( !gotoEnd ) { - jQuery._unmark( true, this ); - } - - function stopQueue( elem, data, index ) { - var hooks = data[ index ]; - jQuery.removeData( elem, index, true ); - hooks.stop( gotoEnd ); - } - - if ( type == null ) { + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { for ( index in data ) { - if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { - stopQueue( this, data, index ); + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); } } - } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ - stopQueue( this, data, index ); } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { - if ( gotoEnd ) { - - // force the next step to be the last - timers[ index ]( true ); - } else { - timers[ index ].saveState(); - } - hadTimers = true; + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; timers.splice( index, 1 ); } } @@ -8522,40 +9059,39 @@ jQuery.fn.extend({ // start the next in the queue if the last step wasn't forced // timers currently will call their complete callbacks, which will dequeue // but only if they were gotoEnd - if ( !( gotoEnd && hadTimers ) ) { + if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } }); } - }); -// Animations created synchronously will run synchronously -function createFxNow() { - setTimeout( clearFxNow, 0 ); - return ( fxNow = jQuery.now() ); -} - -function clearFxNow() { - fxNow = undefined; -} - // Generate parameters to create a standard animation -function genFx( type, num ) { - var obj = {}; +function genFx( type, includeWidth ) { + var which, + attrs = { height: type }, + i = 0; + + // if we include width, step value is 1 to do all cssExpand values, + // if we don't include width, step value is 2 to skip over Left and Right + includeWidth = includeWidth? 1 : 0; + for( ; i < 4 ; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { - obj[ this ] = type; - }); + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } - return obj; + return attrs; } // Generate shortcuts for custom animations jQuery.each({ - slideDown: genFx( "show", 1 ), - slideUp: genFx( "hide", 1 ), - slideToggle: genFx( "toggle", 1 ), + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } @@ -8565,279 +9101,89 @@ jQuery.each({ }; }); -jQuery.extend({ - speed: function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : - opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; - - // normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function( noUnmark ) { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } else if ( noUnmark !== false ) { - jQuery._unmark( this ); - } - }; - - return opt; - }, - - easing: { - linear: function( p, n, firstNum, diff ) { - return firstNum + diff * p; - }, - swing: function( p, n, firstNum, diff ) { - return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; - } - }, - - timers: [], +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; - fx: function( elem, options, prop ) { - this.options = options; - this.elem = elem; - this.prop = prop; + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; - options.orig = options.orig || {}; + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; } -}); + // Queueing + opt.old = opt.complete; -jQuery.fx.prototype = { - // Simple function for setting a style value - update: function() { - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); } - ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); - }, - - // Get the current size - cur: function() { - if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { - return this.elem[ this.prop ]; - } - - var parsed, - r = jQuery.css( this.elem, this.prop ); - // Empty strings, null, undefined and "auto" are converted to 0, - // complex values such as "rotate(1rad)" are returned as is, - // simple values such as "10px" are parsed to Float. - return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; - }, - - // Start an animation from one number to another - custom: function( from, to, unit ) { - var self = this, - fx = jQuery.fx; - - this.startTime = fxNow || createFxNow(); - this.end = to; - this.now = this.start = from; - this.pos = this.state = 0; - this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); - - function t( gotoEnd ) { - return self.step( gotoEnd ); - } - - t.queue = this.options.queue; - t.elem = this.elem; - t.saveState = function() { - if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { - jQuery._data( self.elem, "fxshow" + self.prop, self.start ); - } - }; - - if ( t() && jQuery.timers.push(t) && !timerId ) { - timerId = setInterval( fx.tick, fx.interval ); - } - }, - - // Simple 'show' function - show: function() { - var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); - - // Remember where we started, so that we can go back to it later - this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop ); - this.options.show = true; - - // Begin the animation - // Make sure that we start at a small width/height to avoid any flash of content - if ( dataShow !== undefined ) { - // This show is picking up where a previous hide or show left off - this.custom( this.cur(), dataShow ); - } else { - this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); } + }; - // Start by showing the element - jQuery( this.elem ).show(); - }, - - // Simple 'hide' function - hide: function() { - // Remember where we started, so that we can go back to it later - this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); - this.options.hide = true; + return opt; +}; - // Begin the animation - this.custom( this.cur(), 0 ); +jQuery.easing = { + linear: function( p ) { + return p; }, - - // Each step of an animation - step: function( gotoEnd ) { - var p, n, complete, - t = fxNow || createFxNow(), - done = true, - elem = this.elem, - options = this.options; - - if ( gotoEnd || t >= options.duration + this.startTime ) { - this.now = this.end; - this.pos = this.state = 1; - this.update(); - - options.animatedProperties[ this.prop ] = true; - - for ( p in options.animatedProperties ) { - if ( options.animatedProperties[ p ] !== true ) { - done = false; - } - } - - if ( done ) { - // Reset the overflow - if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - - jQuery.each( [ "", "X", "Y" ], function( index, value ) { - elem.style[ "overflow" + value ] = options.overflow[ index ]; - }); - } - - // Hide the element if the "hide" operation was done - if ( options.hide ) { - jQuery( elem ).hide(); - } - - // Reset the properties, if the item has been hidden or shown - if ( options.hide || options.show ) { - for ( p in options.animatedProperties ) { - jQuery.style( elem, p, options.orig[ p ] ); - jQuery.removeData( elem, "fxshow" + p, true ); - // Toggle data is no longer needed - jQuery.removeData( elem, "toggle" + p, true ); - } - } - - // Execute the complete function - // in the event that the complete function throws an exception - // we must ensure it won't be called twice. #5684 - - complete = options.complete; - if ( complete ) { - - options.complete = false; - complete.call( elem ); - } - } - - return false; - - } else { - // classical easing cannot be used with an Infinity duration - if ( options.duration == Infinity ) { - this.now = t; - } else { - n = t - this.startTime; - this.state = n / options.duration; - - // Perform the easing function, defaults to swing - this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); - this.now = this.start + ( (this.end - this.start) * this.pos ); - } - // Perform the next step of the animation - this.update(); - } - - return true; + swing: function( p ) { + return 0.5 - Math.cos( p*Math.PI ) / 2; } }; -jQuery.extend( jQuery.fx, { - tick: function() { - var timer, - timers = jQuery.timers, - i = 0; - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - // Checks the timer has not already been removed - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } +jQuery.timers = []; +jQuery.fx = Tween.prototype.init; +jQuery.fx.tick = function() { + var timer, + timers = jQuery.timers, + i = 0; - if ( !timers.length ) { - jQuery.fx.stop(); + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); } - }, + } - interval: 13, + if ( !timers.length ) { + jQuery.fx.stop(); + } +}; - stop: function() { - clearInterval( timerId ); - timerId = null; - }, +jQuery.fx.timer = function( timer ) { + if ( timer() && jQuery.timers.push( timer ) && !timerId ) { + timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; - speeds: { - slow: 600, - fast: 200, - // Default speed - _default: 400 - }, +jQuery.fx.interval = 13; - step: { - opacity: function( fx ) { - jQuery.style( fx.elem, "opacity", fx.now ); - }, +jQuery.fx.stop = function() { + clearInterval( timerId ); + timerId = null; +}; - _default: function( fx ) { - if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { - fx.elem.style[ fx.prop ] = fx.now + fx.unit; - } else { - fx.elem[ fx.prop ] = fx.now; - } - } - } -}); +jQuery.fx.speeds = { + slow: 600, + fast: 200, + // Default speed + _default: 400 +}; -// Adds width/height step functions -// Do not set anything below 0 -jQuery.each([ "width", "height" ], function( i, prop ) { - jQuery.fx.step[ prop ] = function( fx ) { - jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); - }; -}); +// Back Compat <1.8 extension point +jQuery.fx.step = {}; if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { @@ -8846,172 +9192,52 @@ if ( jQuery.expr && jQuery.expr.filters ) { }).length; }; } +var rroot = /^(?:body|html)$/i; -// Try to restore the default display value of an element -function defaultDisplay( nodeName ) { - - if ( !elemdisplay[ nodeName ] ) { - - var body = document.body, - elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), - display = elem.css( "display" ); - elem.remove(); - - // If the simple way fails, - // get element's real default display by attaching it to a temp iframe - if ( display === "none" || display === "" ) { - // No iframe to use yet, so create it - if ( !iframe ) { - iframe = document.createElement( "iframe" ); - iframe.frameBorder = iframe.width = iframe.height = 0; - } - - body.appendChild( iframe ); - - // Create a cacheable copy of the iframe document on first call. - // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML - // document to it; WebKit & Firefox won't allow reusing the iframe document. - if ( !iframeDoc || !iframe.createElement ) { - iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; - iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); - iframeDoc.close(); - } - - elem = iframeDoc.createElement( nodeName ); - - iframeDoc.body.appendChild( elem ); - - display = jQuery.css( elem, "display" ); - body.removeChild( iframe ); - } - - // Store the correct default display - elemdisplay[ nodeName ] = display; - } - - return elemdisplay[ nodeName ]; -} - - - - -var rtable = /^t(?:able|d|h)$/i, - rroot = /^(?:body|html)$/i; - -if ( "getBoundingClientRect" in document.documentElement ) { - jQuery.fn.offset = function( options ) { - var elem = this[0], box; - - if ( options ) { - return this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - try { - box = elem.getBoundingClientRect(); - } catch(e) {} - - var doc = elem.ownerDocument, - docElem = doc.documentElement; - - // Make sure we're not dealing with a disconnected DOM node - if ( !box || !jQuery.contains( docElem, elem ) ) { - return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; - } - - var body = doc.body, - win = getWindow(doc), - clientTop = docElem.clientTop || body.clientTop || 0, - clientLeft = docElem.clientLeft || body.clientLeft || 0, - scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, - scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, - top = box.top + scrollTop - clientTop, - left = box.left + scrollLeft - clientLeft; - - return { top: top, left: left }; - }; - -} else { - jQuery.fn.offset = function( options ) { - var elem = this[0]; - - if ( options ) { - return this.each(function( i ) { +jQuery.fn.offset = function( options ) { + if ( arguments.length ) { + return options === undefined ? + this : + this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - var computedStyle, - offsetParent = elem.offsetParent, - prevOffsetParent = elem, - doc = elem.ownerDocument, - docElem = doc.documentElement, - body = doc.body, - defaultView = doc.defaultView, - prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, - top = elem.offsetTop, - left = elem.offsetLeft; - - while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { - if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { - break; - } - - computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; - top -= elem.scrollTop; - left -= elem.scrollLeft; - - if ( elem === offsetParent ) { - top += elem.offsetTop; - left += elem.offsetLeft; - - if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } + } - prevOffsetParent = offsetParent; - offsetParent = elem.offsetParent; - } + var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, + box = { top: 0, left: 0 }, + elem = this[ 0 ], + doc = elem && elem.ownerDocument; - if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } + if ( !doc ) { + return; + } - prevComputedStyle = computedStyle; - } + if ( (body = doc.body) === elem ) { + return jQuery.offset.bodyOffset( elem ); + } - if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { - top += body.offsetTop; - left += body.offsetLeft; - } + docElem = doc.documentElement; - if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { - top += Math.max( docElem.scrollTop, body.scrollTop ); - left += Math.max( docElem.scrollLeft, body.scrollLeft ); - } + // Make sure it's not a disconnected DOM node + if ( !jQuery.contains( docElem, elem ) ) { + return box; + } - return { top: top, left: left }; + // If we don't have gBCR, just use 0,0 rather than error + // BlackBerry 5, iOS 3 (original iPhone) + if ( typeof elem.getBoundingClientRect !== "undefined" ) { + box = elem.getBoundingClientRect(); + } + win = getWindow( doc ); + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + scrollTop = win.pageYOffset || docElem.scrollTop; + scrollLeft = win.pageXOffset || docElem.scrollLeft; + return { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft }; -} +}; jQuery.offset = { @@ -9076,7 +9302,7 @@ jQuery.fn.extend({ position: function() { if ( !this[0] ) { - return null; + return; } var elem = this[0], @@ -9111,49 +9337,36 @@ jQuery.fn.extend({ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; } - return offsetParent; + return offsetParent || document.body; }); } }); // Create scrollLeft and scrollTop methods -jQuery.each( ["Left", "Top"], function( i, name ) { - var method = "scroll" + name; +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { + var top = /Y/.test( prop ); jQuery.fn[ method ] = function( val ) { - var elem, win; - - if ( val === undefined ) { - elem = this[ 0 ]; + return jQuery.access( this, function( elem, method, val ) { + var win = getWindow( elem ); - if ( !elem ) { - return null; + if ( val === undefined ) { + return win ? (prop in win) ? win[ prop ] : + win.document.documentElement[ method ] : + elem[ method ]; } - win = getWindow( elem ); - - // Return the scroll offset - return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : - jQuery.support.boxModel && win.document.documentElement[ method ] || - win.document.body[ method ] : - elem[ method ]; - } - - // Set the scroll offset - return this.each(function() { - win = getWindow( this ); - if ( win ) { win.scrollTo( - !i ? val : jQuery( win ).scrollLeft(), - i ? val : jQuery( win ).scrollTop() + !top ? val : jQuery( win ).scrollLeft(), + top ? val : jQuery( win ).scrollTop() ); } else { - this[ method ] = val; + elem[ method ] = val; } - }); + }, method, val, arguments.length, null ); }; }); @@ -9164,84 +9377,47 @@ function getWindow( elem ) { elem.defaultView || elem.parentWindow : false; } +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { + // margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return jQuery.access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there + // isn't a whole lot we can do. See pull request at this URL for discussion: + // https://github.com/jquery/jquery/pull/764 + return elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest + // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + return value === undefined ? + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, value, extra ) : - - -// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods -jQuery.each([ "Height", "Width" ], function( i, name ) { - - var type = name.toLowerCase(); - - // innerHeight and innerWidth - jQuery.fn[ "inner" + name ] = function() { - var elem = this[0]; - return elem ? - elem.style ? - parseFloat( jQuery.css( elem, type, "padding" ) ) : - this[ type ]() : - null; - }; - - // outerHeight and outerWidth - jQuery.fn[ "outer" + name ] = function( margin ) { - var elem = this[0]; - return elem ? - elem.style ? - parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : - this[ type ]() : - null; - }; - - jQuery.fn[ type ] = function( size ) { - // Get window width or height - var elem = this[0]; - if ( !elem ) { - return size == null ? null : this; - } - - if ( jQuery.isFunction( size ) ) { - return this.each(function( i ) { - var self = jQuery( this ); - self[ type ]( size.call( this, i, self[ type ]() ) ); - }); - } - - if ( jQuery.isWindow( elem ) ) { - // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat - var docElemProp = elem.document.documentElement[ "client" + name ], - body = elem.document.body; - return elem.document.compatMode === "CSS1Compat" && docElemProp || - body && body[ "client" + name ] || docElemProp; - - // Get document width or height - } else if ( elem.nodeType === 9 ) { - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - return Math.max( - elem.documentElement["client" + name], - elem.body["scroll" + name], elem.documentElement["scroll" + name], - elem.body["offset" + name], elem.documentElement["offset" + name] - ); - - // Get or set width or height on the element - } else if ( size === undefined ) { - var orig = jQuery.css( elem, type ), - ret = parseFloat( orig ); - - return jQuery.isNumeric( ret ) ? ret : orig; - - // Set the width or height on the element (default to pixels if value is unitless) - } else { - return this.css( type, typeof size === "string" ? size : size + "px" ); - } - }; - + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable, null ); + }; + }); }); - - - - // Expose jQuery to the global object window.jQuery = window.$ = jQuery; @@ -9261,6 +9437,4 @@ if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); } - - })( window ); diff --git a/resources/jquery/jquery.json.js b/resources/jquery/jquery.json.js index 63230b3b..aac3428b 100644 --- a/resources/jquery/jquery.json.js +++ b/resources/jquery/jquery.json.js @@ -1,180 +1,193 @@ -/* +/** * jQuery JSON Plugin - * version: 2.1 (2009-08-14) + * version: 2.3 (2011-09-17) * * This document is licensed as free software under the terms of the * MIT License: http://www.opensource.org/licenses/mit-license.php * - * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org + * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org * website's http://www.json.org/json2.js, which proclaims: * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that * I uphold. * - * It is also influenced heavily by MochiKit's serializeJSON, which is + * It is also influenced heavily by MochiKit's serializeJSON, which is * copyrighted 2005 by Bob Ippolito. - * - * @see http://code.google.com/p/jquery-json/ */ -(function($) { - /** jQuery.toJSON( json-serializble ) - Converts the given argument into a JSON respresentation. - - If an object has a "toJSON" function, that will be used to get the representation. - Non-integer/string keys are skipped in the object, as are keys that point to a function. - - json-serializble: - The *thing* to be converted. - **/ - $.toJSON = function(o) - { - if (typeof(JSON) == 'object' && JSON.stringify) - return JSON.stringify(o); - - var type = typeof(o); - - if (o === null) - return "null"; - - if (type == "undefined") - return undefined; - - if (type == "number" || type == "boolean") - return o + ""; - - if (type == "string") - return $.quoteString(o); - - if (type == 'object') - { - if (typeof o.toJSON == "function") - return $.toJSON( o.toJSON() ); - - if (o.constructor === Date) - { - var month = o.getUTCMonth() + 1; - if (month < 10) month = '0' + month; - - var day = o.getUTCDate(); - if (day < 10) day = '0' + day; - - var year = o.getUTCFullYear(); - - var hours = o.getUTCHours(); - if (hours < 10) hours = '0' + hours; - - var minutes = o.getUTCMinutes(); - if (minutes < 10) minutes = '0' + minutes; - - var seconds = o.getUTCSeconds(); - if (seconds < 10) seconds = '0' + seconds; - - var milli = o.getUTCMilliseconds(); - if (milli < 100) milli = '0' + milli; - if (milli < 10) milli = '0' + milli; - - return '"' + year + '-' + month + '-' + day + 'T' + - hours + ':' + minutes + ':' + seconds + - '.' + milli + 'Z"'; - } - - if (o.constructor === Array) - { - var ret = []; - for (var i = 0; i < o.length; i++) - ret.push( $.toJSON(o[i]) || "null" ); - - return "[" + ret.join(",") + "]"; - } - - var pairs = []; - for (var k in o) { - var name; - var type = typeof k; - - if (type == "number") - name = '"' + k + '"'; - else if (type == "string") - name = $.quoteString(k); - else - continue; //skip non-string or number keys - - if (typeof o[k] == "function") - continue; //skip pairs where the value is a function. - - var val = $.toJSON(o[k]); - - pairs.push(name + ":" + val); - } - - return "{" + pairs.join(", ") + "}"; - } - }; - - /** jQuery.evalJSON(src) - Evaluates a given piece of json source. - **/ - $.evalJSON = function(src) - { - if (typeof(JSON) == 'object' && JSON.parse) - return JSON.parse(src); - return eval("(" + src + ")"); - }; - - /** jQuery.secureEvalJSON(src) - Evals JSON in a way that is *more* secure. - **/ - $.secureEvalJSON = function(src) - { - if (typeof(JSON) == 'object' && JSON.parse) - return JSON.parse(src); - - var filtered = src; - filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); - filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - - if (/^[\],:{}\s]*$/.test(filtered)) - return eval("(" + src + ")"); - else - throw new SyntaxError("Error parsing JSON, source is not valid."); - }; - - /** jQuery.quoteString(string) - Returns a string-repr of a string, escaping quotes intelligently. - Mostly a support function for toJSON. - - Examples: - >>> jQuery.quoteString("apple") - "apple" - - >>> jQuery.quoteString('"Where are we going?", she asked.') - "\"Where are we going?\", she asked." - **/ - $.quoteString = function(string) - { - if (string.match(_escapeable)) - { - return '"' + string.replace(_escapeable, function (a) - { - var c = _meta[a]; - if (typeof c === 'string') return c; - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); - }) + '"'; - } - return '"' + string + '"'; - }; - - var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; - - var _meta = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; -})(jQuery); +(function( $ ) { + + var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, + meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + /** + * jQuery.toJSON + * Converts the given argument into a JSON respresentation. + * + * @param o {Mixed} The json-serializble *thing* to be converted + * + * If an object has a toJSON prototype, that will be used to get the representation. + * Non-integer/string keys are skipped in the object, as are keys that point to a + * function. + * + */ + $.toJSON = typeof JSON === 'object' && JSON.stringify + ? JSON.stringify + : function( o ) { + + if ( o === null ) { + return 'null'; + } + + var type = typeof o; + + if ( type === 'undefined' ) { + return undefined; + } + if ( type === 'number' || type === 'boolean' ) { + return '' + o; + } + if ( type === 'string') { + return $.quoteString( o ); + } + if ( type === 'object' ) { + if ( typeof o.toJSON === 'function' ) { + return $.toJSON( o.toJSON() ); + } + if ( o.constructor === Date ) { + var month = o.getUTCMonth() + 1, + day = o.getUTCDate(), + year = o.getUTCFullYear(), + hours = o.getUTCHours(), + minutes = o.getUTCMinutes(), + seconds = o.getUTCSeconds(), + milli = o.getUTCMilliseconds(); + + if ( month < 10 ) { + month = '0' + month; + } + if ( day < 10 ) { + day = '0' + day; + } + if ( hours < 10 ) { + hours = '0' + hours; + } + if ( minutes < 10 ) { + minutes = '0' + minutes; + } + if ( seconds < 10 ) { + seconds = '0' + seconds; + } + if ( milli < 100 ) { + milli = '0' + milli; + } + if ( milli < 10 ) { + milli = '0' + milli; + } + return '"' + year + '-' + month + '-' + day + 'T' + + hours + ':' + minutes + ':' + seconds + + '.' + milli + 'Z"'; + } + if ( o.constructor === Array ) { + var ret = []; + for ( var i = 0; i < o.length; i++ ) { + ret.push( $.toJSON( o[i] ) || 'null' ); + } + return '[' + ret.join(',') + ']'; + } + var name, + val, + pairs = []; + for ( var k in o ) { + type = typeof k; + if ( type === 'number' ) { + name = '"' + k + '"'; + } else if (type === 'string') { + name = $.quoteString(k); + } else { + // Keys must be numerical or string. Skip others + continue; + } + type = typeof o[k]; + + if ( type === 'function' || type === 'undefined' ) { + // Invalid values like these return undefined + // from toJSON, however those object members + // shouldn't be included in the JSON string at all. + continue; + } + val = $.toJSON( o[k] ); + pairs.push( name + ':' + val ); + } + return '{' + pairs.join( ',' ) + '}'; + } + }; + + /** + * jQuery.evalJSON + * Evaluates a given piece of json source. + * + * @param src {String} + */ + $.evalJSON = typeof JSON === 'object' && JSON.parse + ? JSON.parse + : function( src ) { + return eval('(' + src + ')'); + }; + + /** + * jQuery.secureEvalJSON + * Evals JSON in a way that is *more* secure. + * + * @param src {String} + */ + $.secureEvalJSON = typeof JSON === 'object' && JSON.parse + ? JSON.parse + : function( src ) { + + var filtered = + src + .replace( /\\["\\\/bfnrtu]/g, '@' ) + .replace( /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace( /(?:^|:|,)(?:\s*\[)+/g, ''); + + if ( /^[\],:{}\s]*$/.test( filtered ) ) { + return eval( '(' + src + ')' ); + } else { + throw new SyntaxError( 'Error parsing JSON, source is not valid.' ); + } + }; + + /** + * jQuery.quoteString + * Returns a string-repr of a string, escaping quotes intelligently. + * Mostly a support function for toJSON. + * Examples: + * >>> jQuery.quoteString('apple') + * "apple" + * + * >>> jQuery.quoteString('"Where are we going?", she asked.') + * "\"Where are we going?\", she asked." + */ + $.quoteString = function( string ) { + if ( string.match( escapeable ) ) { + return '"' + string.replace( escapeable, function( a ) { + var c = meta[a]; + if ( typeof c === 'string' ) { + return c; + } + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }; + +})( jQuery ); diff --git a/resources/jquery/jquery.localize.js b/resources/jquery/jquery.localize.js index 3a7925bf..3e786ec2 100644 --- a/resources/jquery/jquery.localize.js +++ b/resources/jquery/jquery.localize.js @@ -1,73 +1,165 @@ /** * Simple Placeholder-based Localization * - * Call on a selection of HTML which contains <html:msg key="message-key" /> elements or elements with - * title-msg="message-key" or alt-msg="message-key" attributes. <html:msg /> elements will be replaced - * with localized text, elements with title-msg and alt-msg attributes will receive localized title - * and alt attributes. - * * + * Call on a selection of HTML which contains <html:msg key="message-key" /> elements or elements + * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes. + * <html:msg /> elements will be replaced with localized text, *-msg attributes will be replaced + * with attributes that do not have the "-msg" suffix and contain a localized message. + * + * Example: + * // Messages: { 'title': 'Awesome', 'desc': 'Cat doing backflip' 'search' contains 'Search' } + * var html = '\ + * <p>\ + * <html:msg key="title" />\ + * <img src="something.jpg" title-msg="title" alt-msg="desc" />\ + * <input type="text" placeholder-msg="search" />\ + * </p>'; + * $( 'body' ).append( $( html ).localize() ); + * + * Appends something like this to the body... + * <p> + * Awesome + * <img src="something.jpg" title="Awesome" alt="Cat doing backflip" /> + * <input type="text" placeholder="Search" /> + * </p> + * + * Arguments can be passed into uses of a message using the params property of the options object + * given to .localize(). Multiple messages can be given parameters, because the params property is + * an object keyed by the message key to apply the parameters to, each containing an array of + * parameters to use. The limitation is that you can not use different parameters to individual uses + * of a message in the same selection being localized - they will all recieve the same parameters. + * + * Example: + * // Messages: { 'easy-as': 'Easy as $1 $2 $3.' } + * var html = '<p><html:msg key="easy-as" /></p>'; + * $( 'body' ).append( $( html ).localize( { 'params': { 'easy-as': ['a', 'b', 'c'] } } ) ); + * + * Appends something like this to the body... + * <p>Easy as a, b, c</p> + * + * Raw HTML content can be used, instead of it being escaped as text. To do this, just use the raw + * attribute on a msg element. + * + * Example: + * // Messages: { 'hello': '<b><i>Hello</i> $1!</b>' } + * var html = '\ + * <p>\ + * <!-- escaped: --><html:msg key="hello" />\ + * <!-- raw: --><html:msg key="hello" raw />\ + * </p>'; + * $( 'body' ).append( $( html ).localize( { 'params': { 'hello': ['world'] } } ) ); + * + * Appends something like this to the body... + * <p> + * <!-- escaped: --><b><i>Hello</i> world!</b> + * <!-- raw: --><b><i>Hello</i> world!</b> + * </p> + * + * Message keys can also be remapped, allowing the same generic template to be used with a variety + * of messages. This is important for improving re-usability of templates. + * + * Example: + * // Messages: { 'good-afternoon': 'Good afternoon' } + * var html = '<p><html:msg key="greeting" /></p>'; + * $( 'body' ).append( $( html ).localize( { 'keys': { 'greeting': 'good-afternoon' } } ) ); + * + * Appends something like this to the body... + * <p>Good afternoon</p> + * + * Message keys can also be prefixed globally, which is handy when writing extensions, where by + * convention all messages are prefixed with the extension's name. + * * Example: - * <p class="somethingCool"> - * <html:msg key="my-message" /> - * <img src="something.jpg" title-msg="my-title-message" alt-msg="my-alt-message" /> - * </p> - * - * Localizes to... - * <p class="somethingCool"> - * My Message - * <img src="something.jpg" title="My Title Message" alt="My Alt Message" /> - * </p> + * // Messages: { 'teleportation-warning': 'You may not get there all in one piece.' } + * var html = '<p><html:msg key="warning" /></p>'; + * $( 'body' ).append( $( html ).localize( { 'prefix': 'teleportation-' } ) ); + * + * Appends something like this to the body... + * <p>You may not get there all in one piece.</p> + * */ -( function( $ ) { +( function ( $, mw ) { + +/** + * Gets a localized message, using parameters from options if present. + * + * @function + * @param {String} key Message key to get localized message for + * @returns {String} Localized message + */ +function msg( options, key ) { + var args = options.params[key] || []; + // Format: mw.msg( key [, p1, p2, ...] ) + args.unshift( options.prefix + ( options.keys[key] || key ) ); + return mw.msg.apply( mw, args ); +} + /** * Localizes a DOM selection by replacing <html:msg /> elements with localized text and adding * localized title and alt attributes to elements with title-msg and alt-msg attributes * respectively. * - * @param Object: options Map of options - * * prefix: Message prefix to use when localizing elements and attributes + * @method + * @param {Object} options Map of options to be used while localizing + * @param {String} options.prefix String to prepend to all message keys + * @param {Object} options.keys Message key aliases, used for remapping keys to a template + * @param {Object} options.params Lists of parameters to use with certain message keys + * @returns {jQuery} This selection */ +$.fn.localize = function ( options ) { + var $target = this, + attributes = ['title', 'alt', 'placeholder']; + + // Extend options + options = $.extend( { + prefix: '', + keys: {}, + params: {} + }, options ); + + // Elements + // Ok, so here's the story on this selector. In IE 6/7, searching for 'msg' turns up the + // 'html:msg', but searching for 'html:msg' doesn't. In later IE and other browsers, searching + // for 'html:msg' turns up the 'html:msg', but searching for 'msg' doesn't. So searching for + // both 'msg' and 'html:msg' seems to get the job done. This feels pretty icky, though. + $target.find( 'msg,html\\:msg' ).each( function () { + var $el = $(this); + // Escape by default + if ( $el.attr( 'raw' ) ) { + $el.html( msg( options, $el.attr( 'key' ) ) ); + } else { + $el.text( msg( options, $el.attr( 'key' ) ) ); + } + // Remove wrapper + $el.replaceWith( $el.html() ); + } ); + + // Attributes + // Note: there's no way to prevent escaping of values being injected into attributes, this is + // on purpose, not a design flaw. + $.each( attributes, function ( i, attr ) { + var msgAttr = attr + '-msg'; + $target.find( '[' + msgAttr + ']' ).each( function () { + var $el = $(this); + $el.attr( attr, msg( options, $el.attr( msgAttr ) ) ).removeAttr( msgAttr ); + } ); + } ); -$.fn.localize = function( options ) { - options = $.extend( { 'prefix': '', 'keys': {}, 'params': {} }, options ); - function msg( key ) { - var args = key in options.params ? options.params[key] : []; - // Format: mw.msg( key [, p1, p2, ...] ) - args.unshift( options.prefix + ( key in options.keys ? options.keys[key] : key ) ); - return mw.msg.apply( mw, args ); - }; - return $(this) - // Ok, so here's the story on this selector. - // In IE 6/7, searching for 'msg' turns up the 'html:msg', but searching for 'html:msg' does not. - // In later IE and other browsers, searching for 'html:msg' turns up the 'html:msg', but searching for 'msg' does not. - // So searching for both 'msg' and 'html:msg' seems to get the job done. - // This feels pretty icky, though. - .find( 'msg,html\\:msg' ) - .each( function() { - var $el = $(this); - $el - .text( msg( $el.attr( 'key' ) ) ) - .replaceWith( $el.html() ); - } ) - .end() - .find( '[title-msg]' ) - .each( function() { - var $el = $(this); - $el - .attr( 'title', msg( $el.attr( 'title-msg' ) ) ) - .removeAttr( 'title-msg' ); - } ) - .end() - .find( '[alt-msg]' ) - .each( function() { - var $el = $(this); - $el - .attr( 'alt', msg( $el.attr( 'alt-msg' ) ) ) - .removeAttr( 'alt-msg' ); - } ) - .end(); + // HTML, Text for elements which cannot have children e.g. OPTION + $target.find( '[data-msg-text]' ).each( function() { + var $el = $( this ); + $el.text( msg( options, $el.attr( 'data-msg-text' ) ) ); + } ); + + $target.find( '[data-msg-html]' ).each( function() { + var $el = $( this ); + $el.html( msg( options, $el.attr( 'data-msg-html' ) ) ); + } ); + + return $target; }; // Let IE know about the msg tag before it's used... document.createElement( 'msg' ); -} )( jQuery ); + +}( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.makeCollapsible.js b/resources/jquery/jquery.makeCollapsible.js index 7a1897ce..0a4d3645 100644 --- a/resources/jquery/jquery.makeCollapsible.js +++ b/resources/jquery/jquery.makeCollapsible.js @@ -14,28 +14,31 @@ * @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0> * @license GPL2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> */ -( function( $, mw ) { +( function ( $, mw ) { -$.fn.makeCollapsible = function() { +$.fn.makeCollapsible = function () { - return this.each(function() { - var _fn = 'jquery.makeCollapsible> '; + return this.each(function () { // Define reused variables and functions - var $that = $(this).addClass( 'mw-collapsible' ), // case: $( '#myAJAXelement' ).makeCollapsible() + var $toggle, + lpx = 'jquery.makeCollapsible> ', + $that = $(this).addClass( 'mw-collapsible' ), // case: $( '#myAJAXelement' ).makeCollapsible() that = this, collapsetext = $(this).attr( 'data-collapsetext' ), expandtext = $(this).attr( 'data-expandtext' ), - toggleElement = function( $collapsible, action, $defaultToggle, instantHide ) { + toggleElement = function ( $collapsible, action, $defaultToggle, instantHide ) { + var $collapsibleContent, $containers; + // Validate parameters if ( !$collapsible.jquery ) { // $collapsible must be an instance of jQuery return; } - if ( action != 'expand' && action != 'collapse' ) { + if ( action !== 'expand' && action !== 'collapse' ) { // action must be string with 'expand' or 'collapse' return; } - if ( typeof $defaultToggle == 'undefined' ) { + if ( $defaultToggle === undefined ) { $defaultToggle = null; } if ( $defaultToggle !== null && !($defaultToggle instanceof $) ) { @@ -45,9 +48,7 @@ $.fn.makeCollapsible = function() { return; } - var $containers = null; - - if ( action == 'collapse' ) { + if ( action === 'collapse' ) { // Collapse the element if ( $collapsible.is( 'table' ) ) { @@ -80,7 +81,7 @@ $.fn.makeCollapsible = function() { } } else { // <div>, <p> etc. - var $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); + $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); // If a collapsible-content is defined, collapse it if ( $collapsibleContent.length ) { @@ -123,7 +124,7 @@ $.fn.makeCollapsible = function() { } } else { // <div>, <p> etc. - var $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); + $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); // If a collapsible-content is defined, collapse it if ( $collapsibleContent.length ) { @@ -142,8 +143,8 @@ $.fn.makeCollapsible = function() { } }, // Toggles collapsible and togglelink class and updates text label - toggleLinkDefault = function( that, e ) { - var $that = $(that), + toggleLinkDefault = function ( that, e ) { + var $that = $(that), $collapsible = $that.closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' ); e.preventDefault(); e.stopPropagation(); @@ -175,9 +176,9 @@ $.fn.makeCollapsible = function() { return; }, // Toggles collapsible and togglelink class - toggleLinkPremade = function( $that, e ) { - var $collapsible = $that.eq(0).closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' ); - if ( $(e.target).is('a') ) { + toggleLinkPremade = function ( $that, e ) { + var $collapsible = $that.eq(0).closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' ); + if ( $(e.target).is( 'a' ) ) { return true; } e.preventDefault(); @@ -200,11 +201,11 @@ $.fn.makeCollapsible = function() { return; }, // Toggles customcollapsible - toggleLinkCustom = function( $that, e, $collapsible ) { + toggleLinkCustom = function ( $that, e, $collapsible ) { // For the initial state call of customtogglers there is no event passed if (e) { e.preventDefault(); - e.stopPropagation(); + e.stopPropagation(); } // Get current state and toggle to the opposite var action = $collapsible.hasClass( 'mw-collapsed' ) ? 'expand' : 'collapse'; @@ -214,7 +215,7 @@ $.fn.makeCollapsible = function() { }; // Use custom text or default ? - if( !collapsetext ) { + if ( !collapsetext ) { collapsetext = mw.msg( 'collapsible-collapse' ); } if ( !expandtext ) { @@ -229,7 +230,7 @@ $.fn.makeCollapsible = function() { .parent() .prepend( ' [' ) .append( '] ' ) - .bind( 'click.mw-collapse', function(e) { + .on( 'click.mw-collapse', function ( e ) { toggleLinkDefault( this, e ); } ); @@ -247,15 +248,15 @@ $.fn.makeCollapsible = function() { var thatId = $that.attr( 'id' ), $customTogglers = $( '.' + thatId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) ); - mw.log( _fn + 'Found custom collapsible: #' + thatId ); + mw.log( lpx + 'Found custom collapsible: #' + thatId ); // Double check that there is actually a customtoggle link if ( $customTogglers.length ) { - $customTogglers.bind( 'click.mw-collapse', function( e ) { + $customTogglers.on( 'click.mw-collapse', function ( e ) { toggleLinkCustom( $(this), e, $that ); } ); } else { - mw.log( _fn + '#' + thatId + ': Missing toggler!' ); + mw.log( lpx + '#' + thatId + ': Missing toggler!' ); } // Initial state @@ -271,22 +272,22 @@ $.fn.makeCollapsible = function() { // Elements are treated differently if ( $that.is( 'table' ) ) { // The toggle-link will be in one the the cells (td or th) of the first row - var $firstRowCells = $( 'tr:first th, tr:first td', that ), - $toggle = $firstRowCells.find( '> .mw-collapsible-toggle' ); + var $firstRowCells = $that.find( 'tr:first th, tr:first td' ); + $toggle = $firstRowCells.find( '> .mw-collapsible-toggle' ); // If theres no toggle link, add it to the last cell if ( !$toggle.length ) { $firstRowCells.eq(-1).prepend( $toggleLink ); } else { - $toggleLink = $toggle.unbind( 'click.mw-collapse' ).bind( 'click.mw-collapse', function( e ) { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { toggleLinkPremade( $toggle, e ); } ); } } else if ( $that.is( 'ul' ) || $that.is( 'ol' ) ) { // The toggle-link will be in the first list-item - var $firstItem = $( 'li:first', $that), - $toggle = $firstItem.find( '> .mw-collapsible-toggle' ); + var $firstItem = $that.find( 'li:first' ); + $toggle = $firstItem.find( '> .mw-collapsible-toggle' ); // If theres no toggle link, add it if ( !$toggle.length ) { @@ -294,12 +295,12 @@ $.fn.makeCollapsible = function() { // to be "1". Except if the value-attribute is already used. // If no value was set WebKit returns "", Mozilla returns '-1', others return null or undefined. var firstval = $firstItem.attr( 'value' ); - if ( firstval === undefined || !firstval || firstval == '-1' ) { + if ( firstval === undefined || !firstval || firstval === '-1' || firstval === -1 ) { $firstItem.attr( 'value', '1' ); } $that.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() ); } else { - $toggleLink = $toggle.unbind( 'click.mw-collapse' ).bind( 'click.mw-collapse', function( e ) { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { toggleLinkPremade( $toggle, e ); } ); } @@ -307,7 +308,7 @@ $.fn.makeCollapsible = function() { } else { // <div>, <p> etc. // The toggle-link will be the first child of the element - var $toggle = $that.find( '> .mw-collapsible-toggle' ); + $toggle = $that.find( '> .mw-collapsible-toggle' ); // If a direct child .content-wrapper does not exists, create it if ( !$that.find( '> .mw-collapsible-content' ).length ) { @@ -318,7 +319,7 @@ $.fn.makeCollapsible = function() { if ( !$toggle.length ) { $that.prepend( $toggleLink ); } else { - $toggleLink = $toggle.unbind( 'click.mw-collapse' ).bind( 'click.mw-collapse', function( e ) { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { toggleLinkPremade( $toggle, e ); } ); } @@ -336,4 +337,5 @@ $.fn.makeCollapsible = function() { } } ); }; -} )( jQuery, mediaWiki ); + +}( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.messageBox.css b/resources/jquery/jquery.messageBox.css deleted file mode 100644 index 96332aa5..00000000 --- a/resources/jquery/jquery.messageBox.css +++ /dev/null @@ -1,15 +0,0 @@ -.js-messagebox { - margin: 1em 5%; - padding: 0.5em 2.5%; - border: 1px solid #ccc; - background-color: #fcfcfc; - font-size: 0.8em; -} -.js-messagebox .js-messagebox-group { - margin: 1px; - padding: 0.5em 2.5%; - border-bottom: 1px solid #ddd; -} -.js-messagebox .js-messagebox-group:last-child { - border-bottom: thin none transparent; -}
\ No newline at end of file diff --git a/resources/jquery/jquery.messageBox.js b/resources/jquery/jquery.messageBox.js deleted file mode 100644 index 690fedd2..00000000 --- a/resources/jquery/jquery.messageBox.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * jQuery messageBox - * - * Function to inform the user of something. Use sparingly (since there's mw.log for - * messages aimed at developers / debuggers). Based on the function in MediaWiki's - * legacy javascript (wikibits.js) by Aryeh Gregor called jsMsg() added in r23233. - * - * @author Krinkle <krinklemail@gmail.com> - * - * Dual license: - * @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0> - * @license GPL2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> - */ -( function( $ ) { -// @return jQuery object of the message box -$.messageBoxNew = function( options ) { - options = $.extend( { - 'id': 'js-messagebox', // unique identifier for this message box - 'parent': 'body', // jQuery/CSS selector - 'insert': 'prepend' // 'prepend' or 'append' - }, options ); - var $curBox = $( '#' + options.id ); - // Only create a new box if it doesn't exist already - if ( $curBox.length > 0 ) { - if ( $curBox.hasClass( 'js-messagebox' ) ) { - return $curBox; - } else { - return $curBox.addClass( 'js-messagebox' ); - } - } else { - var $newBox = $( '<div>', { - 'id': options.id, - 'class': 'js-messagebox', - 'css': { - 'display': 'none' - } - }); - if ( $( options.parent ).length < 1 ) { - options.parent = 'body'; - } - if ( options.insert === 'append' ) { - $newBox.appendTo( options.parent ); - return $newBox; - } else { - $newBox.prependTo( options.parent ); - return $newBox; - } - } -}; -// Calling with no message or message set to empty string or null will hide the group, -// setting 'replace' to true as well will reset and hide the group entirely. -// If there are no visible groups the main message box is hidden automatically, -// and shown again once there are messages -// @return jQuery object of message group -$.messageBox = function( options ) { - options = $.extend( { - 'message': '', - 'group': 'default', - 'replace': false, // if true replaces any previous message in this group - 'target': 'js-messagebox' - }, options ); - var $target = $.messageBoxNew( { id: options.target } ); - var groupID = options.target + '-' + options.group; - var $group = $( '#' + groupID ); - // Create group container if not existant - if ( $group.length < 1 ) { - $group = $( '<div>', { - 'id': groupID, - 'class': 'js-messagebox-group' - }); - $target.prepend( $group ); - } - // Replace ? - if ( options.replace === true ) { - $group.empty(); - } - // Hide it ? - if ( options.message === '' || options.message === null ) { - $group.hide(); - } else { - // Actual message addition - $group.prepend( $( '<p>' ).append( options.message ) ).show(); - $target.slideDown(); - } - // If the last visible group was just hidden, slide the entire box up - // Othere wise slideDown (if already visible nothing will happen) - if ( $target.find( '> *:visible' ).length === 0 ) { - // to avoid a sudden dissapearance of the last group followed by - // a slide up of only the outline, show it for a second - $group.show(); - $target.slideUp(); - $group.hide(); - } else { - $target.slideDown(); - } - return $group; -}; -} )( jQuery ); diff --git a/resources/jquery/jquery.mwExtension.js b/resources/jquery/jquery.mwExtension.js index 023ea7f3..bbffd7b7 100644 --- a/resources/jquery/jquery.mwExtension.js +++ b/resources/jquery/jquery.mwExtension.js @@ -1,26 +1,26 @@ /* * JavaScript backwards-compatibility alternatives and other convenience functions */ -( function( $ ) { +( function ( $ ) { $.extend({ - trimLeft: function( str ) { + trimLeft: function ( str ) { return str === null ? '' : str.toString().replace( /^\s+/, '' ); }, - trimRight: function( str ) { + trimRight: function ( str ) { return str === null ? '' : str.toString().replace( /\s+$/, '' ); }, - ucFirst: function( str ) { + ucFirst: function ( str ) { return str.charAt( 0 ).toUpperCase() + str.substr( 1 ); }, - escapeRE: function( str ) { - return str.replace ( /([\\{}()|.?*+\-^$\[\]])/g, "\\$1" ); + escapeRE: function ( str ) { + return str.replace ( /([\\{}()|.?*+\-\^$\[\]])/g, "\\$1" ); }, - isDomElement: function( el ) { + isDomElement: function ( el ) { return !!el && !!el.nodeType; }, - isEmpty: function( v ) { + isEmpty: function ( v ) { if ( v === '' || v === 0 || v === '0' || v === null || v === false || v === undefined ) { @@ -39,8 +39,8 @@ } return false; }, - compareArray: function( arrThis, arrAgainst ) { - if ( arrThis.length != arrAgainst.length ) { + compareArray: function ( arrThis, arrAgainst ) { + if ( arrThis.length !== arrAgainst.length ) { return false; } for ( var i = 0; i < arrThis.length; i++ ) { @@ -54,25 +54,25 @@ } return true; }, - compareObject: function( objectA, objectB ) { + compareObject: function ( objectA, objectB ) { + var prop, type; // Do a simple check if the types match - if ( typeof objectA == typeof objectB ) { + if ( typeof objectA === typeof objectB ) { // Only loop over the contents if it really is an object - if ( typeof objectA == 'object' ) { + if ( typeof objectA === 'object' ) { // If they are aliases of the same object (ie. mw and mediaWiki) return now if ( objectA === objectB ) { return true; } else { - var prop; // Iterate over each property for ( prop in objectA ) { // Check if this property is also present in the other object if ( prop in objectB ) { // Compare the types of the properties - var 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' : @@ -118,4 +118,4 @@ } }); -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.placeholder.js b/resources/jquery/jquery.placeholder.js index 8310cef9..7badb11a 100644 --- a/resources/jquery/jquery.placeholder.js +++ b/resources/jquery/jquery.placeholder.js @@ -3,85 +3,87 @@ * * This will automatically use the HTML5 placeholder attribute if supported, or emulate this behavior if not. * - * @author Trevor Parscal <tparscal@wikimedia.org> - * @author Krinkle <krinklemail@gmail.com> + * @author Trevor Parscal <tparscal@wikimedia.org>, 2012 + * @author Krinkle <krinklemail@gmail.com>, 2012 * @version 0.2.0 * @license GPL v2 */ -( function( $ ) { +( function ( $ ) { -$.fn.placeholder = function() { + $.fn.placeholder = function () { - return this.each( function() { + return this.each( function () { + var placeholder, $input; - // If the HTML5 placeholder attribute is supported, use it - if ( this.placeholder && 'placeholder' in document.createElement( this.tagName ) ) { - return; - } + // If the HTML5 placeholder attribute is supported, use it + if ( this.placeholder && 'placeholder' in document.createElement( this.tagName ) ) { + return; + } - var placeholder = this.getAttribute( 'placeholder' ); - var $input = $(this); + placeholder = this.getAttribute( 'placeholder' ); + $input = $(this); - // Show initially, if empty - if ( this.value === '' || this.value === placeholder ) { - $input.addClass( 'placeholder' ).val( placeholder ); - } + // Show initially, if empty + if ( this.value === '' || this.value === placeholder ) { + $input.addClass( 'placeholder' ).val( placeholder ); + } - $input - // Show on blur if empty - .blur( function() { - if ( this.value === '' ) { - this.value = placeholder; - $input.addClass( 'placeholder' ); - } - } ) + $input + // Show on blur if empty + .blur( function () { + if ( this.value === '' ) { + this.value = placeholder; + $input.addClass( 'placeholder' ); + } + } ) + + // Hide on focus + // Also listen for other events in case $input was + // already focused when the events were bound + .on( 'focus drop keydown paste', function ( e ) { + if ( $input.hasClass( 'placeholder' ) ) { + if ( e.type === 'drop' && e.originalEvent.dataTransfer ) { + // Support for drag&drop. Instead of inserting the dropped + // text somewhere in the middle of the placeholder string, + // we want to set the contents of the search box to the + // dropped text. - // Hide on focus - // Also listen for other events in case $input was - // already focused when the events were bound - .bind( 'focus drop keydown paste', function( e ) { - if ( $input.hasClass( 'placeholder' ) ) { - if ( e.type == 'drop' && e.originalEvent.dataTransfer ) { - // Support for drag&drop. Instead of inserting the dropped - // text somewhere in the middle of the placeholder string, - // we want to set the contents of the search box to the - // dropped text. + // IE wants getData( 'text' ) but Firefox wants getData( 'text/plain' ) + // Firefox fails gracefully with an empty string, IE barfs with an error + try { + // Try the Firefox way + this.value = e.originalEvent.dataTransfer.getData( 'text/plain' ); + } catch ( exception ) { + // Got an exception, so use the IE way + this.value = e.originalEvent.dataTransfer.getData( 'text' ); + } - // IE wants getData( 'text' ) but Firefox wants getData( 'text/plain' ) - // Firefox fails gracefully with an empty string, IE barfs with an error - try { - // Try the Firefox way - this.value = e.originalEvent.dataTransfer.getData( 'text/plain' ); - } catch ( exception ) { - // Got an exception, so use the IE way - this.value = e.originalEvent.dataTransfer.getData( 'text' ); + // On Firefox, drop fires after the dropped text has been inserted, + // but on IE it fires before. If we don't prevent the default action, + // IE will insert the dropped text twice. + e.preventDefault(); + } else { + this.value = ''; } + $input.removeClass( 'placeholder' ); + } + } ); - // On Firefox, drop fires after the dropped text has been inserted, - // but on IE it fires before. If we don't prevent the default action, - // IE will insert the dropped text twice. - e.preventDefault(); - } else { - this.value = ''; + // Blank on submit -- prevents submitting with unintended value + if ( this.form ) { + $( this.form ).submit( function () { + // $input.trigger( 'focus' ); would be problematic + // because it actually focuses $input, leading + // to nasty behavior in mobile browsers + if ( $input.hasClass( 'placeholder' ) ) { + $input + .val( '' ) + .removeClass( 'placeholder' ); } - $input.removeClass( 'placeholder' ); - } - } ); + }); + } - // Blank on submit -- prevents submitting with unintended value - if ( this.form ) { - $( this.form ).submit( function() { - // $input.trigger( 'focus' ); would be problematic - // because it actually focuses $input, leading - // to nasty behavior in mobile browsers - if ( $input.hasClass( 'placeholder' ) ) { - $input - .val( '' ) - .removeClass( 'placeholder' ); - } - }); - } + }); + }; - }); -}; -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.qunit.completenessTest.js b/resources/jquery/jquery.qunit.completenessTest.js index 5abb8691..1475af2a 100644 --- a/resources/jquery/jquery.qunit.completenessTest.js +++ b/resources/jquery/jquery.qunit.completenessTest.js @@ -1,259 +1,362 @@ /** - * jQuery QUnit CompletenessTest 0.3 + * jQuery QUnit CompletenessTest 0.4 * * Tests the completeness of test suites for object oriented javascript * libraries. Written to be used in environments with jQuery and QUnit. - * Requires jQuery 1.5.2 or higher. - * - * Globals: jQuery, QUnit, console.log + * Requires jQuery 1.7.2 or higher. * * Built for and tested with: - * - Safari 5 + * - Chrome 19 * - Firefox 4 + * - Safari 5 * - * @author Timo Tijhof, 2011 - */ -( function( $ ) { - -/** - * CompletenessTest - * @constructor - * - * @example - * var myTester = new CompletenessTest( myLib ); - * @param masterVariable {Object} 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 - * 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. + * @author Timo Tijhof, 2011-2012 */ -var CompletenessTest = function ( masterVariable, ignoreFn ) { - - // Keep track in these objects. Keyed by strings with the - // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true. - this.methodCallTracker = {}; - this.missingTests = {}; - - this.ignoreFn = undefined === ignoreFn ? function(){ return false; } : ignoreFn; +/*global jQuery, QUnit */ +/*jshint eqeqeq:false, eqnull:false, forin:false */ +( function ( $ ) { + "use strict"; + + var util, + hasOwn = Object.prototype.hasOwnProperty, + log = (window.console && window.console.log) + ? function () { return window.console.log.apply(window.console, arguments); } + : function () {}; + + // Simplified version of a few jQuery methods, except that they don't + // call other jQuery methods. Required to be able to run the CompletenessTest + // on jQuery itself as well. + util = { + keys: Object.keys || function ( object ) { + var key, keys = []; + for ( key in object ) { + if ( hasOwn.call( object, key ) ) { + keys.push( key ); + } + } + return keys; + }, + extend: function () { + var options, name, src, copy, + target = arguments[0] || {}, + i = 1, + length = arguments.length; + + for ( ; i < length; i++ ) { + options = arguments[ i ]; + // Only deal with non-null/undefined values + if ( options !== null && options !== undefined ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } - // Lazy limit in case something weird happends (like recurse (part of) ourself). - this.lazyLimit = 1000; - this.lazyCounter = 0; + // Return the modified object + return target; + }, + each: function ( object, callback ) { + var name; + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + }, + // $.type and $.isEmptyObject are safe as is, they don't call + // other $.* methods. Still need to be derefenced into `util` + // since the CompletenessTest will overload them with spies. + type: $.type, + isEmptyObject: $.isEmptyObject + }; - var that = this; - // Bind begin and end to QUnit. - QUnit.begin = function(){ - that.checkTests( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT ); - }; + /** + * CompletenessTest + * @constructor + * + * @example + * var myTester = new CompletenessTest( myLib ); + * @param masterVariable {Object} 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 + * 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. + */ + function CompletenessTest( masterVariable, ignoreFn ) { - QUnit.done = function(){ - that.checkTests( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_CHECK ); - console.log( 'CompletenessTest.ACTION_CHECK', that ); + // Keep track in these objects. Keyed by strings with the + // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true. + this.injectionTracker = {}; + this.methodCallTracker = {}; + this.missingTests = {}; - // Build HTML representing the outcome from CompletenessTest - // And insert it into the header. + this.ignoreFn = undefined === ignoreFn ? function () { return false; } : ignoreFn; - var makeList = function( blob, title, style ) { - title = title || 'Values'; - var html = '<strong>' + mw.html.escape(title) + '</strong>'; - $.each( blob, function( key ) { - html += '<br />' + mw.html.escape(key); - }); - html += '<br /><br /><em>— CompletenessTest</em>'; - var $oldResult = $( '#qunit-completenesstest' ), - $result = $oldResult.length ? $oldResult : $( '<div id="qunit-completenesstest"></div>' ); - return $result.css( style ).html( html ); - }; - - if ( $.isEmptyObject( that.missingTests ) ) { - // Good - var $testResults = makeList( - { 'No missing tests!': true }, - 'missingTests', - { - background: '#D2E0E6', - color: '#366097', - padding: '1em' - } - ); - } else { - // Bad - var $testResults = makeList( - that.missingTests, - 'missingTests', - { - background: '#EE5757', - color: 'black', - padding: '1em' - } - ); - } + // Lazy limit in case something weird happends (like recurse (part of) ourself). + this.lazyLimit = 2000; + this.lazyCounter = 0; - $( '#qunit-testrunner-toolbar' ).prepend( $testResults ); - }; + var that = this; - return this; -}; + // Bind begin and end to QUnit. + QUnit.begin( function () { + that.walkTheObject( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT ); + log( 'CompletenessTest/walkTheObject/ACTION_INJECT', that ); + }); -/* Static members */ -CompletenessTest.ACTION_INJECT = 500; -CompletenessTest.ACTION_CHECK = 501; + QUnit.done( function () { + that.populateMissingTests(); + log( 'CompletenessTest/populateMissingTests', that ); -/* Public methods */ -CompletenessTest.fn = CompletenessTest.prototype = { + var toolbar, testResults, cntTotal, cntCalled, cntMissing; - /** - * CompletenessTest.fn.checkTests - * - * This function recursively walks through the given object, calling itself as it goes. - * 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, - * further down it could be anything). - * @param masterVariable {Object} 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 - * masterVariable. Not including currName. - * @param action {Number} What is this function supposed to do (ACTION_INJECT or ACTION_CHECK) - */ - checkTests: function( currName, currVar, masterVariable, parentPathArray, action ) { + cntTotal = util.keys( that.injectionTracker ).length; + cntCalled = util.keys( that.methodCallTracker ).length; + cntMissing = util.keys( that.missingTests ).length; - // Handle the lazy limit - this.lazyCounter++; - if ( this.lazyCounter > this.lazyLimit ) { - console.log( 'CompletenessTest.fn.checkTests> Limit reached: ' + this.lazyCounter ); - return null; - } + function makeTestResults( blob, title, style ) { + var elOutputWrapper, elTitle, elContainer, elList, elFoot; - var type = $.type( currVar ), - that = this; + elTitle = document.createElement( 'strong' ); + elTitle.textContent = title || 'Values'; - // Hard ignores - if ( this.ignoreFn( currVar, that, parentPathArray ) ) { - return null; + elList = document.createElement( 'ul' ); + util.each( blob, function ( key ) { + var elItem = document.createElement( 'li' ); + elItem.textContent = key; + elList.appendChild( elItem ); + }); - // Functions - } else if ( type === 'function' ) { + elFoot = document.createElement( 'p' ); + elFoot.innerHTML = '<em>— CompletenessTest</em>'; - /* CHECK MODE */ + elContainer = document.createElement( 'div' ); + elContainer.appendChild( elTitle ); + elContainer.appendChild( elList ); + elContainer.appendChild( elFoot ); - if ( action === CompletenessTest.ACTION_CHECK ) { + elOutputWrapper = document.getElementById( 'qunit-completenesstest' ); + if ( !elOutputWrapper ) { + elOutputWrapper = document.createElement( 'div' ); + elOutputWrapper.id = 'qunit-completenesstest'; + } + elOutputWrapper.appendChild( elContainer ); - if ( !currVar.prototype || $.isEmptyObject( currVar.prototype ) ) { + util.each( style, function ( key, value ) { + elOutputWrapper.style[key] = value; + }); + return elOutputWrapper; + } - that.hasTest( parentPathArray.join( '.' ) ); + if ( cntMissing === 0 ) { + // Good + testResults = makeTestResults( + {}, + 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. No missing tests!', + { + backgroundColor: '#D2E0E6', + color: '#366097', + paddingTop: '1em', + paddingRight: '1em', + paddingBottom: '1em', + paddingLeft: '1em' + } + ); + } else { + // Bad + testResults = makeTestResults( + that.missingTests, + 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. ' + cntMissing + ' methods not covered:', + { + backgroundColor: '#EE5757', + color: 'black', + paddingTop: '1em', + paddingRight: '1em', + paddingBottom: '1em', + paddingLeft: '1em' + } + ); + } - // We don't support checking object constructors yet... - } else { + toolbar = document.getElementById( 'qunit-testrunner-toolbar' ); + if ( toolbar ) { + toolbar.insertBefore( testResults, toolbar.firstChild ); + } + }); - // ...the prototypes are fine tho - $.each( currVar.prototype, function( key, value ) { - if ( key === 'constructor' ) return; + return this; + } - // Clone and break reference to parentPathArray - var tmpPathArray = $.extend( [], parentPathArray ); - tmpPathArray.push( 'prototype' ); tmpPathArray.push( key ); + /* Static members */ + CompletenessTest.ACTION_INJECT = 500; + CompletenessTest.ACTION_CHECK = 501; + + /* Public methods */ + CompletenessTest.fn = CompletenessTest.prototype = { + + /** + * CompletenessTest.fn.walkTheObject + * + * This function recursively walks through the given object, calling itself as it goes. + * 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, + * further down it could be anything). + * @param masterVariable {Object} 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 + * masterVariable. Not including currName. + * @param action {Number} What is this function supposed to do (ACTION_INJECT or ACTION_CHECK) + */ + walkTheObject: function ( currName, currVar, masterVariable, parentPathArray, action ) { + + var key, value, tmpPathArray, + type = util.type( currVar ), + that = this; + + // Hard ignores + if ( this.ignoreFn( currVar, that, parentPathArray ) ) { + return null; + } - that.hasTest( tmpPathArray.join( '.' ) ); - } ); - } + // Handle the lazy limit + this.lazyCounter++; + if ( this.lazyCounter > this.lazyLimit ) { + log( 'CompletenessTest.fn.walkTheObject> Limit reached: ' + this.lazyCounter, parentPathArray ); + return null; + } - /* INJECT MODE */ + // Functions + if ( type === 'function' ) { - } else if ( action === CompletenessTest.ACTION_INJECT ) { + if ( !currVar.prototype || util.isEmptyObject( currVar.prototype ) ) { - if ( !currVar.prototype || $.isEmptyObject( currVar.prototype ) ) { + if ( action === CompletenessTest.ACTION_INJECT ) { - // Inject check - that.injectCheck( masterVariable, parentPathArray, function() { - that.methodCallTracker[ parentPathArray.join( '.' ) ] = true; - } ); + that.injectionTracker[ parentPathArray.join( '.' ) ] = true; + that.injectCheck( masterVariable, parentPathArray, function () { + that.methodCallTracker[ parentPathArray.join( '.' ) ] = true; + } ); + } // We don't support checking object constructors yet... + // ...we can check the prototypes fine, though. } else { + if ( action === CompletenessTest.ACTION_INJECT ) { - // ... the prototypes are fine tho - $.each( currVar.prototype, function( key, value ) { - if ( key === 'constructor' ) return; + for ( key in currVar.prototype ) { + if ( hasOwn.call( currVar.prototype, key ) ) { + value = currVar.prototype[key]; + if ( key === 'constructor' ) { + continue; + } - // Clone and break reference to parentPathArray - var tmpPathArray = $.extend( [], parentPathArray ); - tmpPathArray.push( 'prototype' ); tmpPathArray.push( key ); + // Clone and break reference to parentPathArray + tmpPathArray = util.extend( [], parentPathArray ); + tmpPathArray.push( 'prototype' ); + tmpPathArray.push( key ); + + that.walkTheObject( key, value, masterVariable, tmpPathArray, action ); + } + } - that.checkTests( key, value, masterVariable, tmpPathArray, action ); - } ); + } } } - // Recursively. After all, this *is* the completeness test - } else if ( type === 'object' ) { - - $.each( currVar, function( key, value ) { - - // Clone and break reference to parentPathArray - var tmpPathArray = $.extend( [], parentPathArray ); - tmpPathArray.push( key ); + // Recursively. After all, this is the *completeness* test + if ( type === 'function' || type === 'object' ) { + for ( key in currVar ) { + if ( hasOwn.call( currVar, key ) ) { + value = currVar[key]; - that.checkTests( key, value, masterVariable, tmpPathArray, action ); + // Clone and break reference to parentPathArray + tmpPathArray = util.extend( [], parentPathArray ); + tmpPathArray.push( key ); - } ); + that.walkTheObject( key, value, masterVariable, tmpPathArray, action ); + } + } + } + }, - } - }, + populateMissingTests: function () { + var ct = this; + util.each( ct.injectionTracker, function ( key ) { + ct.hasTest( key ); + }); + }, + + /** + * CompletenessTest.fn.hasTest + * + * Checks if the given method name (ie. 'my.foo.bar') + * was called during the test suite (as far as the tracker knows). + * If not it adds it to missingTests. + * + * @param fnName {String} + * @return {Boolean} + */ + hasTest: function ( fnName ) { + if ( !( fnName in this.methodCallTracker ) ) { + this.missingTests[fnName] = true; + return false; + } + return true; + }, + + /** + * CompletenessTest.fn.injectCheck + * + * 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} + */ + injectCheck: function ( masterVariable, objectPathArray, injectFn ) { + var i, len, prev, memberName, lastMember, + curr = masterVariable; + + // Get the object in question through the path from the master variable, + // We can't pass the value directly because we need to re-define the object + // member and keep references to the parent object, member name and member + // value at all times. + for ( i = 0, len = objectPathArray.length; i < len; i++ ) { + memberName = objectPathArray[i]; + + prev = curr; + curr = prev[memberName]; + lastMember = memberName; + } - /** - * CompletenessTest.fn.hasTest - * - * Checks if the given method name (ie. 'my.foo.bar') - * was called during the test suite (as far as the tracker knows). - * If not it adds it to missingTests. - * - * @param fnName {String} - * @return {Boolean} - */ - hasTest: function( fnName ) { - if ( !( fnName in this.methodCallTracker ) ) { - this.missingTests[fnName] = true; - return false; + // Objects are by reference, members (unless objects) are not. + prev[lastMember] = function () { + injectFn(); + return curr.apply( this, arguments ); + }; } - return true; - }, - - /** - * CompletenessTest.fn.injectCheck - * - * 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} - */ - injectCheck: function( masterVariable, objectPathArray, injectFn ) { - var prev, - curr = masterVariable, - lastMember; - - $.each( objectPathArray, function( i, memberName ) { - prev = curr; - curr = prev[memberName]; - lastMember = memberName; - }); - - // Objects are by reference, members (unless objects) are not. - prev[lastMember] = function() { - injectFn(); - return curr.apply( this, arguments ); - }; - } -}; + }; -window.CompletenessTest = CompletenessTest; + /* Expose */ + window.CompletenessTest = CompletenessTest; -} )( jQuery ); +}( jQuery ) ); diff --git a/resources/jquery/jquery.qunit.css b/resources/jquery/jquery.qunit.css index bcecc4c0..55970e00 100644 --- a/resources/jquery/jquery.qunit.css +++ b/resources/jquery/jquery.qunit.css @@ -1,11 +1,11 @@ /** - * QUnit v1.2.0 - A JavaScript Unit Testing Framework + * QUnit v1.10.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ /** Font Family and Sizes */ @@ -20,7 +20,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -38,10 +38,10 @@ line-height: 1em; font-weight: normal; - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + -webkit-border-top-right-radius: 5px; + -webkit-border-top-left-radius: 5px; } #qunit-header a { @@ -54,6 +54,11 @@ color: #fff; } +#qunit-testrunner-toolbar label { + display: inline-block; + padding: 0 .5em 0 .1em; +} + #qunit-banner { height: 5px; } @@ -62,6 +67,7 @@ padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; + overflow: hidden; } #qunit-userAgent { @@ -71,6 +77,9 @@ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +#qunit-modulefilter-container { + float: right; +} /** Tests: Pass/Fail */ @@ -108,13 +117,9 @@ background-color: #fff; - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; } #qunit-tests table { @@ -157,8 +162,7 @@ #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; + padding: 5px; background-color: #fff; border-bottom: none; list-style-position: inside; @@ -167,9 +171,9 @@ /*** Passing Styles */ #qunit-tests li li.pass { - color: #5E740B; + color: #3c510c; background-color: #fff; - border-left: 26px solid #C6E746; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } @@ -185,15 +189,15 @@ #qunit-tests li li.fail { color: #710909; background-color: #fff; - border-left: 26px solid #EE5757; + border-left: 10px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; + border-radius: 0 0 5px 5px; + -moz-border-radius: 0 0 5px 5px; + -webkit-border-bottom-right-radius: 5px; + -webkit-border-bottom-left-radius: 5px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } @@ -216,6 +220,9 @@ border-bottom: 1px solid white; } +#qunit-testresult .module-name { + font-weight: bold; +} /** Fixture */ @@ -223,4 +230,6 @@ position: absolute; top: -10000px; left: -10000px; + width: 1000px; + height: 1000px; } diff --git a/resources/jquery/jquery.qunit.js b/resources/jquery/jquery.qunit.js index 6d2a8a7b..d4f17b5a 100644 --- a/resources/jquery/jquery.qunit.js +++ b/resources/jquery/jquery.qunit.js @@ -1,79 +1,98 @@ /** - * QUnit v1.2.0 - A JavaScript Unit Testing Framework + * QUnit v1.10.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ -(function(window) { +(function( window ) { -var defined = { +var QUnit, + config, + onErrorFnPrev, + testId = 0, + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, + defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { + var x = "qunit-test-string"; try { - return !!sessionStorage.getItem; - } catch(e) { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { return false; } - })() + }()) }; -var testId = 0, - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty; - -var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.testEnvironmentArg = testEnvironmentArg; - this.async = async; - this.callback = callback; +function Test( settings ) { + extend( this, settings ); this.assertions = []; -}; + this.testNumber = ++Test.count; +} + +Test.count = 0; + Test.prototype = { init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.className = "running"; - li.id = this.id = "test-output" + testId++; + var a, b, li, + tests = id( "qunit-tests" ); + + if ( tests ) { + b = document.createElement( "strong" ); + b.innerHTML = this.name; + + // `a` initialized at top of scope + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: this.testNumber }); + + li = document.createElement( "li" ); + li.appendChild( b ); + li.appendChild( a ); + li.className = "running"; + li.id = this.id = "qunit-test-output" + testId++; + tests.appendChild( li ); } }, setup: function() { - if (this.module != config.previousModule) { + if ( this.module !== config.previousModule ) { if ( config.previousModule ) { - runLoggingCallbacks('moduleDone', QUnit, { + runLoggingCallbacks( "moduleDone", QUnit, { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all - } ); + }); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( 'moduleStart', QUnit, { + runLoggingCallbacks( "moduleStart", QUnit, { name: this.module - } ); + }); + } else if ( config.autorun ) { + runLoggingCallbacks( "moduleStart", QUnit, { + name: this.module + }); } config.current = this; + this.testEnvironment = extend({ setup: function() {}, teardown: function() {} - }, this.moduleTestEnvironment); - if (this.testEnvironmentArg) { - extend(this.testEnvironment, this.testEnvironmentArg); - } + }, this.moduleTestEnvironment ); - runLoggingCallbacks( 'testStart', QUnit, { + runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module }); @@ -82,31 +101,41 @@ Test.prototype = { // TODO why?? QUnit.current_testEnvironment = this.testEnvironment; + if ( !config.pollution ) { + saveGlobal(); + } + if ( config.notrycatch ) { + this.testEnvironment.setup.call( this.testEnvironment ); + return; + } try { - if ( !config.pollution ) { - saveGlobal(); - } - - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + this.testEnvironment.setup.call( this.testEnvironment ); + } catch( e ) { + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); } }, run: function() { config.current = this; + + var running = id( "qunit-testresult" ); + + if ( running ) { + running.innerHTML = "Running: <br/>" + this.name; + } + if ( this.async ) { QUnit.stop(); } if ( config.notrycatch ) { - this.callback.call(this.testEnvironment); + this.callback.call( this.testEnvironment, QUnit.assert ); return; } + try { - this.callback.call(this.testEnvironment); - } catch(e) { - fail("Test " + this.testName + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + this.callback.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -118,34 +147,46 @@ Test.prototype = { }, teardown: function() { config.current = this; - try { - this.testEnvironment.teardown.call(this.testEnvironment); - checkPollution(); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + if ( config.notrycatch ) { + this.testEnvironment.teardown.call( this.testEnvironment ); + return; + } else { + try { + this.testEnvironment.teardown.call( this.testEnvironment ); + } catch( e ) { + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); + } } + checkPollution(); }, finish: function() { config.current = this; - if ( this.expected != null && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + if ( config.requireExpects && this.expected == null ) { + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + } else if ( this.expected != null && this.expected != this.assertions.length ) { + QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + } else if ( this.expected == null && !this.assertions.length ) { + QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); } - var good = 0, bad = 0, - tests = id("qunit-tests"); + var assertion, a, b, i, li, ol, + test = this, + good = 0, + bad = 0, + tests = id( "qunit-tests" ); config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { - var ol = document.createElement("ol"); + ol = document.createElement( "ol" ); - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; + for ( i = 0; i < this.assertions.length; i++ ) { + assertion = this.assertions[i]; - var li = document.createElement("li"); + li = document.createElement( "li" ); li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); ol.appendChild( li ); if ( assertion.result ) { @@ -159,49 +200,48 @@ Test.prototype = { // store result when possible if ( QUnit.config.reorder && defined.sessionStorage ) { - if (bad) { - sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); } else { - sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); + sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); } } - if (bad == 0) { + if ( bad === 0 ) { ol.style.display = "none"; } - var b = document.createElement("strong"); + // `b` initialized at top of scope + b = document.createElement( "strong" ); b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; - var a = document.createElement("a"); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - addEvent(b, "click", function() { var next = b.nextSibling.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); - addEvent(b, "dblclick", function(e) { + addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + window.location = QUnit.url({ testNumber: test.testNumber }); } }); - var li = id(this.id); + // `li` initialized at top of scope + li = id( this.id ); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); + a = li.firstChild; li.appendChild( b ); - li.appendChild( a ); + li.appendChild ( a ); li.appendChild( ol ); } else { - for ( var i = 0; i < this.assertions.length; i++ ) { + for ( i = 0; i < this.assertions.length; i++ ) { if ( !this.assertions[i].result ) { bad++; config.stats.bad++; @@ -210,23 +250,23 @@ Test.prototype = { } } - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); - } - - runLoggingCallbacks( 'testDone', QUnit, { + runLoggingCallbacks( "testDone", QUnit, { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length - } ); + }); + + QUnit.reset(); + + config.current = undefined; }, queue: function() { - var test = this; + var bad, + test = this; + synchronize(function() { test.init(); }); @@ -245,214 +285,295 @@ Test.prototype = { test.finish(); }); } + + // `bad` initialized at top of scope // defer when previous test run passed, if storage is available - var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); - if (bad) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + + if ( bad ) { run(); } else { - synchronize(run, true); - }; + synchronize( run, true ); + } } - }; -var QUnit = { +// Root QUnit object. +// `QUnit` initialized at top of scope +QUnit = { // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { + module: function( name, testEnvironment ) { config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; }, - asyncTest: function(testName, expected, callback) { + asyncTest: function( testName, expected, callback ) { if ( arguments.length === 2 ) { callback = expected; expected = null; } - QUnit.test(testName, expected, callback, true); + QUnit.test( testName, expected, callback, true ); }, - test: function(testName, expected, callback, async) { - var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg; + test: function( testName, expected, callback, async ) { + var test, + name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>"; if ( arguments.length === 2 ) { callback = expected; expected = null; } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } if ( config.currentModule ) { - name = '<span class="module-name">' + config.currentModule + "</span>: " + name; + name = "<span class='module-name'>" + config.currentModule + "</span>: " + name; } - if ( !validTest(config.currentModule + ": " + testName) ) { + test = new Test({ + name: name, + testName: testName, + expected: expected, + async: async, + callback: callback, + module: config.currentModule, + moduleTestEnvironment: config.currentModuleTestEnvironment, + stack: sourceFromStacktrace( 2 ) + }); + + if ( !validTest( test ) ) { return; } - var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; test.queue(); }, - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { - config.current.expected = asserts; + // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; + } }, + start: function( count ) { + config.semaphore -= count || 1; + // don't start until equal number of stop-calls + if ( config.semaphore > 0 ) { + return; + } + // ignore if start is called more often then stop + if ( config.semaphore < 0 ) { + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if ( config.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + config.blocking = false; + process( true ); + }, 13); + } else { + config.blocking = false; + process( true ); + } + }, + + stop: function( count ) { + config.semaphore += count || 1; + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + config.semaphore = 1; + QUnit.start(); + }, config.testTimeout ); + } + } +}; + +// Asssert helpers +// All of these must call either QUnit.push() or manually do: +// - runLoggingCallbacks( "log", .. ); +// - config.current.assertions.push({ .. }); +QUnit.assert = { /** - * Asserts true. + * Asserts rough true-ish result. + * @name ok + * @function * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ - ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeInnerText(msg); - runLoggingCallbacks( 'log', QUnit, details ); + ok: function( result, msg ) { + if ( !config.current ) { + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + result = !!result; + + var source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: msg + }; + + msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); + msg = "<span class='test-message'>" + msg + "</span>"; + + if ( !result ) { + source = sourceFromStacktrace( 2 ); + if ( source ) { + details.source = source; + msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; + } + } + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ - result: a, + result: result, message: msg }); }, /** - * Checks that the first two arguments are equal, with an optional message. + * Assert that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) + * @name equal + * @function + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); + equal: function( actual, expected, message ) { + QUnit.push( expected == actual, actual, expected, message ); }, - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); + /** + * @name notEqual + * @function + */ + notEqual: function( actual, expected, message ) { + QUnit.push( expected != actual, actual, expected, message ); }, - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + /** + * @name deepEqual + * @function + */ + deepEqual: function( actual, expected, message ) { + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); }, - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + /** + * @name notDeepEqual + * @function + */ + notDeepEqual: function( actual, expected, message ) { + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); }, - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); + /** + * @name strictEqual + * @function + */ + strictEqual: function( actual, expected, message ) { + QUnit.push( expected === actual, actual, expected, message ); }, - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); + /** + * @name notStrictEqual + * @function + */ + notStrictEqual: function( actual, expected, message ) { + QUnit.push( expected !== actual, actual, expected, message ); }, - raises: function(block, expected, message) { - var actual, ok = false; + throws: function( block, expected, message ) { + var actual, + ok = false; - if (typeof expected === 'string') { + // 'expected' is optional + if ( typeof expected === "string" ) { message = expected; expected = null; } + config.current.ignoreGlobalErrors = true; try { - block(); + block.call( config.current.testEnvironment ); } catch (e) { actual = e; } + config.current.ignoreGlobalErrors = false; - if (actual) { + if ( actual ) { // we don't want to validate thrown error - if (!expected) { + if ( !expected ) { ok = true; // expected is a regexp - } else if (QUnit.objectType(expected) === "regexp") { - ok = expected.test(actual); + } else if ( QUnit.objectType( expected ) === "regexp" ) { + ok = expected.test( actual ); // expected is a constructor - } else if (actual instanceof expected) { + } else if ( actual instanceof expected ) { ok = true; // expected is a validation function which returns true is validation passed - } else if (expected.call({}, actual) === true) { + } else if ( expected.call( {}, actual ) === true ) { ok = true; } - } - QUnit.ok(ok, message); - }, - - start: function(count) { - config.semaphore -= count || 1; - if (config.semaphore > 0) { - // don't start until equal number of stop-calls - return; - } - if (config.semaphore < 0) { - // ignore if start is called more often then stop - config.semaphore = 0; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if (config.semaphore > 0) { - return; - } - if ( config.timeout ) { - clearTimeout(config.timeout); - } - - config.blocking = false; - process(true); - }, 13); + QUnit.push( ok, actual, null, message ); } else { - config.blocking = false; - process(true); + QUnit.pushFailure( message, null, 'No exception was thrown.' ); } - }, + } +}; - stop: function(count) { - config.semaphore += count || 1; - config.blocking = true; +/** + * @deprecate since 1.8.0 + * Kept assertion helpers in root for backwards compatibility + */ +extend( QUnit, QUnit.assert ); - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout(config.timeout); - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout); - } - } +/** + * @deprecated since 1.9.0 + * Kept global "raises()" for backwards compatibility + */ +QUnit.raises = QUnit.assert.throws; + +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.equals = function() { + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); +}; +QUnit.same = function() { + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); }; -//We want access to the constructor's prototype +// We want access to the constructor's prototype (function() { - function F(){}; + function F() {} F.prototype = QUnit; QUnit = new F(); - //Make F QUnit's constructor so that we can add to the prototype later + // Make F QUnit's constructor so that we can add to the prototype later QUnit.constructor = F; -})(); +}()); -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; - -// Maintain internal state -var config = { +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +config = { // The queue of tests to run queue: [], @@ -470,9 +591,28 @@ var config = { // by default, modify document.title when suite is done altertitle: true, - urlConfig: ['noglobals', 'notrycatch'], + // when enabled, all tests must call expect() + requireExpects: false, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: {}, - //logging callback queues + // logging callback queues begin: [], done: [], log: [], @@ -482,16 +622,17 @@ var config = { moduleDone: [] }; -// Load paramaters +// Initialize more QUnit.config and QUnit.urlParams (function() { - var location = window.location || { search: "", protocol: "file:" }, + var i, + location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, urlParams = {}, current; if ( params[ 0 ] ) { - for ( var i = 0; i < length; i++ ) { + for ( i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); // allow just a key to turn on a flag, e.g., test.html?noglobals @@ -501,32 +642,39 @@ var config = { } QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName config.filter = urlParams.filter; + // Exact match of the module name + config.module = urlParams.module; + + config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; + // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); + QUnit.isLocal = location.protocol === "file:"; +}()); + +// Export global variables, unless an 'exports' object exists, +// in that case we assume we're in CommonJS (dealt with on the bottom of the script) +if ( typeof exports === "undefined" ) { + extend( window, QUnit ); -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); + // Expose QUnit object window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; } -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { +// Extend QUnit object, +// these after set here because they should not be exposed as global functions +extend( QUnit, { config: config, // Initialize the configuration options init: function() { - extend(config, { + extend( config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, - started: +new Date, + started: +new Date(), updateRate: 1000, blocking: false, autostart: true, @@ -536,9 +684,21 @@ extend(QUnit, { semaphore: 0 }); - var tests = id( "qunit-tests" ), - banner = id( "qunit-banner" ), - result = id( "qunit-testresult" ); + var tests, banner, result, + qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" + + "<h2 id='qunit-banner'></h2>" + + "<div id='qunit-testrunner-toolbar'></div>" + + "<h2 id='qunit-userAgent'></h2>" + + "<ol id='qunit-tests'></ol>"; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; @@ -557,43 +717,29 @@ extend(QUnit, { result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); - result.innerHTML = 'Running...<br/> '; + result.innerHTML = "Running...<br/> "; } }, - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. - */ + // Resets the test setup. Useful for tests that modify the DOM. reset: function() { - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } }, - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ + // Trigger an event on an element. + // @example triggerEvent( document.body, "click" ); triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); + event = document.createEvent( "MouseEvents" ); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); + elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); + elem.fireEvent( "on" + type ); } }, @@ -603,65 +749,76 @@ extend(QUnit, { }, objectType: function( obj ) { - if (typeof obj === "undefined") { + if ( typeof obj === "undefined" ) { return "undefined"; - // consider: typeof null === object } - if (obj === null) { + if ( obj === null ) { return "null"; } - var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; + var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); + switch ( type ) { + case "Number": + if ( isNaN(obj) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Date": + case "RegExp": + case "Function": + return type.toLowerCase(); } - if (typeof obj === "object") { - return "object"; + if ( typeof obj === "object" ) { + return "object"; } return undefined; }, - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; + push: function( result, actual, expected, message ) { + if ( !config.current ) { + throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); + } - message = escapeInnerText(message) || (result ? "okay" : "failed"); - message = '<span class="test-message">' + message + "</span>"; - expected = escapeInnerText(QUnit.jsDump.parse(expected)); - actual = escapeInnerText(QUnit.jsDump.parse(actual)); - var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; - if (actual != expected) { - output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; - output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; - } - if (!result) { - var source = sourceFromStacktrace(); - if (source) { + var output, source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); + message = "<span class='test-message'>" + message + "</span>"; + output = message; + + if ( !result ) { + expected = escapeInnerText( QUnit.jsDump.parse(expected) ); + actual = escapeInnerText( QUnit.jsDump.parse(actual) ); + output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; + + if ( actual != expected ) { + output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; + output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; + } + + source = sourceFromStacktrace(); + + if ( source ) { details.source = source; - output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; + output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; } + + output += "</table>"; } - output += "</table>"; - runLoggingCallbacks( 'log', QUnit, details ); + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: !!result, @@ -669,10 +826,49 @@ extend(QUnit, { }); }, + pushFailure: function( message, source, actual ) { + if ( !config.current ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + + var output, + details = { + module: config.current.module, + name: config.current.testName, + result: false, + message: message + }; + + message = escapeInnerText( message ) || "error"; + message = "<span class='test-message'>" + message + "</span>"; + output = message; + + output += "<table>"; + + if ( actual ) { + output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>"; + } + + if ( source ) { + details.source = source; + output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; + } + + output += "</table>"; + + runLoggingCallbacks( "log", QUnit, details ); + + config.current.assertions.push({ + result: false, + message: output + }); + }, + url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); - var querystring = "?", - key; + var key, + querystring = "?"; + for ( key in params ) { if ( !hasOwn.call( params, key ) ) { continue; @@ -686,27 +882,39 @@ extend(QUnit, { extend: extend, id: id, addEvent: addEvent + // load, equiv, jsDump, diff: Attached later }); -//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later -//Doing this allows us to tell if the following methods have been overwritten on the actual -//QUnit object, which is a deprecated way of using the callbacks. -extend(QUnit.constructor.prototype, { +/** + * @deprecated: Created for backwards compatibility with test runner that set the hook function + * into QUnit.{hook}, instead of invoking it and passing the hook function. + * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. + * Doing this allows us to tell if the following methods have been overwritten on the actual + * QUnit object. + */ +extend( QUnit.constructor.prototype, { + // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes - begin: registerLoggingCallback('begin'), + begin: registerLoggingCallback( "begin" ), + // done: { failed, passed, total, runtime } - done: registerLoggingCallback('done'), + done: registerLoggingCallback( "done" ), + // log: { result, actual, expected, message } - log: registerLoggingCallback('log'), + log: registerLoggingCallback( "log" ), + // testStart: { name } - testStart: registerLoggingCallback('testStart'), + testStart: registerLoggingCallback( "testStart" ), + // testDone: { name, failed, passed, total } - testDone: registerLoggingCallback('testDone'), + testDone: registerLoggingCallback( "testDone" ), + // moduleStart: { name } - moduleStart: registerLoggingCallback('moduleStart'), + moduleStart: registerLoggingCallback( "moduleStart" ), + // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback('moduleDone') + moduleDone: registerLoggingCallback( "moduleDone" ) }); if ( typeof document === "undefined" || document.readyState === "complete" ) { @@ -714,90 +922,164 @@ if ( typeof document === "undefined" || document.readyState === "complete" ) { } QUnit.load = function() { - runLoggingCallbacks( 'begin', QUnit, {} ); + runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); + var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, + numModules = 0, + moduleFilterHtml = "", + urlConfigHtml = "", + oldconfig = extend( {}, config ); + QUnit.init(); extend(config, oldconfig); config.blocking = false; - var urlConfigHtml = '', len = config.urlConfig.length; - for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { - config[val] = QUnit.urlParams[val]; - urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>'; + len = config.urlConfig.length; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[i]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val, + tooltip: "[no tooltip available]" + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; + } + + moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>"; + for ( i in config.modules ) { + if ( config.modules.hasOwnProperty( i ) ) { + numModules += 1; + moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>"; + } } + moduleFilterHtml += "</select>"; - var userAgent = id("qunit-userAgent"); + // `userAgent` initialized at top of scope + userAgent = id( "qunit-userAgent" ); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } - var banner = id("qunit-header"); + + // `banner` initialized at top of scope + banner = id( "qunit-header" ); if ( banner ) { - banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); + banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> "; } - var toolbar = id("qunit-testrunner-toolbar"); + // `toolbar` initialized at top of scope + toolbar = id( "qunit-testrunner-toolbar" ); if ( toolbar ) { - var filter = document.createElement("input"); + // `filter` initialized at top of scope + filter = document.createElement( "input" ); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { - var ol = document.getElementById("qunit-tests"); + var tmp, + ol = document.getElementById( "qunit-tests" ); + if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { - var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace(/ hidepass /, " "); + tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace( / hidepass /, " " ); } if ( defined.sessionStorage ) { if (filter.checked) { - sessionStorage.setItem("qunit-filter-passed-tests", "true"); + sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); } else { - sessionStorage.removeItem("qunit-filter-passed-tests"); + sessionStorage.removeItem( "qunit-filter-passed-tests" ); } } }); - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + + if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { filter.checked = true; - var ol = document.getElementById("qunit-tests"); + // `ol` initialized at top of scope + ol = document.getElementById( "qunit-tests" ); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); + // `label` initialized at top of scope + label = document.createElement( "label" ); + label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + urlConfigCheckboxes = document.createElement( 'span' ); + urlConfigCheckboxes.innerHTML = urlConfigHtml; + addEvent( urlConfigCheckboxes, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigCheckboxes ); + + if (numModules > 1) { + moduleFilter = document.createElement( 'span' ); + moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); + }); + toolbar.appendChild(moduleFilter); + } } - var main = id('qunit-fixture'); + // `main` initialized at top of scope + main = id( "qunit-fixture" ); if ( main ) { config.fixture = main.innerHTML; } - if (config.autostart) { + if ( config.autostart ) { QUnit.start(); } }; -addEvent(window, "load", QUnit.load); +addEvent( window, "load", QUnit.load ); -// addEvent(window, "error") gives us a useless event object -window.onerror = function( message, file, line ) { - if ( QUnit.config.current ) { - ok( false, message + ", " + file + ":" + line ); - } else { - test( "global failure", function() { - ok( false, message + ", " + file + ":" + line ); - }); +// `onErrorFnPrev` initialized at top of scope +// Preserve other handlers +onErrorFnPrev = window.onerror; + +// Cover uncaught exceptions +// Returning true will surpress the default browser handler, +// returning false will let it run. +window.onerror = function ( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); } + + // Treat return value as window.onerror itself does, + // Only do our handling if not surpressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: validTest } ) ); + } + return false; + } + + return ret; }; function done() { @@ -805,33 +1087,34 @@ function done() { // Log the last module results if ( config.currentModule ) { - runLoggingCallbacks( 'moduleDone', QUnit, { + runLoggingCallbacks( "moduleDone", QUnit, { name: config.currentModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all - } ); + }); } - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - runtime = +new Date - config.started, + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + runtime = +new Date() - config.started, passed = config.stats.all - config.stats.bad, html = [ - 'Tests completed in ', + "Tests completed in ", runtime, - ' milliseconds.<br/>', - '<span class="passed">', + " milliseconds.<br/>", + "<span class='passed'>", passed, - '</span> tests of <span class="total">', + "</span> tests of <span class='total'>", config.stats.all, - '</span> passed, <span class="failed">', + "</span> passed, <span class='failed'>", config.stats.bad, - '</span> failed.' - ].join(''); + "</span> failed." + ].join( "" ); if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); } if ( tests ) { @@ -842,70 +1125,130 @@ function done() { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset document.title = [ - (config.stats.bad ? "\u2716" : "\u2714"), - document.title.replace(/^[\u2714\u2716] /i, "") - ].join(" "); + ( config.stats.bad ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { + // `key` & `i` initialized at top of scope + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( window.scrollTo ) { + window.scrollTo(0, 0); } - runLoggingCallbacks( 'done', QUnit, { + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime - } ); + }); } -function validTest( name ) { - var filter = config.filter, - run = false; +/** @return Boolean: true if this test should be ran */ +function validTest( test ) { + var include, + filter = config.filter && config.filter.toLowerCase(), + module = config.module && config.module.toLowerCase(), + fullName = (test.module + ": " + test.testName).toLowerCase(); - if ( !filter ) { + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; return true; } - var not = filter.charAt( 0 ) === "!"; - if ( not ) { - filter = filter.slice( 1 ); + if ( config.testNumber ) { + return test.testNumber === config.testNumber; + } + + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { + return false; } - if ( name.indexOf( filter ) !== -1 ) { - return !not; + if ( !filter ) { + return true; + } + + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); } - if ( not ) { - run = true; + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; } - return run; + // Otherwise, do the opposite + return !include; } -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace() { +// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) +// Later Safari and IE10 are supposed to support error.stack as well +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 3 : offset; + + var stack, include, i, regex; + + if ( e.stacktrace ) { + // Opera + return e.stacktrace.split( "\n" )[ offset + 3 ]; + } else if ( e.stack ) { + // Firefox, Chrome + stack = e.stack.split( "\n" ); + if (/^error$/i.test( stack[0] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) != -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + } else if ( e.sourceURL ) { + // Safari, PhantomJS + // hopefully one day Safari provides actual stacktraces + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} +function sourceFromStacktrace( offset ) { try { throw new Error(); } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[6]; - } else if (e.stack) { - // Firefox, Chrome - return e.stack.split("\n")[4]; - } else if (e.sourceURL) { - // Safari, PhantomJS - // TODO sourceURL points at the 'throw new Error' line above, useless - //return e.sourceURL + ":" + e.line; - } + return extractStacktrace( e, offset ); } } -function escapeInnerText(s) { - if (!s) { +function escapeInnerText( s ) { + if ( !s ) { return ""; } s = s + ""; - return s.replace(/[\&<>]/g, function(s) { - switch(s) { + return s.replace( /[\&<>]/g, function( s ) { + switch( s ) { case "&": return "&"; case "<": return "<"; case ">": return ">"; @@ -918,11 +1261,14 @@ function synchronize( callback, last ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { - process(last); + process( last ); } } function process( last ) { + function next() { + process( last ); + } var start = new Date().getTime(); config.depth = config.depth ? config.depth + 1 : 1; @@ -930,9 +1276,7 @@ function process( last ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { - window.setTimeout( function(){ - process( last ); - }, 13 ); + window.setTimeout( next, 13 ); break; } } @@ -947,7 +1291,8 @@ function saveGlobal() { if ( config.noglobals ) { for ( var key in window ) { - if ( !hasOwn.call( window, key ) ) { + // in Opera sometimes DOM element ids show up here, ignore them + if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { continue; } config.pollution.push( key ); @@ -956,27 +1301,32 @@ function saveGlobal() { } function checkPollution( name ) { - var old = config.pollution; + var newGlobals, + deletedGlobals, + old = config.pollution; + saveGlobal(); - var newGlobals = diff( config.pollution, old ); + newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); } - var deletedGlobals = diff( old, config.pollution ); + deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { - result.splice(i, 1); + result.splice( i, 1 ); i--; break; } @@ -985,32 +1335,21 @@ function diff( a, b ) { return result; } -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { +function extend( a, b ) { for ( var prop in b ) { - if ( b[prop] === undefined ) { - delete a[prop]; + if ( b[ prop ] === undefined ) { + delete a[ prop ]; // Avoid "Member not found" error in IE8 caused by setting window.constructor } else if ( prop !== "constructor" || a !== window ) { - a[prop] = b[prop]; + a[ prop ] = b[ prop ]; } } return a; } -function addEvent(elem, type, fn) { +function addEvent( elem, type, fn ) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { @@ -1020,215 +1359,215 @@ function addEvent(elem, type, fn) { } } -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && +function id( name ) { + return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); } -function registerLoggingCallback(key){ - return function(callback){ +function registerLoggingCallback( key ) { + return function( callback ) { config[key].push( callback ); }; } // Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks(key, scope, args) { +function runLoggingCallbacks( key, scope, args ) { //debugger; - var callbacks; - if ( QUnit.hasOwnProperty(key) ) { - QUnit[key].call(scope, args); + var i, callbacks; + if ( QUnit.hasOwnProperty( key ) ) { + QUnit[ key ].call(scope, args ); } else { - callbacks = config[key]; - for( var i = 0; i < callbacks.length; i++ ) { - callbacks[i].call( scope, args ); + callbacks = config[ key ]; + for ( i = 0; i < callbacks.length; i++ ) { + callbacks[ i ].call( scope, args ); } } } // Test for equality any JavaScript type. // Author: Philippe Rathé <prathe@gmail.com> -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing +QUnit.equiv = (function() { // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); + function bindCallbacks( o, callbacks, args ) { + var prop = QUnit.objectType( o ); + if ( prop ) { + if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { + return callbacks[ prop ].apply( callbacks, args ); } else { - return callbacks[prop]; // or undefined + return callbacks[ prop ]; // or undefined } } } - var getProto = Object.getPrototypeOf || function (obj) { - return obj.__proto__; - }; - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } + // the real equiv function + var innerEquiv, + // stack to decide between skip/abort functions + callers = [], + // stack to avoiding loops from circular referencing + parents = [], - return { - "string" : useStrictEquality, - "boolean" : useStrictEquality, - "number" : useStrictEquality, - "null" : useStrictEquality, - "undefined" : useStrictEquality, - - "nan" : function(b) { - return isNaN(b); - }, - - "date" : function(b, a) { - return QUnit.objectType(b) === "date" - && a.valueOf() === b.valueOf(); - }, - - "regexp" : function(b, a) { - return QUnit.objectType(b) === "regexp" - && a.source === b.source && // the regex itself - a.global === b.global && // and its modifers - // (gmi) ... - a.ignoreCase === b.ignoreCase - && a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function" : function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array" : function(b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if (!(QUnit.objectType(b) === "array")) { - return false; - } - - len = a.length; - if (len !== b.length) { // safe and faster - return false; + getProto = Object.getPrototypeOf || function ( obj ) { + return obj.__proto__; + }, + callbacks = (function () { + + // for string, boolean, number and null + function useStrictEquality( b, a ) { + if ( b instanceof a.constructor || a instanceof b.constructor ) { + // to catch short annotaion VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; } + } - // track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for (j = 0; j < parents.length; j++) { - if (parents[j] === a[i]) { - loop = true;// dont rewalk array - } - } - if (!loop && !innerEquiv(a[i], b[i])) { - parents.pop(); + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function( b ) { + return isNaN( b ); + }, + + "date": function( b, a ) { + return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function( b, a ) { + return QUnit.objectType( b ) === "regexp" && + // the regex itself + a.source === b.source && + // and its modifers + a.global === b.global && + // (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline && + a.sticky === b.sticky; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[callers.length - 1]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop; + + // b could be an object literal here + if ( QUnit.objectType( b ) !== "array" ) { return false; } - } - parents.pop(); - return true; - }, - "object" : function(b, a) { - var i, j, loop; - var eq = true; // unless we can proove it - var aProperties = [], bProperties = []; // collection of - // strings - - // comparing constructors is more strict than using - // instanceof - if (a.constructor !== b.constructor) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if (!((getProto(a) === null && getProto(b) === Object.prototype) || - (getProto(b) === null && getProto(a) === Object.prototype))) - { + len = a.length; + if ( len !== b.length ) { + // safe and faster return false; } - } - // stack constructor before traversing properties - callers.push(a.constructor); - // track reference to avoid circular references - parents.push(a); - - for (i in a) { // be strict: don't ensures hasOwnProperty - // and go deep - loop = false; - for (j = 0; j < parents.length; j++) { - if (parents[j] === a[i]) - loop = true; // don't go down the same path - // twice + // track reference to avoid circular references + parents.push( a ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + if ( parents[j] === a[i] ) { + loop = true;// dont rewalk array + } + } + if ( !loop && !innerEquiv(a[i], b[i]) ) { + parents.pop(); + return false; + } } - aProperties.push(i); // collect a's properties + parents.pop(); + return true; + }, + + "object": function( b, a ) { + var i, j, loop, + // Default to true + eq = true, + aProperties = [], + bProperties = []; + + // comparing constructors is more strict than using + // instanceof + if ( a.constructor !== b.constructor ) { + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || + ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { + return false; + } + } + + // stack constructor before traversing properties + callers.push( a.constructor ); + // track reference to avoid circular references + parents.push( a ); + + for ( i in a ) { // be strict: don't ensures hasOwnProperty + // and go deep + loop = false; + for ( j = 0; j < parents.length; j++ ) { + if ( parents[j] === a[i] ) { + // don't go down the same path twice + loop = true; + } + } + aProperties.push(i); // collect a's properties - if (!loop && !innerEquiv(a[i], b[i])) { - eq = false; - break; + if (!loop && !innerEquiv( a[i], b[i] ) ) { + eq = false; + break; + } } - } - callers.pop(); // unstack, we are done - parents.pop(); + callers.pop(); // unstack, we are done + parents.pop(); - for (i in b) { - bProperties.push(i); // collect b's properties - } + for ( i in b ) { + bProperties.push( i ); // collect b's properties + } - // Ensures identical properties name - return eq - && innerEquiv(aProperties.sort(), bProperties - .sort()); - } - }; - }(); + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + }()); innerEquiv = function() { // can take multiple arguments - var args = Array.prototype.slice.apply(arguments); - if (args.length < 2) { + var args = [].slice.apply( arguments ); + if ( args.length < 2 ) { return true; // end transition } - return (function(a, b) { - if (a === b) { + return (function( a, b ) { + if ( a === b ) { return true; // catch the most you can - } else if (a === null || b === null || typeof a === "undefined" - || typeof b === "undefined" - || QUnit.objectType(a) !== QUnit.objectType(b)) { + } else if ( a === null || b === null || typeof a === "undefined" || + typeof b === "undefined" || + QUnit.objectType(a) !== QUnit.objectType(b) ) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [ b, a ]); } // apply transition with (1..n) arguments - })(args[0], args[1]) - && arguments.callee.apply(this, args.splice(1, - args.length - 1)); + }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); }; return innerEquiv; - -}(); +}()); /** * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | @@ -1242,188 +1581,215 @@ QUnit.equiv = function () { */ QUnit.jsDump = (function() { function quote( str ) { - return '"' + str.toString().replace(/"/g, '\\"') + '"'; - }; + return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; + } function literal( o ) { - return o + ''; - }; + return o + ""; + } function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); - if ( arr.join ) - arr = arr.join( ',' + s + inner ); - if ( !arr ) + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { return pre + post; + } return [ pre, inner + arr, base + post ].join(s); - }; + } function array( arr, stack ) { - var i = arr.length, ret = Array(i); + var i = arr.length, ret = new Array(i); this.up(); - while ( i-- ) + while ( i-- ) { ret[i] = this.parse( arr[i] , undefined , stack); + } this.down(); - return join( '[', ret, ']' ); - }; + return join( "[", ret, "]" ); + } - var reName = /^function (\w+)/; + var reName = /^function (\w+)/, + jsDump = { + parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance + stack = stack || [ ]; + var inStack, res, + parser = this.parsers[ type || this.typeOf(obj) ]; - var jsDump = { - parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance - stack = stack || [ ]; - var parser = this.parsers[ type || this.typeOf(obj) ]; - type = typeof parser; - var inStack = inArray(obj, stack); - if (inStack != -1) { - return 'recursion('+(inStack - stack.length)+')'; - } - //else - if (type == 'function') { - stack.push(obj); - var res = parser.call( this, obj, stack ); + type = typeof parser; + inStack = inArray( obj, stack ); + + if ( inStack != -1 ) { + return "recursion(" + (inStack - stack.length) + ")"; + } + //else + if ( type == "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); stack.pop(); return res; - } - // else - return (type == 'string') ? parser : this.parsers.error; - }, - typeOf:function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if (typeof obj === "undefined") { - type = "undefined"; - } else if (QUnit.is("RegExp", obj)) { - type = "regexp"; - } else if (QUnit.is("Date", obj)) { - type = "date"; - } else if (QUnit.is("Function", obj)) { - type = "function"; - } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { - type = "window"; - } else if (obj.nodeType === 9) { - type = "document"; - } else if (obj.nodeType) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - 'undefined':'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + } + // else + return ( type == "string" ) ? parser : this.parsers.error; + }, + typeOf: function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj) ) { + type = "date"; + } else if ( QUnit.is( "function", obj) ) { + type = "function"; + } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( + // native arrays + toString.call( obj ) === "[object Array]" || + // NodeList objects + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ) { + type = "array"; + } else { + type = typeof obj; + } + return type; }, - array: array, - nodelist: array, - arguments: array, - object:function( map, stack ) { - var ret = [ ]; - QUnit.jsDump.up(); - for ( var key in map ) { - var val = map[key]; - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); - } - QUnit.jsDump.down(); - return join( '{', ret, '}' ); + separator: function() { + return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; }, - node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; - - var tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) { + return ""; } - return ret + close + open + '/' + tag + close; + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + } + return new Array( this._depth_ + (extra||0) ).join(chr); }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; + up: function( a ) { + this._depth_ += a || 1; }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. - }; + down: function( a ) { + this._depth_ -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: "[ERROR]", //when no parser is found, shouldn"t happen + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE + + if ( name ) { + ret += " " + name; + } + ret += "( "; + + ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + var ret = [ ], keys, key, val, i; + QUnit.jsDump.up(); + if ( Object.keys ) { + keys = Object.keys( map ); + } else { + keys = []; + for ( key in map ) { + keys.push( key ); + } + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + } + QUnit.jsDump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var a, val, + open = QUnit.jsDump.HTML ? "<" : "<", + close = QUnit.jsDump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( a in QUnit.jsDump.DOMAttrs ) { + val = node[ QUnit.jsDump.DOMAttrs[a] ]; + if ( val ) { + ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); + } + } + return ret + close + open + "/" + tag + close; + }, + functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array(l); + while ( l-- ) { + args[l] = String.fromCharCode(97+l);//97 is 'a' + } + return " " + args.join( ", " ) + " "; + }, + key: quote, //object calls it internally, the key part of an item in a map + functionCode: "[code]", //function calls it internally, it's the content of the function + attribute: quote, //node calls it internally, it's an html attribute value + string: quote, + date: quote, + regexp: literal, //regex + number: literal, + "boolean": literal + }, + DOMAttrs: { + //attributes to dump from nodes, name=>realName + id: "id", + name: "name", + "class": "className" + }, + HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar: " ",//indentation unit + multiline: true //if true, items in a collection, are separated by a \n, else just a space. + }; return jsDump; -})(); +}()); // from Sizzle.js function getText( elems ) { - var ret = "", elem; + var i, elem, + ret = ""; - for ( var i = 0; elems[i]; i++ ) { + for ( i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes @@ -1437,9 +1803,9 @@ function getText( elems ) { } return ret; -}; +} -//from jquery.js +// from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); @@ -1466,70 +1832,75 @@ function inArray( elem, array ) { * * Usage: QUnit.diff(expected, actual) * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" + * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" */ QUnit.diff = (function() { - function diff(o, n) { - var ns = {}; - var os = {}; - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { + function diff( o, n ) { + var i, + ns = {}, + os = {}; + + for ( i = 0; i < n.length; i++ ) { + if ( ns[ n[i] ] == null ) { + ns[ n[i] ] = { rows: [], o: null }; - ns[n[i]].rows.push(i); + } + ns[ n[i] ].rows.push( i ); } - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { + for ( i = 0; i < o.length; i++ ) { + if ( os[ o[i] ] == null ) { + os[ o[i] ] = { rows: [], n: null }; - os[o[i]].rows.push(i); + } + os[ o[i] ].rows.push( i ); } - for (var i in ns) { + for ( i in ns ) { if ( !hasOwn.call( ns, i ) ) { continue; } - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], + if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { + n[ ns[i].rows[0] ] = { + text: n[ ns[i].rows[0] ], row: os[i].rows[0] }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], + o[ os[i].rows[0] ] = { + text: o[ os[i].rows[0] ], row: ns[i].rows[0] }; } } - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], + for ( i = 0; i < n.length - 1; i++ ) { + if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && + n[ i + 1 ] == o[ n[i].row + 1 ] ) { + + n[ i + 1 ] = { + text: n[ i + 1 ], row: n[i].row + 1 }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], + o[ n[i].row + 1 ] = { + text: o[ n[i].row + 1 ], row: i + 1 }; } } - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], + for ( i = n.length - 1; i > 0; i-- ) { + if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && + n[ i - 1 ] == o[ n[i].row - 1 ]) { + + n[ i - 1 ] = { + text: n[ i - 1 ], row: n[i].row - 1 }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], + o[ n[i].row - 1 ] = { + text: o[ n[i].row - 1 ], row: i - 1 }; } @@ -1541,49 +1912,52 @@ QUnit.diff = (function() { }; } - return function(o, n) { - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + return function( o, n ) { + o = o.replace( /\s+$/, "" ); + n = n.replace( /\s+$/, "" ); - var str = ""; + var i, pre, + str = "", + out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), + oSpace = o.match(/\s+/g), + nSpace = n.match(/\s+/g); - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; + if ( oSpace == null ) { + oSpace = [ " " ]; } else { - oSpace.push(" "); + oSpace.push( " " ); } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; + + if ( nSpace == null ) { + nSpace = [ " " ]; } else { - nSpace.push(" "); + nSpace.push( " " ); } - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '<del>' + out.o[i] + oSpace[i] + "</del>"; + if ( out.n.length === 0 ) { + for ( i = 0; i < out.o.length; i++ ) { + str += "<del>" + out.o[i] + oSpace[i] + "</del>"; } } else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '<del>' + out.o[n] + oSpace[n] + "</del>"; + if ( out.n[0].text == null ) { + for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { + str += "<del>" + out.o[n] + oSpace[n] + "</del>"; } } - for (var i = 0; i < out.n.length; i++) { + for ( i = 0; i < out.n.length; i++ ) { if (out.n[i].text == null) { - str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; + str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; } else { - var pre = ""; + // `pre` initialized at top of scope + pre = ""; - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; + for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { + pre += "<del>" + out.o[n] + oSpace[n] + "</del>"; } str += " " + out.n[i].text + nSpace[i] + pre; } @@ -1592,6 +1966,12 @@ QUnit.diff = (function() { return str; }; -})(); +}()); + +// for CommonJS enviroments, export everything +if ( typeof exports !== "undefined" ) { + extend(exports, QUnit); +} -})(this); +// get at whatever the global object is, like window in browsers +}( (function() {return this;}.call()) )); diff --git a/resources/jquery/jquery.spinner.css b/resources/jquery/jquery.spinner.css index 150a51b4..4a775283 100644 --- a/resources/jquery/jquery.spinner.css +++ b/resources/jquery/jquery.spinner.css @@ -1,12 +1,40 @@ .mw-spinner { + background-color: transparent; + background-position: center center; + background-repeat: no-repeat; +} + +.mw-spinner-small { /* @embed */ - background: transparent url(images/spinner.gif); + background-image: url(images/spinner.gif); height: 20px; width: 20px; + /* Avoid issues with .mw-spinner-block when floated without width. */ + min-width: 20px; +} + +.mw-spinner-large { + /* @embed */ + background-image: url(images/spinner-large.gif); + height: 32px; + width: 32px; + /* Avoid issues with .mw-spinner-block when floated without width. */ + min-width: 32px; +} + +.mw-spinner-block { + display: block; + /* This overrides width from .mw-spinner-large / .mw-spinner-small, + * This is where the min-width kicks in. + */ + width: 100%; +} + +.mw-spinner-inline { display: inline-block; vertical-align: middle; - /* IE < 8 Hacks */ + /* IE < 8 */ zoom: 1; *display: inline; -}
\ No newline at end of file +} diff --git a/resources/jquery/jquery.spinner.js b/resources/jquery/jquery.spinner.js index 87e45382..4a6ec3b4 100644 --- a/resources/jquery/jquery.spinner.js +++ b/resources/jquery/jquery.spinner.js @@ -3,42 +3,93 @@ * * Simple jQuery plugin to create, inject and remove spinners. */ -( function( $ ) { +( function ( $ ) { -$.extend( { - /** - * Creates a spinner element. - * - * @param id {String} id of the spinner - * @return {jQuery} spinner - */ - createSpinner: function( id ) { - return $( '<div>' ).attr( { - id: 'mw-spinner-' + id, - 'class': 'mw-spinner', - title: '...' - } ); - }, + // Default options for new spinners, + // stored outside the function to share between calls. + var defaults = { + id: undefined, + size: 'small', + type: 'inline' + }; + + $.extend({ + /** + * Creates a spinner element. + * + * The argument is an object with options used to construct the spinner. These can be: + * + * It is a good practice to keep a reference to the created spinner to be able to remove it later. + * Alternatively one can use the id option and removeSpinner() (but make sure to choose an id + * that's unlikely to cause conflicts, e.g. with extensions, gadgets or user scripts). + * + * CSS classes used: + * .mw-spinner for every spinner + * .mw-spinner-small / .mw-spinner-large for size + * .mw-spinner-block / .mw-spinner-inline for display types + * + * @example + * // Create a large spinner reserving all available horizontal space. + * var $spinner = $.createSpinner({ size: 'large', type: 'block' }); + * // Insert above page content. + * $( '#mw-content-text' ).prepend( $spinner ); + * @example + * // Place a small inline spinner next to the "Save" button + * var $spinner = $.createSpinner({ size: 'small', type: 'inline' }); + * // Alternatively, just `$.createSpinner();` as these are the default options. + * $( '#wpSave' ).after( $spinner ); + * @example + * // The following two are equivalent: + * $.createSpinner( 'magic' ); + * $.createSpinner({ id: 'magic' }); + * + * @param {Object|String} opts [optional] ID string or options: + * - id: If given, spinner will be given an id of "mw-spinner-<id>" + * - size: 'small' (default) or 'large' for a 20-pixel or 32-pixel spinner + * - type: 'inline' (default) or 'block'. Inline creates an inline-block with width and + * height equal to spinner size. Block is a block-level element with width 100%, height + * equal to spinner size. + * @return {jQuery} + */ + createSpinner: function ( opts ) { + if ( opts !== undefined && $.type( opts ) !== 'object' ) { + opts = { + id: opts + }; + } + + opts = $.extend( {}, defaults, opts ); + + var $spinner = $( '<div>', { 'class': 'mw-spinner', 'title': '...' } ); + if ( opts.id !== undefined ) { + $spinner.attr( 'id', 'mw-spinner-' + opts.id ); + } + + $spinner.addClass( opts.size === 'large' ? 'mw-spinner-large' : 'mw-spinner-small' ); + $spinner.addClass( opts.type === 'block' ? 'mw-spinner-block' : 'mw-spinner-inline' ); + + return $spinner; + }, + + /** + * Removes a spinner element. + * + * @param {String} id [optional] Id of the spinner, as passed to createSpinner. + * @return {jQuery} The (now detached) spinner. + */ + removeSpinner: function ( id ) { + return $( '#mw-spinner-' + id ).remove(); + } + }); /** - * Removes a spinner element. + * Injects a spinner after the elements in the jQuery collection + * (as siblings, not children). Collection contents remain unchanged. * - * @param id {String} - * @return {jQuery} spinner + * @param {Object} opts See createSpinner() for description. + * @return {jQuery} */ - removeSpinner: function( id ) { - return $( '#mw-spinner-' + id ).remove(); - } -} ); - -/** - * Injects a spinner after the elements in the jQuery collection. - * - * @param id String id of the spinner - * @return {jQuery} - */ -$.fn.injectSpinner = function( id ) { - return this.after( $.createSpinner( id ) ); -}; - -} )( jQuery ); + $.fn.injectSpinner = function ( opts ) { + return this.after( $.createSpinner( opts ) ); + }; +}( jQuery ) ); diff --git a/resources/jquery/jquery.suggestions.css b/resources/jquery/jquery.suggestions.css index 3cbdad22..e0ba647b 100644 --- a/resources/jquery/jquery.suggestions.css +++ b/resources/jquery/jquery.suggestions.css @@ -11,14 +11,15 @@ padding: 0; margin: -1px -1px 0 0; } + /* IGNORED BY IE6 */ html > body .suggestions { margin: -1px 0 0 0; } + .suggestions-special { position: relative; background-color: white; - font-size: 0.8em; cursor: pointer; border: solid 1px #aaaaaa; padding: 0; @@ -28,14 +29,15 @@ html > body .suggestions { padding: 0.25em 0.25em; line-height: 1.25em; } + .suggestions-results { background-color: white; - font-size: 0.8em; cursor: pointer; border: solid 1px #aaaaaa; padding: 0; margin: 0; } + .suggestions-result { color: black; margin: 0; @@ -43,28 +45,33 @@ html > body .suggestions { padding: 0.01em 0.25em; text-align: left; } + .suggestions-result-current { background-color: #4C59A6; color: white; } + .suggestions-special .special-label { - font-size: 0.8em; color: gray; text-align: left; } + .suggestions-special .special-query { color: black; font-style: italic; text-align: left; } + .suggestions-special .special-hover { background-color: silver; } + .suggestions-result-current .special-label, .suggestions-result-current .special-query { color: white; } + .autoellipsis-matched, .highlight { font-weight: bold; -}
\ No newline at end of file +} diff --git a/resources/jquery/jquery.suggestions.js b/resources/jquery/jquery.suggestions.js index 55c30010..d80680fc 100644 --- a/resources/jquery/jquery.suggestions.js +++ b/resources/jquery/jquery.suggestions.js @@ -36,20 +36,24 @@ * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set to e.g. 2, the suggestions box * will never be grown beyond 2 times the width of the textbox. * Type: Number, Range: 1 - infinity, Default: 3 - * positionFromLeft: Whether to position the suggestion box with the left attribute or the right + * expandFrom: Which direction to offset the suggestion box from. + * Values 'start' and 'end' translate to left and right respectively depending on the directionality + * of the current document, according to $( 'html' ).css( 'direction' ). + * Type: String, default: 'auto', options: 'left', 'right', 'start', 'end', 'auto'. + * positionFromLeft: Sets expandFrom=left, for backwards compatibility * Type: Boolean, Default: true * highlightInput: Whether to hightlight matched portions of the input or not * Type: Boolean, Default: false */ -( function( $ ) { +( function ( $ ) { $.suggestions = { /** * Cancel any delayed updateSuggestions() call and inform the user so * they can cancel their result fetching if they use AJAX or something */ - cancel: function( context ) { - if ( context.data.timerID != null ) { + cancel: function ( context ) { + if ( context.data.timerID !== null ) { clearTimeout( context.data.timerID ); } if ( $.isFunction( context.config.cancel ) ) { @@ -61,7 +65,7 @@ $.suggestions = { * restores the value the currently displayed suggestions are based on, rather than the value just before * highlight() overwrote it; the former is arguably slightly more sensible. */ - restore: function( context ) { + restore: function ( context ) { context.data.$textbox.val( context.data.prevText ); }, /** @@ -70,7 +74,7 @@ $.suggestions = { * function does nothing. * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time */ - update: function( context, delayed ) { + update: function ( context, delayed ) { // Only fetch if the value in the textbox changed and is not empty // if the textbox is empty then clear the result div, but leave other settings intouched function maybeFetch() { @@ -86,7 +90,7 @@ $.suggestions = { } // Cancel previous call - if ( context.data.timerID != null ) { + if ( context.data.timerID !== null ) { clearTimeout( context.data.timerID ); } if ( delayed ) { @@ -97,13 +101,13 @@ $.suggestions = { } $.suggestions.special( context ); }, - special: function( context ) { + special: function ( context ) { // Allow custom rendering - but otherwise don't do any rendering if ( typeof context.config.special.render === 'function' ) { // Wait for the browser to update the value - setTimeout( function() { + setTimeout( function () { // Render special - $special = context.data.$container.find( '.suggestions-special' ); + var $special = context.data.$container.find( '.suggestions-special' ); context.config.special.render.call( $special, context.data.$textbox.val() ); }, 1 ); } @@ -113,7 +117,8 @@ $.suggestions = { * @param property String Name of property * @param value Mixed Value to set property with */ - configure: function( context, property, value ) { + configure: function ( context, property, value ) { + var newCSS; // Validate creation using fallback values switch( property ) { case 'fetch': @@ -121,6 +126,7 @@ $.suggestions = { case 'special': case 'result': case '$region': + case 'expandFrom': context.config[property] = value; break; case 'suggestions': @@ -134,19 +140,77 @@ $.suggestions = { // Rebuild the suggestions list context.data.$container.show(); // Update the size and position of the list - var newCSS = { + newCSS = { top: context.config.$region.offset().top + context.config.$region.outerHeight(), bottom: 'auto', width: context.config.$region.outerWidth(), height: 'auto' }; - if ( context.config.positionFromLeft ) { + + // Process expandFrom, after this it is set to left or right. + context.config.expandFrom = ( function ( expandFrom ) { + var regionWidth, docWidth, regionCenter, docCenter, + docDir = $( document.documentElement ).css( 'direction' ), + $region = context.config.$region; + + // Backwards compatible + if ( context.config.positionFromLeft ) { + expandFrom = 'left'; + + // Catch invalid values, default to 'auto' + } else if ( $.inArray( expandFrom, ['left', 'right', 'start', 'end', 'auto'] ) === -1 ) { + expandFrom = 'auto'; + } + + if ( expandFrom === 'auto' ) { + if ( $region.data( 'searchsuggest-expand-dir' ) ) { + // If the markup explicitly contains a direction, use it. + expandFrom = $region.data( 'searchsuggest-expand-dir' ); + } else { + regionWidth = $region.outerWidth(); + docWidth = $( document ).width(); + if ( ( regionWidth / docWidth ) > 0.85 ) { + // If the input size takes up more than 85% of the document horizontally + // expand the suggestions to the writing direction's native end. + expandFrom = 'start'; + } else { + // Calculate the center points of the input and document + regionCenter = $region.offset().left + regionWidth / 2; + docCenter = docWidth / 2; + if ( Math.abs( regionCenter - docCenter ) / docCenter < 0.10 ) { + // If the input's center is within 10% of the document center + // use the writing direction's native end. + expandFrom = 'start'; + } else { + // Otherwise expand the input from the closest side of the page, + // towards the side of the page with the most free open space + expandFrom = regionCenter > docCenter ? 'right' : 'left'; + } + } + } + } + + if ( expandFrom === 'start' ) { + expandFrom = docDir === 'rtl' ? 'right': 'left'; + + } else if ( expandFrom === 'end' ) { + expandFrom = docDir === 'rtl' ? 'left': 'right'; + } + + return expandFrom; + + }( context.config.expandFrom ) ); + + if ( context.config.expandFrom === 'left' ) { + // Expand from left newCSS.left = context.config.$region.offset().left; newCSS.right = 'auto'; } else { + // Expand from right newCSS.left = 'auto'; newCSS.right = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); } + context.data.$container.css( newCSS ); var $results = context.data.$container.children( '.suggestions-results' ); $results.empty(); @@ -154,12 +218,13 @@ $.suggestions = { var $autoEllipseMe = $( [] ); var matchedText = null; for ( var i = 0; i < context.config.suggestions.length; i++ ) { + /*jshint loopfunc:true */ var text = context.config.suggestions[i]; var $result = $( '<div>' ) .addClass( 'suggestions-result' ) .attr( 'rel', i ) .data( 'text', context.config.suggestions[i] ) - .mousemove( function( e ) { + .mousemove( function ( e ) { context.data.selectedWithMouse = true; $.suggestions.highlight( context, $(this).closest( '.suggestions-results div' ), false @@ -220,9 +285,9 @@ $.suggestions = { * @param result <tr> to highlight: jQuery object, or 'prev' or 'next' * @param updateTextbox If true, put the suggestion in the textbox */ - highlight: function( context, result, updateTextbox ) { + highlight: function ( context, result, updateTextbox ) { var selected = context.data.$container.find( '.suggestions-result-current' ); - if ( !result.get || selected.get( 0 ) != result.get( 0 ) ) { + if ( !result.get || selected.get( 0 ) !== result.get( 0 ) ) { if ( result === 'prev' ) { if( selected.is( '.suggestions-special' ) ) { result = context.data.$container.find( '.suggestions-result:last' ); @@ -277,8 +342,8 @@ $.suggestions = { * Respond to keypress event * @param key Integer Code of key pressed */ - keypress: function( e, context, key ) { - var wasVisible = context.data.$container.is( ':visible' ), + keypress: function ( e, context, key ) { + var wasVisible = context.data.$container.is( ':visible' ), preventDefault = false; switch ( key ) { // Arrow down @@ -311,7 +376,7 @@ $.suggestions = { case 13: context.data.$container.hide(); preventDefault = wasVisible; - selected = context.data.$container.find( '.suggestions-result-current' ); + var selected = context.data.$container.find( '.suggestions-result-current' ); if ( selected.length === 0 || context.data.selectedWithMouse ) { // if nothing is selected OR if something was selected with the mouse, // cancel any current requests and submit the form @@ -340,22 +405,23 @@ $.suggestions = { } } }; -$.fn.suggestions = function() { +$.fn.suggestions = function () { // Multi-context fields - var returnValue = null; - var args = arguments; + var returnValue, + args = arguments; - $(this).each( function() { + $(this).each( function () { + var context, key; /* Construction / Loading */ - var context = $(this).data( 'suggestions-context' ); + context = $(this).data( 'suggestions-context' ); if ( context === undefined || context === null ) { context = { config: { - 'fetch' : function() {}, - 'cancel': function() {}, + 'fetch' : function () {}, + 'cancel': function () {}, 'special': {}, 'result': {}, '$region': $(this), @@ -364,7 +430,7 @@ $.fn.suggestions = function() { 'delay': 120, 'submitOnClick': false, 'maxExpandFactor': 3, - 'positionFromLeft': true, + 'expandFrom': 'auto', 'highlightInput': false } }; @@ -376,14 +442,14 @@ $.fn.suggestions = function() { if ( args.length > 0 ) { if ( typeof args[0] === 'object' ) { // Apply set of properties - for ( var key in args[0] ) { + for ( key in args[0] ) { $.suggestions.configure( context, key, args[0][key] ); } } else if ( typeof args[0] === 'string' ) { if ( args.length > 1 ) { // Set property values $.suggestions.configure( context, args[0], args[1] ); - } else if ( returnValue == null ) { + } 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]] ); } @@ -395,45 +461,35 @@ $.fn.suggestions = function() { if ( context.data === undefined ) { context.data = { // ID of running timer - 'timerID': null, + timerID: null, + // Text in textbox when suggestions were last fetched - 'prevText': null, + prevText: null, + // Number of results visible without scrolling - 'visibleResults': 0, + visibleResults: 0, + // Suggestion the last mousedown event occured on - 'mouseDownOn': $( [] ), - '$textbox': $(this), - 'selectedWithMouse': false - }; - // Setup the css for positioning the results box - var newCSS = { - top: Math.round( context.data.$textbox.offset().top + context.data.$textbox.outerHeight() ), - width: context.data.$textbox.outerWidth(), - display: 'none' + mouseDownOn: $( [] ), + $textbox: $(this), + selectedWithMouse: false }; - if ( context.config.positionFromLeft ) { - newCSS.left = context.config.$region.offset().left; - newCSS.right = 'auto'; - } else { - newCSS.left = 'auto'; - newCSS.right = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); - } context.data.$container = $( '<div>' ) - .css( newCSS ) + .css( 'display', 'none' ) .addClass( 'suggestions' ) .append( $( '<div>' ).addClass( 'suggestions-results' ) // Can't use click() because the container div is hidden when the textbox loses focus. Instead, // listen for a mousedown followed by a mouseup on the same div - .mousedown( function( e ) { + .mousedown( function ( e ) { context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' ); } ) - .mouseup( function( e ) { + .mouseup( function ( e ) { var $result = $( e.target ).closest( '.suggestions-results div' ); var $other = context.data.mouseDownOn; context.data.mouseDownOn = $( [] ); - if ( $result.get( 0 ) != $other.get( 0 ) ) { + if ( $result.get( 0 ) !== $other.get( 0 ) ) { return; } $.suggestions.highlight( context, $result, true ); @@ -448,14 +504,14 @@ $.fn.suggestions = function() { $( '<div>' ).addClass( 'suggestions-special' ) // Can't use click() because the container div is hidden when the textbox loses focus. Instead, // listen for a mousedown followed by a mouseup on the same div - .mousedown( function( e ) { + .mousedown( function ( e ) { context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' ); } ) - .mouseup( function( e ) { + .mouseup( function ( e ) { var $special = $( e.target ).closest( '.suggestions-special' ); var $other = context.data.mouseDownOn; context.data.mouseDownOn = $( [] ); - if ( $special.get( 0 ) != $other.get( 0 ) ) { + if ( $special.get( 0 ) !== $other.get( 0 ) ) { return; } context.data.$container.hide(); @@ -464,7 +520,7 @@ $.fn.suggestions = function() { } context.data.$textbox.focus(); } ) - .mousemove( function( e ) { + .mousemove( function ( e ) { context.data.selectedWithMouse = true; $.suggestions.highlight( context, $( e.target ).closest( '.suggestions-special' ), false @@ -472,12 +528,13 @@ $.fn.suggestions = function() { } ) ) .appendTo( $( 'body' ) ); + $(this) // Stop browser autocomplete from interfering .attr( 'autocomplete', 'off') - .keydown( function( e ) { + .keydown( function ( e ) { // Store key pressed to handle later - context.data.keypressed = ( e.keyCode === undefined ) ? e.which : e.keyCode; + context.data.keypressed = e.which; context.data.keypressedCount = 0; switch ( context.data.keypressed ) { @@ -496,18 +553,18 @@ $.fn.suggestions = function() { } } } ) - .keypress( function( e ) { + .keypress( function ( e ) { context.data.keypressedCount++; $.suggestions.keypress( e, context, context.data.keypressed ); } ) - .keyup( function( e ) { + .keyup( function ( e ) { // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a // keypress in between, solve it if ( context.data.keypressedCount === 0 ) { $.suggestions.keypress( e, context, context.data.keypressed ); } } ) - .blur( function() { + .blur( function () { // When losing focus because of a mousedown // on a suggestion, don't hide the suggestions if ( context.data.mouseDownOn.length > 0 ) { @@ -517,9 +574,11 @@ $.fn.suggestions = function() { $.suggestions.cancel( context ); } ); } + // Store the context for next time $(this).data( 'suggestions-context', context ); } ); - return returnValue !== null ? returnValue : $(this); + return returnValue !== undefined ? returnValue : $(this); }; -} )( jQuery );
\ No newline at end of file + +}( jQuery ) ); diff --git a/resources/jquery/jquery.tabIndex.js b/resources/jquery/jquery.tabIndex.js index 75731d7c..cdae0bad 100644 --- a/resources/jquery/jquery.tabIndex.js +++ b/resources/jquery/jquery.tabIndex.js @@ -1,50 +1,52 @@ /** * jQuery tabIndex */ -( function( $ ) { -/** - * Finds the lowerst tabindex in use within a selection - * - * @return number Lowest tabindex on the page - */ -$.fn.firstTabIndex = function() { - var minTabIndex = null; - $(this).find( '[tabindex]' ).each( function() { - var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 ); - // In IE6/IE7 the above jQuery selector returns all elements, - // becuase it has a default value for tabIndex in IE6/IE7 of 0 - // (rather than null/undefined). Therefore check "> 0" as well. - // Under IE7 under Windows NT 5.2 is also capable of returning NaN. - if ( tabIndex > 0 && !isNaN( tabIndex ) ) { - // Initial value - if ( minTabIndex === null ) { - minTabIndex = tabIndex; - } else if ( tabIndex < minTabIndex ) { - minTabIndex = tabIndex; +( function ( $ ) { + + /** + * Finds the lowerst tabindex in use within a selection + * + * @return number Lowest tabindex on the page + */ + $.fn.firstTabIndex = function () { + var minTabIndex = null; + $(this).find( '[tabindex]' ).each( function () { + var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 ); + // In IE6/IE7 the above jQuery selector returns all elements, + // becuase it has a default value for tabIndex in IE6/IE7 of 0 + // (rather than null/undefined). Therefore check "> 0" as well. + // Under IE7 under Windows NT 5.2 is also capable of returning NaN. + if ( tabIndex > 0 && !isNaN( tabIndex ) ) { + // Initial value + if ( minTabIndex === null ) { + minTabIndex = tabIndex; + } else if ( tabIndex < minTabIndex ) { + minTabIndex = tabIndex; + } } - } - } ); - return minTabIndex; -}; + } ); + return minTabIndex; + }; -/** - * Finds the highest tabindex in use within a selection - * - * @return number Highest tabindex on the page - */ -$.fn.lastTabIndex = function() { - var maxTabIndex = null; - $(this).find( '[tabindex]' ).each( function() { - var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 ); - if ( tabIndex > 0 && !isNaN( tabIndex ) ) { - // Initial value - if ( maxTabIndex === null ) { - maxTabIndex = tabIndex; - } else if ( tabIndex > maxTabIndex ) { - maxTabIndex = tabIndex; + /** + * Finds the highest tabindex in use within a selection + * + * @return number Highest tabindex on the page + */ + $.fn.lastTabIndex = function () { + var maxTabIndex = null; + $(this).find( '[tabindex]' ).each( function () { + var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 ); + if ( tabIndex > 0 && !isNaN( tabIndex ) ) { + // Initial value + if ( maxTabIndex === null ) { + maxTabIndex = tabIndex; + } else if ( tabIndex > maxTabIndex ) { + maxTabIndex = tabIndex; + } } - } - } ); - return maxTabIndex; -}; -} )( jQuery ); + } ); + return maxTabIndex; + }; + +}( jQuery ) ); diff --git a/resources/jquery/jquery.tablesorter.js b/resources/jquery/jquery.tablesorter.js index ea86b64e..3ef71d57 100644 --- a/resources/jquery/jquery.tablesorter.js +++ b/resources/jquery/jquery.tablesorter.js @@ -56,11 +56,11 @@ * @author Christian Bach/christian.bach@polyester.se */ -( function( $ ) { +( function ( $, mw ) { /* Local scope */ - var ts, + var ts, parsers = []; /* Parser utility functions */ @@ -77,9 +77,15 @@ function getElementText( node ) { var $node = $( node ), - data = $node.attr( 'data-sort-value' ); - if ( data !== undefined ) { - return data; + // Use data-sort-value attribute. + // Use data() instead of attr() so that live value changes + // are processed as well (bug 38152). + data = $node.data( 'sortValue' ); + + if ( data !== null && data !== undefined ) { + // Cast any numbers or other stuff to a string, methods + // like charAt, toLowerCase and split are expected. + return String( data ); } else { return $node.text(); } @@ -94,7 +100,7 @@ } function detectParserForColumn( table, rows, cellIndex ) { - var l = parsers.length, + var l = parsers.length, nodeValue, // Start with 1 because 0 is the fallback parser i = 1, @@ -133,13 +139,13 @@ } function buildParserCache( table, $headers ) { - var rows = table.tBodies[0].rows, + var rows = table.tBodies[0].rows, sortType, parsers = []; if ( rows[0] ) { - var cells = rows[0].cells, + var cells = rows[0].cells, len = cells.length, i, parser; @@ -163,7 +169,7 @@ /* Other utility functions */ function buildCache( table ) { - var totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0, + var totalRows = ( table.tBodies[0] && table.tBodies[0].rows.length ) || 0, totalCells = ( table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length ) || 0, parsers = table.config.parsers, cache = { @@ -174,7 +180,7 @@ for ( var i = 0; i < totalRows; ++i ) { // Add the table data to main data array - var $row = $( table.tBodies[0].rows[i] ), + var $row = $( table.tBodies[0].rows[i] ), cols = []; // if this is a child row, add it to the last row's children and @@ -200,7 +206,7 @@ } function appendToTable( table, cache ) { - var row = cache.row, + var row = cache.row, normalized = cache.normalized, totalRows = normalized.length, checkCell = ( normalized[0].length - 1 ), @@ -218,22 +224,22 @@ } table.tBodies[0].appendChild( fragment ); } - + /** * Find all header rows in a thead-less table and put them in a <thead> tag. * This only treats a row as a header row if it contains only <th>s (no <td>s) * and if it is preceded entirely by header rows. The algorithm stops when * it encounters the first non-header row. - * + * * 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> - */ + */ function emulateTHeadAndFoot( $table ) { var $rows = $table.find( '> tbody > tr' ); if( !$table.get(0).tHead ) { var $thead = $( '<thead>' ); - $rows.each( function() { + $rows.each( function () { if ( $(this).children( 'td' ).length > 0 ) { // This row contains a <td>, so it's not a header row // Stop here @@ -251,18 +257,18 @@ break; } $tfoot.prepend( $( $rows[i] )); - } + } $table.append( $tfoot ); } } function buildHeaders( table, msg ) { - var maxSeen = 0, + var maxSeen = 0, longest, realCellIndex = 0, $tableHeaders = $( 'thead:eq(0) > tr', table ); if ( $tableHeaders.length > 1 ) { - $tableHeaders.each( function() { + $tableHeaders.each( function () { if ( this.cells.length > maxSeen ) { maxSeen = this.cells.length; longest = this; @@ -270,7 +276,7 @@ }); $tableHeaders = $( longest ); } - $tableHeaders = $tableHeaders.children( 'th' ).each( function( index ) { + $tableHeaders = $tableHeaders.children( 'th' ).each( function ( index ) { this.column = realCellIndex; var colspan = this.colspan; @@ -299,7 +305,7 @@ function isValueInArray( v, a ) { var l = a.length; for ( var i = 0; i < l; i++ ) { - if ( a[i][0] == v ) { + if ( a[i][0] === v ) { return true; } } @@ -311,7 +317,7 @@ $headers.removeClass( css[0] ).removeClass( css[1] ); var h = []; - $headers.each( function( offset ) { + $headers.each( function ( offset ) { if ( !this.sortDisabled ) { h[this.column] = $( this ); } @@ -324,62 +330,31 @@ } function sortText( a, b ) { - return ( (a < b) ? false : ((a > b) ? true : 0) ); + return ( (a < b) ? -1 : ((a > b) ? 1 : 0) ); } function sortTextDesc( a, b ) { - return ( (b < a) ? false : ((b > a) ? true : 0) ); + return ( (b < a) ? -1 : ((b > a) ? 1 : 0) ); } - function checkSorting( array1, array2, sortList ) { - var col, fn, ret; - for ( var i = 0, len = sortList.length; i < len; i++ ) { - col = sortList[i][0]; - fn = ( sortList[i][1] ) ? sortTextDesc : sortText; - ret = fn.call( this, array1[col], array2[col] ); - if ( ret !== 0 ) { - return ret; - } + function multisort( table, sortList, cache ) { + var sortFn = []; + var len = sortList.length; + for ( var i = 0; i < len; i++ ) { + sortFn[i] = ( sortList[i][1] ) ? sortTextDesc : sortText; } - return ret; - } - - // Merge sort algorithm - // Based on http://en.literateprograms.org/Merge_sort_(JavaScript) - function mergeSortHelper( array, begin, beginRight, end, sortList ) { - for ( ; begin < beginRight; ++begin ) { - if ( checkSorting( array[begin], array[beginRight], sortList ) ) { - var v = array[begin]; - array[begin] = array[beginRight]; - var begin2 = beginRight; - while ( begin2 + 1 < end && checkSorting( v, array[begin2 + 1], sortList ) ) { - var tmp = array[begin2]; - array[begin2] = array[begin2 + 1]; - array[begin2 + 1] = tmp; - ++begin2; + cache.normalized.sort( function ( array1, array2 ) { + var col, ret; + for ( var i = 0; i < len; i++ ) { + col = sortList[i][0]; + ret = sortFn[i].call( this, array1[col], array2[col] ); + if ( ret !== 0 ) { + return ret; } - array[begin2] = v; } - } - } - - function mergeSort(array, begin, end, sortList) { - var size = end - begin; - if ( size < 2 ) { - return; - } - - var beginRight = begin + Math.floor(size / 2); - - mergeSort( array, begin, beginRight, sortList ); - mergeSort( array, beginRight, end, sortList ); - mergeSortHelper( array, begin, beginRight, end, sortList ); - } - - function multisort( table, sortList, cache ) { - var i = sortList.length; - mergeSort( cache.normalized, 0, cache.normalized.length, sortList ); - + // Fall back to index number column to ensure stable sort + return sortText.call( this, array1[array1.length - 1], array2[array2.length - 1] ); + } ); return cache; } @@ -413,16 +388,15 @@ function buildDateTable() { var regex = []; - ts.monthNames = [ - [], - [] - ]; + ts.monthNames = {}; for ( var i = 1; i < 13; i++ ) { - ts.monthNames[0][i] = mw.config.get( 'wgMonthNames' )[i].toLowerCase(); - ts.monthNames[1][i] = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' ); - regex.push( $.escapeRE( ts.monthNames[0][i] ) ); - regex.push( $.escapeRE( ts.monthNames[1][i] ) ); + var name = mw.config.get( 'wgMonthNames' )[i].toLowerCase(); + ts.monthNames[name] = i; + regex.push( $.escapeRE( name ) ); + name = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' ); + ts.monthNames[name] = i; + regex.push( $.escapeRE( name ) ); } // Build piped string @@ -430,19 +404,19 @@ // 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' ); } function explodeRowspans( $table ) { // Split multi row cells into multiple cells with the same content - $table.find( '> tbody > tr > [rowspan]' ).each(function() { + $table.find( '> tbody > tr > [rowspan]' ).each(function () { var rowSpan = this.rowSpan; this.rowSpan = 1; var cell = $( this ); @@ -487,8 +461,8 @@ new RegExp( /^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/) ], currency: [ - new RegExp( /^[£$€?.]/), - new RegExp( /[£$€]/g) + new RegExp( /(^[£$€¥]|[£$€¥]$)/), + new RegExp( /[£$€¥]/g) ], url: [ new RegExp( /^(https?|ftp|file):\/\/$/), @@ -529,16 +503,16 @@ }, dateRegex: [], - monthNames: [], + monthNames: {}, /** * @param $tables {jQuery} * @param settings {Object} (optional) */ - construct: function( $tables, settings ) { - return $tables.each( function( i, table ) { + construct: function ( $tables, settings ) { + return $tables.each( function ( i, table ) { // Declare and cache. - var $document, $headers, cache, config, sortOrder, + var $document, $headers, cache, config, sortOrder, $table = $( table ), shiftDown = 0, firstTime = true; @@ -551,7 +525,7 @@ // No thead found. Look for rows with <th>s and // move them into a <thead> tag or a <tfoot> tag emulateTHeadAndFoot( $table ); - + // Still no thead? Then quit if ( !table.tHead ) { return; @@ -586,8 +560,8 @@ // Apply event handling to headers // this is too big, perhaps break it out? - $headers.click( function( e ) { - if ( e.target.nodeName.toLowerCase() == 'a' ) { + $headers.click( function ( e ) { + if ( e.target.nodeName.toLowerCase() === 'a' ) { // The user clicked on a link inside a table header // Do nothing and let the default link click action continue return true; @@ -612,9 +586,16 @@ explodeRowspans( $table ); // try to auto detect column type, and store in tables config table.config.parsers = buildParserCache( table, $headers ); - // build the cache for the tbody cells - cache = buildCache( table ); } + + // Build the cache for the tbody cells + // to share between calculations for this sort action. + // Re-calculated each time a sort action is performed due to possiblity + // that sort values change. Shouldn't be too expensive, but if it becomes + // too slow an event based system should be implemented somehow where + // cells get event .change() and bubbles up to the <table> here + cache = buildCache( table ); + var totalRows = ( $table[0].tBodies[0] && $table[0].tBodies[0].rows.length ) || 0; if ( !table.sortDisabled && totalRows > 0 ) { @@ -643,7 +624,7 @@ for ( var j = 0; j < config.sortList.length; j++ ) { var s = config.sortList[j], o = config.headerList[s[0]]; - if ( s[0] == i ) { + if ( s[0] === i ) { o.count = s[1]; o.count++; s[1] = o.count % 2; @@ -666,9 +647,9 @@ } // Cancel selection - } ).mousedown( function() { + } ).mousedown( function () { if ( config.cancelSelection ) { - this.onselectstart = function() { + this.onselectstart = function () { return false; }; return false; @@ -677,11 +658,11 @@ } ); }, - addParser: function( parser ) { - var l = parsers.length, + addParser: function ( parser ) { + var l = parsers.length, a = true; for ( var i = 0; i < l; i++ ) { - if ( parsers[i].id.toLowerCase() == parser.id.toLowerCase() ) { + if ( parsers[i].id.toLowerCase() === parser.id.toLowerCase() ) { a = false; } } @@ -690,9 +671,9 @@ } }, - formatDigit: function( s ) { + formatDigit: function ( s ) { if ( ts.transformTable !== false ) { - var out = '', + var out = '', c; for ( var p = 0; p < s.length; p++ ) { c = s.charAt(p); @@ -708,19 +689,19 @@ return ( isNaN(i)) ? 0 : i; }, - formatFloat: function( s ) { + formatFloat: function ( s ) { var i = parseFloat(s); return ( isNaN(i)) ? 0 : i; }, - formatInt: function( s ) { + formatInt: function ( s ) { var i = parseInt( s, 10 ); return ( isNaN(i)) ? 0 : i; }, - clearTableBody: function( table ) { + clearTableBody: function ( table ) { if ( $.browser.msie ) { - var empty = function( el ) { + var empty = function ( el ) { while ( el.firstChild ) { el.removeChild( el.firstChild ); } @@ -736,21 +717,21 @@ ts = $.tablesorter; // Register as jQuery prototype method - $.fn.tablesorter = function( settings ) { + $.fn.tablesorter = function ( settings ) { return ts.construct( this, settings ); }; // Add default parsers ts.addParser( { id: 'text', - is: function( s ) { + is: function ( s ) { return true; }, - format: function( s ) { + format: function ( s ) { s = $.trim( s.toLowerCase() ); if ( ts.collationRegex ) { var tsc = ts.collationTable; - s = s.replace( ts.collationRegex, function( match ) { + s = s.replace( ts.collationRegex, function ( match ) { var r = tsc[match] ? tsc[match] : tsc[match.toUpperCase()]; return r.toLowerCase(); } ); @@ -762,18 +743,18 @@ ts.addParser( { id: 'IPAddress', - is: function( s ) { + is: function ( s ) { return ts.rgx.IPAddress[0].test(s); }, - format: function( s ) { - var a = s.split( '.' ), + format: function ( s ) { + var a = s.split( '.' ), r = '', l = a.length; for ( var i = 0; i < l; i++ ) { var item = a[i]; - if ( item.length == 1 ) { + if ( item.length === 1 ) { r += '00' + item; - } else if ( item.length == 2 ) { + } else if ( item.length === 2 ) { r += '0' + item; } else { r += item; @@ -786,10 +767,10 @@ ts.addParser( { id: 'currency', - is: function( s ) { + is: function ( s ) { return ts.rgx.currency[0].test(s); }, - format: function( s ) { + format: function ( s ) { return $.tablesorter.formatDigit( s.replace( ts.rgx.currency[1], '' ) ); }, type: 'numeric' @@ -797,10 +778,10 @@ ts.addParser( { id: 'url', - is: function( s ) { + is: function ( s ) { return ts.rgx.url[0].test(s); }, - format: function( s ) { + format: function ( s ) { return $.trim( s.replace( ts.rgx.url[1], '' ) ); }, type: 'text' @@ -808,10 +789,10 @@ ts.addParser( { id: 'isoDate', - is: function( s ) { + is: function ( s ) { return ts.rgx.isoDate[0].test(s); }, - format: function( s ) { + format: function ( s ) { return $.tablesorter.formatFloat((s !== '') ? new Date(s.replace( new RegExp( /-/g), '/')).getTime() : '0' ); }, @@ -820,10 +801,10 @@ ts.addParser( { id: 'usLongDate', - is: function( s ) { + is: function ( s ) { return ts.rgx.usLongDate[0].test(s); }, - format: function( s ) { + format: function ( s ) { return $.tablesorter.formatFloat( new Date(s).getTime() ); }, type: 'numeric' @@ -831,55 +812,47 @@ ts.addParser( { id: 'date', - is: function( s ) { + is: function ( s ) { return ( ts.dateRegex[0].test(s) || ts.dateRegex[1].test(s) || ts.dateRegex[2].test(s )); }, - format: function( s, table ) { + format: function ( s, table ) { + var match; s = $.trim( s.toLowerCase() ); - for ( var i = 1, j = 0; i < 13 && j < 2; i++ ) { - s = s.replace( ts.monthNames[j][i], i ); - if ( i == 12 ) { - j++; - i = 0; + 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] ]; + } else if ( mw.config.get( 'wgDefaultDateFormat' ) === 'dmy' ) { + s = [ match[3], match[2], match[1] ]; } + } else if ( ( match = s.match( ts.dateRegex[1] ) ) !== null ) { + s = [ match[3], '' + ts.monthNames[match[2]], match[1] ]; + } else if ( ( match = s.match( ts.dateRegex[2] ) ) !== null ) { + s = [ match[3], '' + ts.monthNames[match[1]], match[2] ]; + } else { + // Should never get here + return '99999999'; } - s = s.replace( /[\-\.\,' ]/g, '/' ); - - // Replace double slashes - s = s.replace( /\/\//g, '/' ); - s = s.replace( /\/\//g, '/' ); - s = s.split( '/' ); - // Pad Month and Day - if ( s[0] && s[0].length == 1 ) { - s[0] = '0' + s[0]; - } - if ( s[1] && s[1].length == 1 ) { + if ( s[1].length === 1 ) { s[1] = '0' + s[1]; } - var y; + if ( s[2].length === 1 ) { + s[2] = '0' + s[2]; + } - if ( !s[2] ) { - // Fix yearless dates - s[2] = 2000; - } else if ( ( y = parseInt( s[2], 10) ) < 100 ) { + var y; + if ( ( y = parseInt( s[0], 10) ) < 100 ) { // Guestimate years without centuries if ( y < 30 ) { - s[2] = 2000 + y; + s[0] = 2000 + y; } else { - s[2] = 1900 + y; + s[0] = 1900 + y; } } - // Resort array depending on preferences - if ( mw.config.get( 'wgDefaultDateFormat' ) == 'mdy' || mw.config.get( 'wgContentLanguage' ) == 'en' ) { - s.push( s.shift() ); - s.push( s.shift() ); - } else if ( mw.config.get( 'wgDefaultDateFormat' ) == 'dmy' ) { - var d = s.shift(); - s.push( s.shift() ); - s.push(d); + while ( s[0].length < 4 ) { + s[0] = '0' + s[0]; } return parseInt( s.join( '' ), 10 ); }, @@ -888,10 +861,10 @@ ts.addParser( { id: 'time', - is: function( s ) { + is: function ( s ) { return ts.rgx.time[0].test(s); }, - format: function( s ) { + format: function ( s ) { return $.tablesorter.formatFloat( new Date( '2000/01/01 ' + s ).getTime() ); }, type: 'numeric' @@ -899,13 +872,13 @@ ts.addParser( { id: 'number', - is: function( s, table ) { + is: function ( s, table ) { return $.tablesorter.numberRegex.test( $.trim( s )); }, - format: function( s ) { + format: function ( s ) { return $.tablesorter.formatDigit(s); }, type: 'numeric' } ); -} )( jQuery ); +}( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.textSelection.js b/resources/jquery/jquery.textSelection.js index 91b6e75d..abb0fa3f 100644 --- a/resources/jquery/jquery.textSelection.js +++ b/resources/jquery/jquery.textSelection.js @@ -1,527 +1,562 @@ /** * These plugins provide extra functionality for interaction with textareas. */ -( function( $ ) { +( function ( $ ) { + /*jshint noempty:false */ -if (document.selection && document.selection.createRange) { - // On IE, patch the focus() method to restore the windows' scroll position - // (bug 32241) - $.fn.extend({ - focus : (function ( _focus ) { - return function () { - if ( arguments.length == 0 ) { - var $w = $( window ); - var state = {top: $w.scrollTop(), left: $w.scrollLeft()}; - var result = _focus.apply( this, arguments ); - window.scrollTo( state.top, state.left ); - return result; - } - return _focus.apply( this, arguments ); - }; - })( $.fn.focus ) - }); -} - -$.fn.textSelection = function( command, options ) { - -/** - * Helper function to get an IE TextRange object for an element - */ -function rangeForElementIE( e ) { - if ( e.nodeName.toLowerCase() == 'input' ) { - return e.createTextRange(); - } else { - var sel = document.body.createTextRange(); - sel.moveToElementText( e ); - return sel; - } -} - -/** - * Helper function for IE for activating the textarea. Called only in the - * IE-specific code paths below; makes use of IE-specific non-standard - * function setActive() if possible to avoid screen flicker. - */ -function activateElementOnIE( element ) { - if ( element.setActive ) { - element.setActive(); // bug 32241: doesn't scroll - } else { - $( element ).focus(); // may scroll (but we patched it above) + if ( document.selection && document.selection.createRange ) { + // On IE, patch the focus() method to restore the windows' scroll position + // (bug 32241) + $.fn.extend({ + focus: ( function ( jqFocus ) { + return function () { + var $w, state, result; + if ( arguments.length === 0 ) { + $w = $( window ); + state = {top: $w.scrollTop(), left: $w.scrollLeft()}; + result = jqFocus.apply( this, arguments ); + window.scrollTo( state.top, state.left ); + return result; + } + return jqFocus.apply( this, arguments ); + }; + }( $.fn.focus ) ) + }); } -} -var fn = { -/** - * Get the contents of the textarea - */ -getContents: function() { - return this.val(); -}, -/** - * Get the currently selected text in this textarea. Will focus the textarea - * in some browsers (IE/Opera) - */ -getSelection: function() { - var e = this.get( 0 ); - var retval = ''; - if ( $(e).is( ':hidden' ) ) { - // Do nothing - } else if ( document.selection && document.selection.createRange ) { - activateElementOnIE( e ); - var range = document.selection.createRange(); - retval = range.text; - } else if ( e.selectionStart || e.selectionStart == '0' ) { - retval = e.value.substring( e.selectionStart, e.selectionEnd ); - } - return retval; -}, -/** - * Ported from skins/common/edit.js by Trevor Parscal - * (c) 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org - * - * Inserts text at the begining and end of a text selection, optionally - * inserting text at the caret when selection is empty. - * - * @fixme document the options parameters - */ -encapsulateSelection: function( options ) { - return this.each( function() { - var pre = options.pre, post = options.post; + $.fn.textSelection = function ( command, options ) { /** - * Check if the selected text is the same as the insert text + * Helper function to get an IE TextRange object for an element */ - function checkSelectedText() { - if ( !selText ) { - selText = options.peri; - isSample = true; - } else if ( options.replace ) { - selText = options.peri; + function rangeForElementIE( e ) { + if ( e.nodeName.toLowerCase() === 'input' ) { + return e.createTextRange(); } else { - while ( selText.charAt( selText.length - 1 ) == ' ' ) { - // Exclude ending space char - selText = selText.substring( 0, selText.length - 1 ); - post += ' '; - } - while ( selText.charAt( 0 ) == ' ' ) { - // Exclude prepending space char - selText = selText.substring( 1, selText.length ); - pre = ' ' + pre; - } + var sel = document.body.createTextRange(); + sel.moveToElementText( e ); + return sel; } } /** - * Do the splitlines stuff. - * - * Wrap each line of the selected text with pre and post + * Helper function for IE for activating the textarea. Called only in the + * IE-specific code paths below; makes use of IE-specific non-standard + * function setActive() if possible to avoid screen flicker. */ - function doSplitLines( selText, pre, post ) { - var insertText = ''; - var selTextArr = selText.split( '\n' ); - for ( var i = 0; i < selTextArr.length; i++ ) { - insertText += pre + selTextArr[i] + post; - if ( i != selTextArr.length - 1 ) { - insertText += '\n'; - } + function activateElementOnIE( element ) { + if ( element.setActive ) { + element.setActive(); // bug 32241: doesn't scroll + } else { + $( element ).focus(); // may scroll (but we patched it above) } - return insertText; } - var isSample = false; - if ( this.style.display == 'none' ) { - // Do nothing - } else if ( document.selection && document.selection.createRange ) { - // IE + var fn = { + /** + * Get the contents of the textarea + */ + getContents: function () { + return this.val(); + }, + /** + * Get the currently selected text in this textarea. Will focus the textarea + * in some browsers (IE/Opera) + */ + getSelection: function () { + var retval, range, + el = this.get( 0 ); - // Note that IE9 will trigger the next section unless we check this first. - // See bug 35201. + if ( $(el).is( ':hidden' ) ) { + // Do nothing + retval = ''; + } else if ( document.selection && document.selection.createRange ) { + activateElementOnIE( el ); + range = document.selection.createRange(); + retval = range.text; + } else if ( el.selectionStart || el.selectionStart === 0 ) { + retval = el.value.substring( el.selectionStart, el.selectionEnd ); + } - activateElementOnIE( this ); - if ( context ) { - context.fn.restoreCursorAndScrollTop(); - } - if ( options.selectionStart !== undefined ) { - $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); - } + return retval; + }, + /** + * Ported from skins/common/edit.js by Trevor Parscal + * (c) 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org + * + * Inserts text at the begining and end of a text selection, optionally + * inserting text at the caret when selection is empty. + * + * @fixme document the options parameters + */ + encapsulateSelection: function ( options ) { + return this.each( function () { + var selText, scrollTop, insertText, + isSample, range, range2, range3, startPos, endPos, + pre = options.pre, + post = options.post; + + /** + * Check if the selected text is the same as the insert text + */ + function checkSelectedText() { + if ( !selText ) { + selText = options.peri; + isSample = true; + } else if ( options.replace ) { + selText = options.peri; + } else { + while ( selText.charAt( selText.length - 1 ) === ' ' ) { + // Exclude ending space char + selText = selText.substring( 0, selText.length - 1 ); + post += ' '; + } + while ( selText.charAt( 0 ) === ' ' ) { + // Exclude prepending space char + selText = selText.substring( 1, selText.length ); + pre = ' ' + pre; + } + } + } - var selText = $(this).textSelection( 'getSelection' ); - var scrollTop = this.scrollTop; - var range = document.selection.createRange(); + /** + * Do the splitlines stuff. + * + * Wrap each line of the selected text with pre and post + */ + function doSplitLines( selText, pre, post ) { + var i, + insertText = '', + selTextArr = selText.split( '\n' ); + for ( i = 0; i < selTextArr.length; i++ ) { + insertText += pre + selTextArr[i] + post; + if ( i !== selTextArr.length - 1 ) { + insertText += '\n'; + } + } + return insertText; + } - checkSelectedText(); - var insertText = pre + selText + post; - if ( options.splitlines ) { - insertText = doSplitLines( selText, pre, post ); - } - if ( options.ownline && range.moveStart ) { - var range2 = document.selection.createRange(); - range2.collapse(); - range2.moveStart( 'character', -1 ); - // FIXME: Which check is correct? - if ( range2.text != "\r" && range2.text != "\n" && range2.text != "" ) { - insertText = "\n" + insertText; - pre += "\n"; - } - var range3 = document.selection.createRange(); - range3.collapse( false ); - range3.moveEnd( 'character', 1 ); - if ( range3.text != "\r" && range3.text != "\n" && range3.text != "" ) { - insertText += "\n"; - post += "\n"; - } - } + isSample = false; + if ( this.style.display === 'none' ) { + // Do nothing + } else if ( document.selection && document.selection.createRange ) { + // IE - range.text = insertText; - if ( isSample && options.selectPeri && range.moveStart ) { - range.moveStart( 'character', - post.length - selText.length ); - range.moveEnd( 'character', - post.length ); - } - range.select(); - // Restore the scroll position - this.scrollTop = scrollTop; - } else if ( this.selectionStart || this.selectionStart == '0' ) { - // Mozilla/Opera + // Note that IE9 will trigger the next section unless we check this first. + // See bug 35201. - $(this).focus(); - if ( options.selectionStart !== undefined ) { - $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); - } - - var selText = $(this).textSelection( 'getSelection' ); - var startPos = this.selectionStart; - var endPos = this.selectionEnd; - var scrollTop = this.scrollTop; - checkSelectedText(); - if ( options.selectionStart !== undefined - && endPos - startPos != options.selectionEnd - options.selectionStart ) - { - // This means there is a difference in the selection range returned by browser and what we passed. - // This happens for Chrome in the case of composite characters. Ref bug #30130 - // Set the startPos to the correct position. - startPos = options.selectionStart; - } + activateElementOnIE( this ); + if ( context ) { + context.fn.restoreCursorAndScrollTop(); + } + if ( options.selectionStart !== undefined ) { + $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); + } - var insertText = pre + selText + post; - if ( options.splitlines ) { - insertText = doSplitLines( selText, pre, post ); - } - if ( options.ownline ) { - if ( startPos != 0 && this.value.charAt( startPos - 1 ) != "\n" && this.value.charAt( startPos - 1 ) != "\r" ) { - insertText = "\n" + insertText; - pre += "\n"; - } - if ( this.value.charAt( endPos ) != "\n" && this.value.charAt( endPos ) != "\r" ) { - insertText += "\n"; - post += "\n"; - } - } - this.value = this.value.substring( 0, startPos ) + insertText + - this.value.substring( endPos, this.value.length ); - // Setting this.value scrolls the textarea to the top, restore the scroll position - this.scrollTop = scrollTop; - if ( window.opera ) { - pre = pre.replace( /\r?\n/g, "\r\n" ); - selText = selText.replace( /\r?\n/g, "\r\n" ); - post = post.replace( /\r?\n/g, "\r\n" ); - } - if ( isSample && options.selectPeri && !options.splitlines ) { - this.selectionStart = startPos + pre.length; - this.selectionEnd = startPos + pre.length + selText.length; - } else { - this.selectionStart = startPos + insertText.length; - this.selectionEnd = this.selectionStart; - } - } - $(this).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline, - options.replace, options.spitlines ] ); - }); -}, -/** - * Ported from Wikia's LinkSuggest extension - * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest - * Some code copied from - * http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ - * - * Get the position (in resolution of bytes not nessecarily characters) - * in a textarea - * - * Will focus the textarea in some browsers (IE/Opera) - * - * @fixme document the options parameters - */ - getCaretPosition: function( options ) { - function getCaret( e ) { - var caretPos = 0, endPos = 0; - if ( document.selection && document.selection.createRange ) { - // IE doesn't properly report non-selected caret position through - // the selection ranges when textarea isn't focused. This can - // lead to saving a bogus empty selection, which then screws up - // whatever we do later (bug 31847). - activateElementOnIE( e ); + selText = $(this).textSelection( 'getSelection' ); + scrollTop = this.scrollTop; + range = document.selection.createRange(); - // IE Support - var preFinished = false; - var periFinished = false; - var postFinished = false; - var preText, rawPreText, periText; - var rawPeriText, postText, rawPostText; - // Create range containing text in the selection - var periRange = document.selection.createRange().duplicate(); - // Create range containing text before the selection - var preRange = rangeForElementIE( e ); - // Move the end where we need it - preRange.setEndPoint("EndToStart", periRange); - // Create range containing text after the selection - var postRange = rangeForElementIE( e ); - // Move the start where we need it - postRange.setEndPoint("StartToEnd", periRange); - // Load the text values we need to compare - preText = rawPreText = preRange.text; - periText = rawPeriText = periRange.text; - postText = rawPostText = postRange.text; - /* - * Check each range for trimmed newlines by shrinking the range by 1 - * character and seeing if the text property has changed. If it has - * not changed then we know that IE has trimmed a \r\n from the end. - */ - do { - if ( !preFinished ) { - if ( preRange.compareEndPoints( "StartToEnd", preRange ) == 0 ) { - preFinished = true; - } else { - preRange.moveEnd( "character", -1 ); - if ( preRange.text == preText ) { - rawPreText += "\r\n"; + checkSelectedText(); + insertText = pre + selText + post; + if ( options.splitlines ) { + insertText = doSplitLines( selText, pre, post ); + } + if ( options.ownline && range.moveStart ) { + range2 = document.selection.createRange(); + range2.collapse(); + range2.moveStart( 'character', -1 ); + // FIXME: Which check is correct? + if ( range2.text !== "\r" && range2.text !== "\n" && range2.text !== "" ) { + insertText = "\n" + insertText; + pre += "\n"; + } + range3 = document.selection.createRange(); + range3.collapse( false ); + range3.moveEnd( 'character', 1 ); + if ( range3.text !== "\r" && range3.text !== "\n" && range3.text !== "" ) { + insertText += "\n"; + post += "\n"; + } + } + + range.text = insertText; + if ( isSample && options.selectPeri && range.moveStart ) { + range.moveStart( 'character', - post.length - selText.length ); + range.moveEnd( 'character', - post.length ); + } + range.select(); + // Restore the scroll position + this.scrollTop = scrollTop; + } else if ( this.selectionStart || this.selectionStart === 0 ) { + // Mozilla/Opera + + $(this).focus(); + if ( options.selectionStart !== undefined ) { + $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); + } + + selText = $(this).textSelection( 'getSelection' ); + startPos = this.selectionStart; + endPos = this.selectionEnd; + scrollTop = this.scrollTop; + checkSelectedText(); + if ( options.selectionStart !== undefined + && endPos - startPos !== options.selectionEnd - options.selectionStart ) + { + // This means there is a difference in the selection range returned by browser and what we passed. + // This happens for Chrome in the case of composite characters. Ref bug #30130 + // Set the startPos to the correct position. + startPos = options.selectionStart; + } + + insertText = pre + selText + post; + if ( options.splitlines ) { + insertText = doSplitLines( selText, pre, post ); + } + if ( options.ownline ) { + if ( startPos !== 0 && this.value.charAt( startPos - 1 ) !== "\n" && this.value.charAt( startPos - 1 ) !== "\r" ) { + insertText = "\n" + insertText; + pre += "\n"; + } + if ( this.value.charAt( endPos ) !== "\n" && this.value.charAt( endPos ) !== "\r" ) { + insertText += "\n"; + post += "\n"; + } + } + this.value = this.value.substring( 0, startPos ) + insertText + + this.value.substring( endPos, this.value.length ); + // Setting this.value scrolls the textarea to the top, restore the scroll position + this.scrollTop = scrollTop; + if ( window.opera ) { + pre = pre.replace( /\r?\n/g, "\r\n" ); + selText = selText.replace( /\r?\n/g, "\r\n" ); + post = post.replace( /\r?\n/g, "\r\n" ); + } + if ( isSample && options.selectPeri && !options.splitlines ) { + this.selectionStart = startPos + pre.length; + this.selectionEnd = startPos + pre.length + selText.length; } else { - preFinished = true; + this.selectionStart = startPos + insertText.length; + this.selectionEnd = this.selectionStart; } } + $(this).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline, + options.replace, options.spitlines ] ); + }); + }, + /** + * Ported from Wikia's LinkSuggest extension + * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest + * Some code copied from + * http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ + * + * Get the position (in resolution of bytes not nessecarily characters) + * in a textarea + * + * Will focus the textarea in some browsers (IE/Opera) + * + * @fixme document the options parameters + */ + getCaretPosition: function ( options ) { + function getCaret( e ) { + var caretPos = 0, endPos = 0; + if ( document.selection && document.selection.createRange ) { + // IE doesn't properly report non-selected caret position through + // the selection ranges when textarea isn't focused. This can + // lead to saving a bogus empty selection, which then screws up + // whatever we do later (bug 31847). + activateElementOnIE( e ); + + var + preText, rawPreText, periText, + rawPeriText, postText, rawPostText, + + // IE Support + preFinished = false, + periFinished = false, + postFinished = false, + // Range containing text in the selection + periRange = document.selection.createRange().duplicate(), + // Range containing text before the selection + preRange, + // Range containing text after the selection + postRange; + + preRange = rangeForElementIE( e ), + // Move the end where we need it + preRange.setEndPoint( 'EndToStart', periRange ); + + postRange = rangeForElementIE( e ); + // Move the start where we need it + postRange.setEndPoint( 'StartToEnd', periRange ); + + // Load the text values we need to compare + preText = rawPreText = preRange.text; + periText = rawPeriText = periRange.text; + postText = rawPostText = postRange.text; + + /* + * Check each range for trimmed newlines by shrinking the range by 1 + * character and seeing if the text property has changed. If it has + * not changed then we know that IE has trimmed a \r\n from the end. + */ + do { + if ( !preFinished ) { + if ( preRange.compareEndPoints( 'StartToEnd', preRange ) === 0 ) { + preFinished = true; + } else { + preRange.moveEnd( 'character', -1 ); + if ( preRange.text === preText ) { + rawPreText += "\r\n"; + } else { + preFinished = true; + } + } + } + if ( !periFinished ) { + if ( periRange.compareEndPoints( 'StartToEnd', periRange ) === 0 ) { + periFinished = true; + } else { + periRange.moveEnd( 'character', -1 ); + if ( periRange.text === periText ) { + rawPeriText += "\r\n"; + } else { + periFinished = true; + } + } + } + if ( !postFinished ) { + if ( postRange.compareEndPoints( 'StartToEnd', postRange ) === 0 ) { + postFinished = true; + } else { + postRange.moveEnd( 'character', -1 ); + if ( postRange.text === postText ) { + rawPostText += "\r\n"; + } else { + postFinished = true; + } + } + } + } while ( ( !preFinished || !periFinished || !postFinished ) ); + caretPos = rawPreText.replace( /\r\n/g, "\n" ).length; + endPos = caretPos + rawPeriText.replace( /\r\n/g, "\n" ).length; + } else if ( e.selectionStart || e.selectionStart === 0 ) { + // Firefox support + caretPos = e.selectionStart; + endPos = e.selectionEnd; + } + return options.startAndEnd ? [ caretPos, endPos ] : caretPos; } - if ( !periFinished ) { - if ( periRange.compareEndPoints( "StartToEnd", periRange ) == 0 ) { - periFinished = true; - } else { - periRange.moveEnd( "character", -1 ); - if ( periRange.text == periText ) { - rawPeriText += "\r\n"; + return getCaret( this.get( 0 ) ); + }, + /** + * @fixme document the options parameters + */ + setSelection: function ( options ) { + return this.each( function () { + var selection, length, newLines; + if ( $(this).is( ':hidden' ) ) { + // Do nothing + } else if ( this.selectionStart || this.selectionStart === 0 ) { + // Opera 9.0 doesn't allow setting selectionStart past + // selectionEnd; any attempts to do that will be ignored + // Make sure to set them in the right order + if ( options.start > this.selectionEnd ) { + this.selectionEnd = options.end; + this.selectionStart = options.start; } else { - periFinished = true; + this.selectionStart = options.start; + this.selectionEnd = options.end; } + } else if ( document.body.createTextRange ) { + selection = rangeForElementIE( this ); + length = this.value.length; + // IE doesn't count \n when computing the offset, so we won't either + newLines = this.value.match( /\n/g ); + if ( newLines ) { + length = length - newLines.length; + } + selection.moveStart( 'character', options.start ); + selection.moveEnd( 'character', -length + options.end ); + + // This line can cause an error under certain circumstances (textarea empty, no selection) + // Silence that error + try { + selection.select(); + } catch ( e ) { } } + }); + }, + /** + * Ported from Wikia's LinkSuggest extension + * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest + * + * 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 + * is already visible. Defaults to false + * + * @fixme document the options parameters (function body suggests options.force is a boolean, not options itself) + */ + scrollToCaretPosition: function ( options ) { + function getLineLength( e ) { + return Math.floor( e.scrollWidth / ( $.client.profile().platform === 'linux' ? 7 : 8 ) ); } - if ( !postFinished ) { - if ( postRange.compareEndPoints("StartToEnd", postRange) == 0 ) { - postFinished = true; - } else { - postRange.moveEnd( "character", -1 ); - if ( postRange.text == postText ) { - rawPostText += "\r\n"; - } else { - postFinished = true; + function getCaretScrollPosition( e ) { + var i, j; + // FIXME: This functions sucks and is off by a few lines most + // of the time. It should be replaced by something decent. + var text = e.value.replace( /\r/g, '' ); + var caret = $( e ).textSelection( 'getCaretPosition' ); + var lineLength = getLineLength( e ); + var row = 0; + var charInLine = 0; + var lastSpaceInLine = 0; + for ( i = 0; i < caret; i++ ) { + charInLine++; + if ( text.charAt( i ) === ' ' ) { + lastSpaceInLine = charInLine; + } else if ( text.charAt( i ) === "\n" ) { + lastSpaceInLine = 0; + charInLine = 0; + row++; + } + if ( charInLine > lineLength ) { + if ( lastSpaceInLine > 0 ) { + charInLine = charInLine - lastSpaceInLine; + lastSpaceInLine = 0; + row++; + } + } + } + var nextSpace = 0; + for ( j = caret; j < caret + lineLength; j++ ) { + if ( + text.charAt( j ) === ' ' || + text.charAt( j ) === "\n" || + caret === text.length + ) { + nextSpace = j; + break; } } + if ( nextSpace > lineLength && caret <= lineLength ) { + charInLine = caret - lastSpaceInLine; + row++; + } + return ( $.client.profile().platform === 'mac' ? 13 : ( $.client.profile().platform === 'linux' ? 15 : 16 ) ) * row; } - } while ( ( !preFinished || !periFinished || !postFinished ) ); - caretPos = rawPreText.replace( /\r\n/g, "\n" ).length; - endPos = caretPos + rawPeriText.replace( /\r\n/g, "\n" ).length; - } else if ( e.selectionStart || e.selectionStart == '0' ) { - // Firefox support - caretPos = e.selectionStart; - endPos = e.selectionEnd; - } - return options.startAndEnd ? [ caretPos, endPos ] : caretPos; - } - return getCaret( this.get( 0 ) ); -}, -/** - * @fixme document the options parameters - */ -setSelection: function( options ) { - return this.each( function() { - if ( $(this).is( ':hidden' ) ) { - // Do nothing - } else if ( this.selectionStart || this.selectionStart == '0' ) { - // Opera 9.0 doesn't allow setting selectionStart past - // selectionEnd; any attempts to do that will be ignored - // Make sure to set them in the right order - if ( options.start > this.selectionEnd ) { - this.selectionEnd = options.end; - this.selectionStart = options.start; - } else { - this.selectionStart = options.start; - this.selectionEnd = options.end; + return this.each(function () { + var scroll, range, savedRange, pos, oldScrollTop; + if ( $(this).is( ':hidden' ) ) { + // Do nothing + } else if ( this.selectionStart || this.selectionStart === 0 ) { + // Mozilla + scroll = getCaretScrollPosition( this ); + if ( options.force || scroll < $(this).scrollTop() || + scroll > $(this).scrollTop() + $(this).height() ) { + $(this).scrollTop( scroll ); + } + } else if ( document.selection && document.selection.createRange ) { + // IE / Opera + /* + * IE automatically scrolls the selected text to the + * bottom of the textarea at range.select() time, except + * if it was already in view and the cursor position + * wasn't changed, in which case it does nothing. To + * cover that case, we'll force it to act by moving one + * character back and forth. + */ + range = document.body.createTextRange(); + savedRange = document.selection.createRange(); + pos = $(this).textSelection( 'getCaretPosition' ); + oldScrollTop = this.scrollTop; + range.moveToElementText( this ); + range.collapse(); + range.move( 'character', pos + 1); + range.select(); + if ( this.scrollTop !== oldScrollTop ) { + this.scrollTop += range.offsetTop; + } else if ( options.force ) { + range.move( 'character', -1 ); + range.select(); + } + savedRange.select(); + } + $(this).trigger( 'scrollToPosition' ); + } ); } - } else if ( document.body.createTextRange ) { - var selection = rangeForElementIE( this ); - var length = this.value.length; - // IE doesn't count \n when computing the offset, so we won't either - var newLines = this.value.match( /\n/g ); - if ( newLines ) length = length - newLines.length; - selection.moveStart( 'character', options.start ); - selection.moveEnd( 'character', -length + options.end ); + }; - // This line can cause an error under certain circumstances (textarea empty, no selection) - // Silence that error - try { - selection.select(); - } catch( e ) { } - } - }); -}, -/** - * Ported from Wikia's LinkSuggest extension - * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest - * - * 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 - * is already visible. Defaults to false - * - * @fixme document the options parameters (function body suggests options.force is a boolean, not options itself) - */ -scrollToCaretPosition: function( options ) { - function getLineLength( e ) { - return Math.floor( e.scrollWidth / ( $.client.profile().platform == 'linux' ? 7 : 8 ) ); - } - function getCaretScrollPosition( e ) { - // FIXME: This functions sucks and is off by a few lines most - // of the time. It should be replaced by something decent. - var text = e.value.replace( /\r/g, "" ); - var caret = $( e ).textSelection( 'getCaretPosition' ); - var lineLength = getLineLength( e ); - var row = 0; - var charInLine = 0; - var lastSpaceInLine = 0; - for ( i = 0; i < caret; i++ ) { - charInLine++; - if ( text.charAt( i ) == " " ) { - lastSpaceInLine = charInLine; - } else if ( text.charAt( i ) == "\n" ) { - lastSpaceInLine = 0; - charInLine = 0; - row++; - } - if ( charInLine > lineLength ) { - if ( lastSpaceInLine > 0 ) { - charInLine = charInLine - lastSpaceInLine; - lastSpaceInLine = 0; - row++; + // Apply defaults + switch ( command ) { + //case 'getContents': // no params + //case 'setContents': // no params with defaults + //case 'getSelection': // no params + case 'encapsulateSelection': + options = $.extend( { + pre: '', // Text to insert before the cursor/selection + peri: '', // Text to insert between pre and post and select afterwards + post: '', // Text to insert after the cursor/selection + ownline: false, // Put the inserted text on a line of its own + replace: false, // If there is a selection, replace it with peri instead of leaving it alone + selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true) + splitlines: false, // If multiple lines are selected, encapsulate each line individually + selectionStart: undefined, // Position to start selection at + selectionEnd: undefined // Position to end selection at. Defaults to start + }, options ); + break; + case 'getCaretPosition': + options = $.extend( { + // Return [start, end] instead of just start + startAndEnd: false + }, options ); + // FIXME: We may not need character position-based functions if we insert markers in the right places + break; + case 'setSelection': + options = $.extend( { + // Position to start selection at + start: undefined, + // Position to end selection at. Defaults to start + end: undefined, + // Element to start selection in (iframe only) + startContainer: undefined, + // Element to end selection in (iframe only). Defaults to startContainer + endContainer: undefined + }, options ); + + if ( options.end === undefined ) { + options.end = options.start; } - } - } - var nextSpace = 0; - for ( j = caret; j < caret + lineLength; j++ ) { - if ( - text.charAt( j ) == " " || - text.charAt( j ) == "\n" || - caret == text.length - ) { - nextSpace = j; + if ( options.endContainer === undefined ) { + options.endContainer = options.startContainer; + } + // FIXME: We may not need character position-based functions if we insert markers in the right places + break; + case 'scrollToCaretPosition': + options = $.extend( { + force: false // Force a scroll even if the caret position is already visible + }, options ); break; - } } - if ( nextSpace > lineLength && caret <= lineLength ) { - charInLine = caret - lastSpaceInLine; - row++; + + var context = $(this).data( 'wikiEditor-context' ); + var hasIframe = typeof context !== 'undefined' && context && typeof context.$iframe !== 'undefined'; + + // IE selection restore voodoo + var needSave = false; + if ( hasIframe && context.savedSelection !== null ) { + context.fn.restoreSelection(); + needSave = true; } - return ( $.client.profile().platform == 'mac' ? 13 : ( $.client.profile().platform == 'linux' ? 15 : 16 ) ) * row; - } - return this.each(function() { - if ( $(this).is( ':hidden' ) ) { - // Do nothing - } else if ( this.selectionStart || this.selectionStart == '0' ) { - // Mozilla - var scroll = getCaretScrollPosition( this ); - if ( options.force || scroll < $(this).scrollTop() || - scroll > $(this).scrollTop() + $(this).height() ) - $(this).scrollTop( scroll ); - } else if ( document.selection && document.selection.createRange ) { - // IE / Opera - /* - * IE automatically scrolls the selected text to the - * bottom of the textarea at range.select() time, except - * if it was already in view and the cursor position - * wasn't changed, in which case it does nothing. To - * cover that case, we'll force it to act by moving one - * character back and forth. - */ - var range = document.body.createTextRange(); - var savedRange = document.selection.createRange(); - var pos = $(this).textSelection( 'getCaretPosition' ); - var oldScrollTop = this.scrollTop; - range.moveToElementText( this ); - range.collapse(); - range.move( 'character', pos + 1); - range.select(); - if ( this.scrollTop != oldScrollTop ) - this.scrollTop += range.offsetTop; - else if ( options.force ) { - range.move( 'character', -1 ); - range.select(); - } - savedRange.select(); + var retval = ( hasIframe ? context.fn : fn )[command].call( this, options ); + if ( hasIframe && needSave ) { + context.fn.saveSelection(); } - $(this).trigger( 'scrollToPosition' ); - } ); -} -}; - // Apply defaults - switch ( command ) { - //case 'getContents': // no params - //case 'setContents': // no params with defaults - //case 'getSelection': // no params - case 'encapsulateSelection': - options = $.extend( { - 'pre': '', // Text to insert before the cursor/selection - 'peri': '', // Text to insert between pre and post and select afterwards - 'post': '', // Text to insert after the cursor/selection - 'ownline': false, // Put the inserted text on a line of its own - 'replace': false, // If there is a selection, replace it with peri instead of leaving it alone - 'selectPeri': true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true) - 'splitlines': false, // If multiple lines are selected, encapsulate each line individually - 'selectionStart': undefined, // Position to start selection at - 'selectionEnd': undefined // Position to end selection at. Defaults to start - }, options ); - break; - case 'getCaretPosition': - options = $.extend( { - 'startAndEnd': false // Return [start, end] instead of just start - }, options ); - // FIXME: We may not need character position-based functions if we insert markers in the right places - break; - case 'setSelection': - options = $.extend( { - 'start': undefined, // Position to start selection at - 'end': undefined, // Position to end selection at. Defaults to start - 'startContainer': undefined, // Element to start selection in (iframe only) - 'endContainer': undefined // Element to end selection in (iframe only). Defaults to startContainer - }, options ); - if ( options.end === undefined ) - options.end = options.start; - if ( options.endContainer == undefined ) - options.endContainer = options.startContainer; - // FIXME: We may not need character position-based functions if we insert markers in the right places - break; - case 'scrollToCaretPosition': - options = $.extend( { - 'force': false // Force a scroll even if the caret position is already visible - }, options ); - break; - } - var context = $(this).data( 'wikiEditor-context' ); - var hasIframe = typeof context !== 'undefined' && context && typeof context.$iframe !== 'undefined'; - // IE selection restore voodoo - var needSave = false; - if ( hasIframe && context.savedSelection !== null ) { - context.fn.restoreSelection(); - needSave = true; - } - var retval = ( hasIframe ? context.fn : fn )[command].call( this, options ); - if ( hasIframe && needSave ) { - context.fn.saveSelection(); - } - return retval; -}; -} )( jQuery ); + return retval; + }; + +}( jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.edit.js b/resources/mediawiki.action/mediawiki.action.edit.js index e685ca94..1c51c974 100644 --- a/resources/mediawiki.action/mediawiki.action.edit.js +++ b/resources/mediawiki.action/mediawiki.action.edit.js @@ -1,42 +1,74 @@ -( function ( $, mw ) { - var isReady, toolbar, currentFocused; +( function ( mw, $ ) { + var isReady, toolbar, currentFocused, queue, $toolbar, slice; isReady = false; + queue = []; + $toolbar = false; + slice = Array.prototype.slice; + + /** + * Internal helper that does the actual insertion + * of the button into the toolbar. + * See mw.toolbar.addButton for parameter documentation. + */ + function insertButton( b /* imageFile */, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ) { + // Backwards compatibility + if ( typeof b !== 'object' ) { + b = { + imageFile: b, + speedTip: speedTip, + tagOpen: tagOpen, + tagClose: tagClose, + sampleText: sampleText, + imageId: imageId, + selectText: selectText + }; + } + var $image = $( '<img>', { + width : 23, + height: 22, + src : b.imageFile, + alt : b.speedTip, + title : b.speedTip, + id : b.imageId || undefined, + 'class': 'mw-toolbar-editbutton' + } ).click( function () { + toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText, b.selectText ); + return false; + } ); + + $toolbar.append( $image ); + return true; + } toolbar = { - $toolbar: false, - buttons: [], /** - * If you want to add buttons, use - * mw.toolbar.addButton( imageFile, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ); + * Add buttons to the toolbar. + * Takes care of race conditions and time-based dependencies + * by placing buttons in a queue if this method is called before + * the toolbar is created. + * @param {Object} button: Object with the following properties: + * - imageFile + * - speedTip + * - tagOpen + * - tagClose + * - sampleText + * - imageId + * - selectText + * For compatiblity, passing the above as separate arguments + * (in the listed order) is also supported. */ addButton: function () { if ( isReady ) { - toolbar.insertButton.apply( toolbar, arguments ); + insertButton.apply( toolbar, arguments ); } else { - toolbar.buttons.push( [].slice.call( arguments ) ); - } - }, - insertButton: function ( imageFile, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ) { - var image = $('<img>', { - width : 23, - height: 22, - src : imageFile, - alt : speedTip, - title : speedTip, - id : imageId || '', - 'class': 'mw-toolbar-editbutton' - } ).click( function () { - mw.toolbar.insertTags( tagOpen, tagClose, sampleText, selectText ); - return false; - } ); - - toolbar.$toolbar.append( image ); - return true; + // Convert arguments list to array + queue.push( slice.call( arguments ) ); + } }, /** - * apply tagOpen/tagClose to selection in textarea, + * Apply tagOpen/tagClose to selection in textarea, * use sampleText instead of selection if there is none. */ insertTags: function ( tagOpen, tagClose, sampleText, selectText ) { @@ -51,7 +83,8 @@ } }, - // For backwards compatibility + // For backwards compatibility, + // Called from EditPage.php, maybe in other places as well. init: function () {} }; @@ -59,29 +92,30 @@ window.addButton = toolbar.addButton; window.insertTags = toolbar.insertTags; - // Explose publicly + // Explose API publicly mw.toolbar = toolbar; $( document ).ready( function () { - var buttons, i, c, iframe; + var buttons, i, b, $iframe; // currentFocus is used to determine where to insert tags currentFocused = $( '#wpTextbox1' ); - // Populate the selector cache for $toolbar - toolbar.$toolbar = $( '#toolbar' ); + // Populate the selector cache for $toolbar + $toolbar = $( '#toolbar' ); // Legacy: Merge buttons from mwCustomEditButtons - buttons = [].concat( toolbar.buttons, window.mwCustomEditButtons ); + buttons = [].concat( queue, window.mwCustomEditButtons ); + // Clear queue + queue.length = 0; for ( i = 0; i < buttons.length; i++ ) { - if ( $.isArray( buttons[i] ) ) { - // Passes our button array as arguments - toolbar.insertButton.apply( toolbar, buttons[i] ); + b = buttons[i]; + if ( $.isArray( b ) ) { + // Forwarded arguments array from mw.toolbar.addButton + insertButton.apply( toolbar, b ); } else { - // Legacy mwCustomEditButtons is an object - c = buttons[i]; - toolbar.insertButton( c.imageFile, c.speedTip, c.tagOpen, - c.tagClose, c.sampleText, c.imageId, c.selectText ); + // Raw object from legacy mwCustomEditButtons + insertButton( b ); } } @@ -92,7 +126,7 @@ isReady = true; // Make sure edit summary does not exceed byte limit - $( '#wpSummary' ).byteLimit( 250 ); + $( '#wpSummary' ).byteLimit( 255 ); /** * Restore the edit box scroll state following a preview operation, @@ -120,15 +154,16 @@ // HACK: make currentFocused work with the usability iframe // With proper focus detection support (HTML 5!) this'll be much cleaner - iframe = $( '.wikiEditor-ui-text iframe' ); - if ( iframe.length > 0 ) { - $( iframe.get( 0 ).contentWindow.document ) + // TODO: Get rid of this WikiEditor code from MediaWiki core! + $iframe = $( '.wikiEditor-ui-text iframe' ); + if ( $iframe.length > 0 ) { + $( $iframe.get( 0 ).contentWindow.document ) // for IE - .add( iframe.get( 0 ).contentWindow.document.body ) + .add( $iframe.get( 0 ).contentWindow.document.body ) .focus( function () { - currentFocused = iframe; + currentFocused = $iframe; } ); } }); -}( jQuery, mediaWiki ) ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.edit.preview.js b/resources/mediawiki.action/mediawiki.action.edit.preview.js new file mode 100644 index 00000000..cddf6ccf --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.edit.preview.js @@ -0,0 +1,135 @@ +/** + * Live edit preview. + */ +( function ( mw, $ ) { + + /** + * @param {jQuery.Event} e + */ + function doLivePreview( e ) { + var $wikiPreview, copySelectors, removeSelectors, $copyElements, $spinner, + targetUrl, postData, $previewDataHolder; + + e.preventDefault(); + + $( mw ).trigger( 'LivePreviewPrepare' ); + + $wikiPreview = $( '#wikiPreview' ); + + // Show #wikiPreview if it's hidden to be able to scroll to it + // (if it is hidden, it's also empty, so nothing changes in the rendering) + $wikiPreview.show(); + + // Jump to where the preview will appear + $wikiPreview[0].scrollIntoView(); + + // List of selectors matching elements that we will + // update from from the ajax-loaded preview page. + copySelectors = [ + // Main + '#wikiPreview', + '#wikiDiff', + '#catlinks', + '.hiddencats', + '#p-lang', + // Editing-related + '.templatesUsed', + '.mw-summary-preview' + ]; + $copyElements = $( copySelectors.join( ',' ) ); + + // Not shown during normal preview, to be removed if present + removeSelectors = [ + '.mw-newarticletext' + ]; + + $( removeSelectors.join( ',' ) ).remove(); + + $spinner = $.createSpinner( { + size: 'large', + type: 'block' + }); + $wikiPreview.before( $spinner ); + $spinner.css( { + position: 'absolute', + marginTop: $spinner.height() + } ); + // Make sure preview area is at least as tall as 2x the height of the spinner. + // 1x because if its smaller, it will spin behind the edit toolbar. + // (this happens on the first preview when editPreview is still empty) + // 2x because the spinner has 1x margin top breathing room. + $wikiPreview.css( 'minHeight', $spinner.height() * 2 ); + + // Can't use fadeTo because it calls show(), and we might want to keep some elements hidden + // (e.g. empty #catlinks) + $copyElements.animate( { + opacity: 0.4 + }, 'fast' ); + + $previewDataHolder = $( '<div>' ); + targetUrl = $( '#editform' ).attr( 'action' ); + + // Gather all the data from the form + postData = $( '#editform' ).formToArray(); + postData.push( { + name: e.target.name, + value: '' + } ); + + // Load new preview data. + // TODO: This should use the action=parse API instead of loading the entire page + // Though that requires figuring out how to conver that raw data into proper HTML. + $previewDataHolder.load( targetUrl + ' ' + copySelectors.join( ',' ), postData, function () { + var i, $from; + // Copy the contents of the specified elements from the loaded page to the real page. + // Also copy their class attributes. + for ( i = 0; i < copySelectors.length; i++ ) { + $from = $previewDataHolder.find( copySelectors[i] ); + + $( copySelectors[i] ) + .empty() + .append( $from.contents() ) + .attr( 'class', $from.attr( 'class' ) ); + } + + $spinner.remove(); + $copyElements.animate( { + opacity: 1 + }, 'fast' ); + + $( mw ).trigger( 'LivePreviewDone', [copySelectors] ); + } ); + } + + $( document ).ready( function () { + // The following elements can change in a preview but are not output + // by the server when they're empty until the preview reponse. + // TODO: Make the server output these always (in a hidden state), so we don't + // have to fish and (hopefully) put them in the right place (since skins + // can change where they are output). + + if ( !document.getElementById( 'p-lang' ) && document.getElementById( 'p-tb' ) ) { + $( '#p-tb' ).after( + $( '<div>' ).prop( 'id', 'p-lang' ) + ); + } + + if ( !$( '.mw-summary-preview' ).length ) { + $( '.editCheckboxes' ).before( + $( '<div>' ).prop( 'className', 'mw-summary-preview' ) + ); + } + + if ( !document.getElementById( 'wikiDiff' ) && document.getElementById( 'wikiPreview' ) ) { + $( '#wikiPreview' ).after( + $( '<div>' ).prop( 'id', 'wikiDiff') + ); + } + + // Make sure diff styles are loaded + mw.loader.load( 'mediawiki.action.history.diff' ); + + $( document.body ).on( 'click', '#wpPreview, #wpDiff', doLivePreview ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.history.diff.css b/resources/mediawiki.action/mediawiki.action.history.diff.css index 3907a5f4..10473be7 100644 --- a/resources/mediawiki.action/mediawiki.action.history.diff.css +++ b/resources/mediawiki.action/mediawiki.action.history.diff.css @@ -4,33 +4,57 @@ table.diff, td.diff-otitle, td.diff-ntitle { background-color: white; } + td.diff-otitle, td.diff-ntitle { text-align: center; } + td.diff-marker { text-align: right; + font-weight: bold; + font-size: 1.25em; } + td.diff-lineno { font-weight: bold; } + +td.diff-addedline, +td.diff-deletedline, +td.diff-context { + font-size: 88%; + vertical-align: top; + white-space: -moz-pre-wrap; + white-space: pre-wrap; +} + +td.diff-addedline, +td.diff-deletedline { + border-style: solid; + border-width: 1px 1px 1px 4px; + border-radius: 0.33em; +} + td.diff-addedline { - background: #cfc; - font-size: smaller; + border-color: #a3d3ff; } + td.diff-deletedline { - background: #ffa; - font-size: smaller; + border-color: #ffe49c; } + td.diff-context { - background: #eee; - font-size: smaller; + background: #f3f3f3; + color: #333333; + border-style: solid; + border-width: 1px 1px 1px 4px; + border-color: #e6e6e6; + border-radius: 0.33em; } + .diffchange { - color: red; font-weight: bold; - white-space: -moz-pre-wrap; - white-space: pre-wrap; text-decoration: none; } @@ -42,20 +66,39 @@ table.diff { /* Ensure that colums are of equal width */ table-layout: fixed; } + +td.diff-addedline .diffchange, +td.diff-deletedline .diffchange { + border-radius: 0.33em; + padding: 0.25em 0; +} + +td.diff-addedline .diffchange { + background: #d8ecff; +} + +td.diff-deletedline .diffchange { + background: #feeec8; +} + table.diff td { - padding: 0; + padding: 0.33em 0.66em; } + table.diff col.diff-marker { width: 2%; } + table.diff col.diff-content { width: 48%; } + table.diff td div { /* Force-wrap very long lines such as URLs or page-widening char strings.*/ word-wrap: break-word; /* As fallback (FF<3.5, Opera <10.5), scrollbars will be added for very wide cells - instead of text overflowing or widening */ + instead of text overflowing or widening + */ overflow: auto; } diff --git a/resources/mediawiki.action/mediawiki.action.history.js b/resources/mediawiki.action/mediawiki.action.history.js index 76b0e6cd..55f799e5 100644 --- a/resources/mediawiki.action/mediawiki.action.history.js +++ b/resources/mediawiki.action/mediawiki.action.history.js @@ -30,20 +30,20 @@ jQuery( document ).ready( function ( $ ) { return true; } - if ( $oldidRadio.prop( 'checked' ) ) { + if ( $oldidRadio.prop( 'checked' ) ) { oldLi = true; $li.addClass( 'selected' ); $oldidRadio.css( 'visibility', 'visible' ); $diffRadio.css( 'visibility', 'hidden' ); - } else if ( $diffRadio.prop( 'checked' ) ) { + } else if ( $diffRadio.prop( 'checked' ) ) { diffLi = true; $li.addClass( 'selected' ); $oldidRadio.css( 'visibility', 'hidden' ); $diffRadio.css( 'visibility', 'visible' ); // This list item has neither checked - } else { + } else { // We're below the selected radios if ( diffLi && oldLi ) { $oldidRadio.css( 'visibility', 'visible' ); diff --git a/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js b/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js index b1d906f6..7a9ceee5 100644 --- a/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js @@ -1,12 +1,14 @@ /** * This module enables double-click-to-edit functionality */ -jQuery( document ).ready( function( $ ) { - var url = $( '#ca-edit a' ).attr( 'href' ); - if ( url ) { - mw.util.$content.dblclick( function( e ) { - e.preventDefault(); - window.location = url; - } ); - } -} ); +( function ( mw, $ ) { + $( function () { + var url = $( '#ca-edit a' ).attr( 'href' ); + if ( url ) { + mw.util.$content.dblclick( function ( e ) { + e.preventDefault(); + window.location = url; + } ); + } + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.view.metadata.js b/resources/mediawiki.action/mediawiki.action.view.metadata.js index b791cabd..ce3c674b 100644 --- a/resources/mediawiki.action/mediawiki.action.view.metadata.js +++ b/resources/mediawiki.action/mediawiki.action.view.metadata.js @@ -1,39 +1,43 @@ -// Exif metadata display for MediaWiki file uploads -// -// Add an expand/collapse link and collapse by default if set to -// (with JS disabled, user will see all items) -// +/** + * Exif metadata display for MediaWiki file uploads + * + * Add an expand/collapse link and collapse by default if set to + * (with JS disabled, user will see all items) + */ +( function ( mw, $ ) { + $( function () { + var $row, $col, $link, + showText = mw.msg( 'metadata-expand' ), + hideText = mw.msg( 'metadata-collapse' ), + $table = $( '#mw_metadata' ), + $tbody = $table.find( 'tbody' ); -jQuery( document ).ready( function( $ ) { - var showText = mw.msg( 'metadata-expand' ); - var hideText = mw.msg( 'metadata-collapse' ); + if ( !$tbody.length ) { + return; + } - var $table = $( '#mw_metadata' ); - var $tbody = $table.find( 'tbody' ); - if ( !$tbody.length ) { - return; - } + $row = $( '<tr class="mw-metadata-show-hide-extended"></tr>' ); + $col = $( '<td colspan="2"></td>' ); - var $row = $( '<tr class="mw-metadata-show-hide-extended"></tr>' ); - var $col = $( '<td colspan="2"></td>' ); + $link = $( '<a>', { + text: showText, + href: '#' + }).click(function () { + if ( $table.hasClass( 'collapsed' ) ) { + $( this ).text( hideText ); + } else { + $( this ).text( showText ); + } + $table.toggleClass( 'expanded collapsed' ); + return false; + }); - var $link = $( '<a></a>', { - 'text': showText, - 'href': '#' - }).click(function() { - if ( $table.hasClass( 'collapsed' ) ) { - $( this ).text( hideText ); - } else { - $( this ).text( showText ); - } - $table.toggleClass( 'expanded collapsed' ); - return false; - }); + $col.append( $link ); + $row.append( $col ); + $tbody.append( $row ); - $col.append( $link ); - $row.append( $col ); - $tbody.append( $row ); + // And collapse! + $table.addClass( 'collapsed' ); + } ); - // And collapse! - $table.addClass( 'collapsed' ); -} ); +}( mediaWiki, jQuery ) );
\ No newline at end of file diff --git a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js index caf9a9f2..d02d4327 100644 --- a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js @@ -1,24 +1,30 @@ /* - * JavaScript to enable right click edit functionality + * JavaScript to enable right click edit functionality. + * When the user right-clicks in a heading, it will open the + * edit screen. */ -jQuery( function( $ ) { +jQuery( function ( $ ) { // Select all h1-h6 elements that contain editsection links - $( 'h1:has(.editsection a), ' + - 'h2:has(.editsection a), ' + - 'h3:has(.editsection a), ' + - 'h4:has(.editsection a), ' + - 'h5:has(.editsection a), ' + - 'h6:has(.editsection a)' - ).live( 'contextmenu', function( e ) { - // Get href of the [edit] link - var href = $(this).find( '.editsection a' ).attr( 'href' ); - // Check if target is the anchor link itself. If so, don't suppress the context menu; this - // way the reader can still do things like copy URL, open in new tab etc. - var $target = $( e.target ); - if ( !$target.is( 'a' ) && !$target.parent().is( '.editsection' ) ){ + // Don't use the ":has:(.editsection a)" selector because it performs very bad. + // http://jsperf.com/jq-1-7-2-vs-jq-1-8-1-performance-of-mw-has/2 + $( document ).on( 'contextmenu', 'h1, h2, h3, h4, h5, h6', function ( e ) { + var $edit, href; + + $edit = $( this ).find( '.editsection a' ); + if ( !$edit.length ) { + return; + } + + // Get href of the editsection link + href = $edit.prop( 'href' ); + + // Headings can contain rich text. + // Make sure to not block contextmenu events on (other) anchor tags + // inside the heading (e.g. to do things like copy URL, open in new tab, ..). + // e.target can be the heading, but it can also be anything inside the heading. + if ( href && e.target.nodeName.toLowerCase() !== 'a' ) { window.location = href; e.preventDefault(); - return false; } } ); } ); diff --git a/resources/mediawiki.action/mediawiki.action.watch.ajax.js b/resources/mediawiki.action/mediawiki.action.watch.ajax.js deleted file mode 100644 index f5f09f52..00000000 --- a/resources/mediawiki.action/mediawiki.action.watch.ajax.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Animate watch/unwatch links to use asynchronous API requests to - * watch pages, rather than navigating to a different URI. - */ -( function ( $, mw, undefined ) { - -/** - * The name of the page to watch or unwatch. - */ -var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) ); - -/** - * Update the link text, link href attribute and (if applicable) - * "loading" class. - * - * @param $link {jQuery} Anchor tag of (un)watch link - * @param action {String} One of 'watch', 'unwatch'. - * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'. - */ -function updateWatchLink( $link, action, state ) { - // message keys 'watch', 'watching', 'unwatch' or 'unwatching'. - var msgKey = state === 'loading' ? action + 'ing' : action, - accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp ), - $li = $link.closest( 'li' ); - - $link - .text( mw.msg( msgKey ) ) - .attr( 'title', mw.msg( 'tooltip-ca-' + action ) + - ( accesskeyTip ? ' ' + accesskeyTip[0] : '' ) - ) - .attr( 'href', mw.util.wikiScript() + '?' + $.param({ - title: title, - action: action - }) - ); - - // Special case for vector icon - if ( $li.hasClass( 'icon' ) ) { - if ( state === 'loading' ) { - $link.addClass( 'loading' ); - } else { - $link.removeClass( 'loading' ); - } - } -} - -/** - * @todo This should be moved somewhere more accessible. - * @param url {String} - * @return {String} The extracted action, defaults to 'view'. - */ -function mwUriGetAction( url ) { - var actionPaths = mw.config.get( 'wgActionPaths' ), - key, parts, m, action; - - // @todo: Does MediaWiki give action path or query param - // precedence ? If the former, move this to the bottom - action = mw.util.getParamValue( 'action', url ); - if ( action !== null ) { - return action; - } - - for ( key in actionPaths ) { - if ( actionPaths.hasOwnProperty( key ) ) { - parts = actionPaths[key].split( '$1' ); - for ( i = 0; i < parts.length; i += 1 ) { - parts[i] = $.escapeRE( parts[i] ); - } - m = new RegExp( parts.join( '(.+)' ) ).exec( url ); - if ( m && m[1] ) { - return key; - } - - } - } - - return 'view'; -} - -$( document ).ready( function() { - var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + - '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + - '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); - - // Allowing people to add inline animated links is a little scary - $links = $links.filter( ':not( #bodyContent *, #content * )' ); - - $links.click( function( e ) { - var $link, api, - action = mwUriGetAction( this.href ); - - if ( action !== 'watch' && action !== 'unwatch' ) { - // Could not extract target action from link url, - // let native browsing handle it further - return true; - } - e.preventDefault(); - e.stopPropagation(); - - $link = $( this ); - - updateWatchLink( $link, action, 'loading' ); - - api = new mw.Api(); - api[action]( - title, - // Success - function( watchResponse ) { - var otherAction = action === 'watch' ? 'unwatch' : 'watch', - $li = $link.closest( 'li' ); - - mw.util.jsMessage( watchResponse.message, 'ajaxwatch' ); - - // Set link to opposite - updateWatchLink( $link, otherAction ); - - // Most common ID style - if ( $li.prop( 'id' ) === 'ca-' + otherAction || $li.prop( 'id' ) === 'ca-' + action ) { - $li.prop( 'id', 'ca-' + otherAction ); - } - - // Bug 12395 - update the watch checkbox on edit pages when the - // page is watched or unwatched via the tab. - if ( watchResponse.watched !== undefined ) { - $( '#wpWatchthis' ).prop( 'checked', true ); - } else { - $( '#wpWatchthis' ).removeProp( 'checked' ); - } - }, - // Error - function(){ - - // Reset link to non-loading mode - updateWatchLink( $link, action ); - - // Format error message - var cleanTitle = title.replace( /_/g, ' ' ); - var link = mw.html.element( - 'a', { - 'href': mw.util.wikiGetlink( title ), - 'title': cleanTitle - }, cleanTitle - ); - var html = mw.msg( 'watcherrortext', link ); - - // Report to user about the error - mw.util.jsMessage( html, 'ajaxwatch' ); - - } - ); - }); - -}); - -})( jQuery, mediaWiki ); diff --git a/resources/mediawiki.api/mediawiki.api.category.js b/resources/mediawiki.api/mediawiki.api.category.js index c8c18e69..cc6f704f 100644 --- a/resources/mediawiki.api/mediawiki.api.category.js +++ b/resources/mediawiki.api/mediawiki.api.category.js @@ -1,8 +1,7 @@ /** * Additional mw.Api methods to assist with API calls related to categories. */ - -( function( $, mw, undefined ) { +( function ( mw, $ ) { $.extend( mw.Api.prototype, { /** @@ -12,22 +11,23 @@ * @param err {Function} optional callback to run if api error * @return ajax call object */ - isCategory: function( title, success, err ) { - var params = { - prop: 'categoryinfo', - titles: title.toString() - }, - ok = function( data ) { - var exists = false; - if ( data.query && data.query.pages ) { - $.each( data.query.pages, function( id, page ) { - if ( page.categoryinfo ) { - exists = true; - } - } ); - } - success( exists ); - }; + isCategory: function ( title, success, err ) { + var params, ok; + params = { + prop: 'categoryinfo', + titles: title.toString() + }; + ok = function ( data ) { + var exists = false; + if ( data.query && data.query.pages ) { + $.each( data.query.pages, function ( id, page ) { + if ( page.categoryinfo ) { + exists = true; + } + } ); + } + success( exists ); + }; return this.get( params, { ok: ok, err: err } ); }, @@ -40,19 +40,18 @@ * @param err {Function} optional callback to run if api error * @return {jqXHR} */ - getCategoriesByPrefix: function( prefix, success, err ) { - - // fetch with allpages to only get categories that have a corresponding description page. - var params = { + getCategoriesByPrefix: function ( prefix, success, err ) { + // Fetch with allpages to only get categories that have a corresponding description page. + var params, ok; + params = { 'list': 'allpages', 'apprefix': prefix, 'apnamespace': mw.config.get('wgNamespaceIds').category }; - - var ok = function( data ) { + ok = function ( data ) { var texts = []; if ( data.query && data.query.allpages ) { - $.each( data.query.allpages, function( i, category ) { + $.each( data.query.allpages, function ( i, category ) { texts.push( new mw.Title( category.title ).getNameText() ); } ); } @@ -71,7 +70,7 @@ * @param async {Boolean} optional asynchronousness (default = true = async) * @return {jqXHR} */ - getCategories: function( title, success, err, async ) { + getCategories: function ( title, success, err, async ) { var params, ok; params = { prop: 'categories', @@ -80,15 +79,15 @@ if ( async === undefined ) { async = true; } - ok = function( data ) { + ok = function ( data ) { var ret = false; if ( data.query && data.query.pages ) { - $.each( data.query.pages, function( id, page ) { + $.each( data.query.pages, function ( id, page ) { if ( page.categories ) { if ( typeof ret !== 'object' ) { ret = []; } - $.each( page.categories, function( i, cat ) { + $.each( page.categories, function ( i, cat ) { ret.push( new mw.Title( cat.title ) ); } ); } @@ -102,4 +101,4 @@ } ); -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.edit.js b/resources/mediawiki.api/mediawiki.api.edit.js index a9d488a8..49af9375 100644 --- a/resources/mediawiki.api/mediawiki.api.edit.js +++ b/resources/mediawiki.api/mediawiki.api.edit.js @@ -1,8 +1,7 @@ /** * Additional mw.Api methods to assist with API calls related to editing wiki pages. */ - -( function( $, mw, undefined ) { +( function ( mw, $ ) { // Cache token so we don't have to keep fetching new ones for every single request. var cachedToken = null; @@ -19,13 +18,14 @@ * @param err {Function} [optional] error callback * @return {jqXHR} */ - postWithEditToken: function( params, ok, err ) { - var api = this, useTokenToPost, getTokenIfBad; + postWithEditToken: function ( params, ok, err ) { + var useTokenToPost, getTokenIfBad, + api = this; if ( cachedToken === null ) { // We don't have a valid cached token, so get a fresh one and try posting. // We do not trap any 'badtoken' or 'notoken' errors, because we don't want // an infinite loop. If this fresh token is bad, something else is very wrong. - useTokenToPost = function( token ) { + useTokenToPost = function ( token ) { params.token = token; api.post( params, ok, err ); }; @@ -34,9 +34,10 @@ // We do have a token, but it might be expired. So if it is 'bad' then // start over with a new token. params.token = cachedToken; - getTokenIfBad = function( code, result ) { + getTokenIfBad = function ( code, result ) { if ( code === 'badtoken' ) { - cachedToken = null; // force a new token + // force a new token, clear any old one + cachedToken = null; api.postWithEditToken( params, ok, err ); } else { err( code, result ); @@ -58,23 +59,17 @@ * @param err {Function} error callback * @return {jqXHR} */ - getEditToken: function( tokenCallback, err ) { + getEditToken: function ( tokenCallback, err ) { var parameters = { - prop: 'info', - intoken: 'edit', - // we need some kind of dummy page to get a token from. This will return a response - // complaining that the page is missing, but we should also get an edit token - titles: 'DummyPageForEditToken' + action: 'tokens', + type: 'edit' }, - ok = function( data ) { + ok = function ( data ) { var token; - $.each( data.query.pages, function( i, page ) { - if ( page.edittoken ) { - token = page.edittoken; - return false; - } - } ); - if ( token !== undefined ) { + // If token type is not available for this user, + // key 'edittoken' is missing or can contain Boolean false + if ( data.tokens && data.tokens.edittoken ) { + token = data.tokens.edittoken; cachedToken = token; tokenCallback( token ); } else { @@ -102,7 +97,7 @@ * @param err {Function} error handler * @return {jqXHR} */ - newSection: function( title, header, message, ok, err ) { + newSection: function ( title, header, message, ok, err ) { var params = { action: 'edit', section: 'new', @@ -116,4 +111,4 @@ } ); -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.js b/resources/mediawiki.api/mediawiki.api.js index 225093b3..a184e3ca 100644 --- a/resources/mediawiki.api/mediawiki.api.js +++ b/resources/mediawiki.api/mediawiki.api.js @@ -1,6 +1,7 @@ -/* mw.Api objects represent the API of a particular MediaWiki server. */ - -( function( $, mw, undefined ) { +/** + * mw.Api objects represent the API of a particular MediaWiki server. + */ +( function ( mw, $ ) { /** * @var defaultOptions {Object} @@ -21,14 +22,7 @@ ajax: { url: mw.util.wikiScript( 'api' ), - ok: function() {}, - - // caller can supply handlers for http transport error or api errors - err: function( code, result ) { - mw.log( 'mw.Api error: ' + code, 'debug' ); - }, - - timeout: 30000, // 30 seconds + timeout: 30 * 1000, // 30 seconds dataType: 'json' } @@ -53,7 +47,7 @@ * @param options {Object} See defaultOptions documentation above. Ajax options can also be * overridden for each individual request to jQuery.ajax() later on. */ - mw.Api = function( options ) { + mw.Api = function ( options ) { if ( options === undefined ) { options = {}; @@ -73,34 +67,31 @@ mw.Api.prototype = { /** - * For api queries, in simple cases the caller just passes a success callback. - * In complex cases they pass an object with a success property as callback and - * probably other options. - * Normalize the argument so that it's always the latter case. + * Normalize the ajax options for compatibility and/or convenience methods. * - * @param {Object|Function} An object contaning one or more of options.ajax, + * @param {undefined|Object|Function} An object contaning one or more of options.ajax, * or just a success function (options.ajax.ok). * @return {Object} Normalized ajax options. */ - normalizeAjaxOptions: function( arg ) { - var opt = arg; + normalizeAjaxOptions: function ( arg ) { + // Arg argument is usually empty + // (before MW 1.20 it was often used to pass ok/err callbacks) + var opts = arg || {}; + // Options can also be a success callback handler if ( typeof arg === 'function' ) { - opt = { 'ok': arg }; + opts = { ok: arg }; } - if ( !opt.ok ) { - throw new Error( 'ajax options must include ok callback' ); - } - return opt; + return opts; }, /** * Perform API get request * * @param {Object} request parameters - * @param {Object|Function} ajax options, or just a success function - * @return {jqXHR} + * @param {Object|Function} [optional] ajax options + * @return {jQuery.Promise} */ - get: function( parameters, ajaxOptions ) { + get: function ( parameters, ajaxOptions ) { ajaxOptions = this.normalizeAjaxOptions( ajaxOptions ); ajaxOptions.type = 'GET'; return this.ajax( parameters, ajaxOptions ); @@ -111,10 +102,10 @@ * @todo Post actions for nonlocal will need proxy * * @param {Object} request parameters - * @param {Object|Function} ajax options, or just a success function - * @return {jqXHR} + * @param {Object|Function} [optional] ajax options + * @return {jQuery.Promise} */ - post: function( parameters, ajaxOptions ) { + post: function ( parameters, ajaxOptions ) { ajaxOptions = this.normalizeAjaxOptions( ajaxOptions ); ajaxOptions.type = 'POST'; return this.ajax( parameters, ajaxOptions ); @@ -125,39 +116,72 @@ * * @param {Object} request parameters * @param {Object} ajax options - * @return {jqXHR} + * @return {jQuery.Promise} + * - done: API response data as first argument + * - fail: errorcode as first arg, details (string or object) as second arg. */ - ajax: function( parameters, ajaxOptions ) { + ajax: function ( parameters, ajaxOptions ) { + var token, + apiDeferred = $.Deferred(); + parameters = $.extend( {}, this.defaults.parameters, parameters ); ajaxOptions = $.extend( {}, this.defaults.ajax, ajaxOptions ); + // Ensure that token parameter is last (per [[mw:API:Edit#Token]]). + if ( parameters.token ) { + token = parameters.token; + delete parameters.token; + } // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug // So let's escape them here. See bug #28235 // This works because jQuery accepts data as a query string or as an Object ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' ); - ajaxOptions.error = function( xhr, textStatus, exception ) { - ajaxOptions.err( 'http', { - xhr: xhr, - textStatus: textStatus, - exception: exception + // If we extracted a token parameter, add it back in. + if ( token ) { + ajaxOptions.data += '&token=' + encodeURIComponent( token ); + } + + // Backwards compatibility: Before MediaWiki 1.20, + // callbacks were done with the 'ok' and 'err' property in ajaxOptions. + if ( ajaxOptions.ok ) { + apiDeferred.done( ajaxOptions.ok ); + delete ajaxOptions.ok; + } + if ( ajaxOptions.err ) { + apiDeferred.fail( ajaxOptions.err ); + delete ajaxOptions.err; + } + + // Make the AJAX request + $.ajax( ajaxOptions ) + // If AJAX fails, reject API call with error code 'http' + // and details in second argument. + .fail( function ( xhr, textStatus, exception ) { + apiDeferred.reject( 'http', { + xhr: xhr, + textStatus: textStatus, + exception: exception + } ); + } ) + // AJAX success just means "200 OK" response, also check API error codes + .done( function ( result ) { + if ( result === undefined || result === null || result === '' ) { + apiDeferred.reject( 'ok-but-empty', + 'OK response but empty result (check HTTP headers?)' + ); + } else if ( result.error ) { + var code = result.error.code === undefined ? 'unknown' : result.error.code; + apiDeferred.reject( code, result ); + } else { + apiDeferred.resolve( result ); + } } ); - }; - - // Success just means 200 OK; also check for output and API errors - ajaxOptions.success = function( result ) { - if ( result === undefined || result === null || result === '' ) { - ajaxOptions.err( 'ok-but-empty', - 'OK response but empty result (check HTTP headers?)' ); - } else if ( result.error ) { - var code = result.error.code === undefined ? 'unknown' : result.error.code; - ajaxOptions.err( code, result ); - } else { - ajaxOptions.ok( result ); - } - }; - - return $.ajax( ajaxOptions ); + + // Return the Promise + return apiDeferred.promise().fail( function ( code, details ) { + mw.log( 'mw.Api error: ', code, details ); + }); } }; @@ -197,6 +221,7 @@ 'file-too-large', 'filetype-missing', 'filetype-banned', + 'filetype-banned-type', 'filename-tooshort', 'illegal-filename', 'verification-error', @@ -221,4 +246,4 @@ 'exists' ]; -})( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.parse.js b/resources/mediawiki.api/mediawiki.api.parse.js index 1cc68f29..e8d1b3e6 100644 --- a/resources/mediawiki.api/mediawiki.api.parse.js +++ b/resources/mediawiki.api/mediawiki.api.parse.js @@ -1,31 +1,42 @@ /** - * Additional mw.Api methods to assist with API calls related to parsing wikitext. + * mw.Api methods for parsing wikitext. */ - -( function( $, mw ) { +( function ( mw, $ ) { $.extend( mw.Api.prototype, { /** * Convinience method for 'action=parse'. Parses wikitext into HTML. * * @param wikiText {String} - * @param success {Function} callback to which to pass success HTML - * @param err {Function} callback if error (optional) - * @return {jqXHR} + * @param ok {Function} [optional] deprecated (success callback) + * @param err {Function} [optional] deprecated (error callback) + * @return {jQuery.Promise} */ - parse: function( wikiText, success, err ) { - var params = { - text: wikiText, - action: 'parse' - }, - ok = function( data ) { + parse: function ( wikiText, ok, err ) { + var apiDeferred = $.Deferred(); + + // Backwards compatibility (< MW 1.20) + if ( ok ) { + apiDeferred.done( ok ); + } + if ( err ) { + apiDeferred.fail( err ); + } + + this.get( { + action: 'parse', + text: wikiText + } ) + .done( function ( data ) { if ( data.parse && data.parse.text && data.parse.text['*'] ) { - success( data.parse.text['*'] ); + apiDeferred.resolve( data.parse.text['*'] ); } - }; - return this.get( params, { ok: ok, err: err } ); - } + } ) + .fail( apiDeferred.reject ); + // Return the promise + return apiDeferred.promise(); + } } ); -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.titleblacklist.js b/resources/mediawiki.api/mediawiki.api.titleblacklist.js index 5435945b..1f7e275a 100644 --- a/resources/mediawiki.api/mediawiki.api.titleblacklist.js +++ b/resources/mediawiki.api/mediawiki.api.titleblacklist.js @@ -2,7 +2,7 @@ * Additional mw.Api methods to assist with API calls to the API module of the TitleBlacklist extension. */ -( function( $, mw, undefined ) { +( function ( mw, $ ) { $.extend( mw.Api.prototype, { /** @@ -15,13 +15,13 @@ * @param err {Function} optional callback to run if api error * @return {jqXHR} */ - isBlacklisted: function( title, success, err ) { + isBlacklisted: function ( title, success, err ) { var params = { action: 'titleblacklist', tbaction: 'create', tbtitle: title.toString() }, - ok = function( data ) { + ok = function ( data ) { var result; // this fails open (if nothing valid is returned by the api, allows the title) @@ -48,4 +48,5 @@ } } ); -} )( jQuery, mediaWiki ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.watch.js b/resources/mediawiki.api/mediawiki.api.watch.js index 3f2525ad..d3234421 100644 --- a/resources/mediawiki.api/mediawiki.api.watch.js +++ b/resources/mediawiki.api/mediawiki.api.watch.js @@ -2,57 +2,55 @@ * Additional mw.Api methods to assist with (un)watching wiki pages. * @since 1.19 */ -( function( $, mw ) { +( function ( mw, $ ) { + + /** + * @context {mw.Api} + */ + function doWatchInternal( page, success, err, addParams ) { + var params = { + action: 'watch', + title: String( page ), + token: mw.user.tokens.get( 'watchToken' ), + uselang: mw.config.get( 'wgUserLanguage' ) + }; + function ok( data ) { + success( data.watch ); + } + if ( addParams ) { + $.extend( params, addParams ); + } + return this.post( params, { ok: ok, err: err } ); + } $.extend( mw.Api.prototype, { /** * Convinience method for 'action=watch'. * * @param page {String|mw.Title} Full page name or instance of mw.Title - * @param success {Function} callback to which the watch object will be passed - * watch object contains 'title' (full page name), 'watched' (boolean) and + * @param success {Function} Callback to which the watch object will be passed. + * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and * 'message' (parsed HTML of the 'addedwatchtext' message). - * @param err {Function} callback if error (optional) + * @param err {Function} Error callback (optional) * @return {jqXHR} */ - watch: function( page, success, err ) { - var params, ok; - params = { - action: 'watch', - title: String( page ), - token: mw.user.tokens.get( 'watchToken' ), - uselang: mw.config.get( 'wgUserLanguage' ) - }; - ok = function( data ) { - success( data.watch ); - }; - return this.post( params, { ok: ok, err: err } ); + watch: function ( page, success, err ) { + return doWatchInternal.call( this, page, success, err ); }, /** - * Convinience method for 'action=watch&unwatch='. + * Convinience method for 'action=watch&unwatch=1'. * * @param page {String|mw.Title} Full page name or instance of mw.Title - * @param success {Function} callback to which the watch object will be passed - * watch object contains 'title' (full page name), 'unwatched' (boolean) and + * @param success {Function} Callback to which the watch object will be passed. + * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and * 'message' (parsed HTML of the 'removedwatchtext' message). - * @param err {Function} callback if error (optional) + * @param err {Function} Error callback (optional) * @return {jqXHR} */ - unwatch: function( page, success, err ) { - var params, ok; - params = { - action: 'watch', - unwatch: 1, - title: String( page ), - token: mw.user.tokens.get( 'watchToken' ), - uselang: mw.config.get( 'wgUserLanguage' ) - }; - ok = function( data ) { - success( data.watch ); - }; - return this.post( params, { ok: ok, err: err } ); + unwatch: function ( page, success, err ) { + return doWatchInternal.call( this, page, success, err, { unwatch: 1 } ); } } ); -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.language/languages/am.js b/resources/mediawiki.language/languages/am.js deleted file mode 100644 index fa5d3c57..00000000 --- a/resources/mediawiki.language/languages/am.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Amharic (አማርኛ) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/ar.js b/resources/mediawiki.language/languages/ar.js deleted file mode 100644 index 7d9e114f..00000000 --- a/resources/mediawiki.language/languages/ar.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Arabic (العربية) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 6 ); - if ( count == 0 ) { - return forms[0]; - } - if ( count == 1 ) { - return forms[1]; - } - if ( count == 2 ) { - return forms[2]; - } - if ( count % 100 >= 3 && count % 100 <= 10 ) { - return forms[3]; - } - if ( count % 100 >= 11 && count % 100 <= 99 ) { - return forms[4]; - } - return forms[5]; -}; - -mediaWiki.language.digitTransformTable = { - '0': '٠', // ٠ - '1': '١', // ١ - '2': '٢', // ٢ - '3': '٣', // ٣ - '4': '٤', // ٤ - '5': '٥', // ٥ - '6': '٦', // ٦ - '7': '٧', // ٧ - '8': '٨', // ٨ - '9': '٩', // ٩ - '.': '٫', // ٫ wrong table ? - ',': '٬' // ٬ -}; diff --git a/resources/mediawiki.language/languages/bat-smg.js b/resources/mediawiki.language/languages/bat-smg.js deleted file mode 100644 index 6e3f4cc9..00000000 --- a/resources/mediawiki.language/languages/bat-smg.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Samogitian (Žemaitėška) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - count = Math.abs( count ); - if ( count === 0 || ( count % 100 === 0 || ( count % 100 >= 10 && count % 100 < 20 ) ) ) { - return forms[2]; - } - if ( count % 10 === 1 ) { - return forms[0]; - } - if ( count % 10 === 2 ) { - return forms[1]; - } - return forms[3]; -}; diff --git a/resources/mediawiki.language/languages/be-tarask.js b/resources/mediawiki.language/languages/be-tarask.js deleted file mode 100644 index c19f47d1..00000000 --- a/resources/mediawiki.language/languages/be-tarask.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Belarusian in Taraskievica orthography (Беларуская (тарашкевіца)) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - if ( forms.length === 2 ) { - return count == 1 ? forms[0] : forms[1]; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/be.js b/resources/mediawiki.language/languages/be.js deleted file mode 100644 index 97ceee3b..00000000 --- a/resources/mediawiki.language/languages/be.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Belarusian normative (Беларуская) language functions - */ - -mediaWiki.language.convertPlural = function convertPlural( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/bh.js b/resources/mediawiki.language/languages/bh.js deleted file mode 100644 index abbf8bd9..00000000 --- a/resources/mediawiki.language/languages/bh.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Bihari (भोजपुरी) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/bs.js b/resources/mediawiki.language/languages/bs.js index 9f22426a..c0c77aaf 100644 --- a/resources/mediawiki.language/languages/bs.js +++ b/resources/mediawiki.language/languages/bs.js @@ -2,19 +2,18 @@ * Bosnian (bosanski) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'bs', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; + switch ( form ) { + case 'instrumental': // instrumental + word = 's ' + word; + break; + case 'lokativ': // locative + word = 'o ' + word; + break; } + return word; }; diff --git a/resources/mediawiki.language/languages/cs.js b/resources/mediawiki.language/languages/cs.js deleted file mode 100644 index 04dda99d..00000000 --- a/resources/mediawiki.language/languages/cs.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Czech (čeština [subst.], český [adj.], česky [adv.]) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - switch ( count ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/cu.js b/resources/mediawiki.language/languages/cu.js deleted file mode 100644 index 35a54739..00000000 --- a/resources/mediawiki.language/languages/cu.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Old Church Slavonic (Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - return forms[1]; - case 3: - case 4: - return forms[2]; - default: - return forms[3]; - } -}; diff --git a/resources/mediawiki.language/languages/cy.js b/resources/mediawiki.language/languages/cy.js deleted file mode 100644 index 333d956f..00000000 --- a/resources/mediawiki.language/languages/cy.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Welsh (Cymraeg) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 6 ); - count = Math.abs( count ); - if ( count >= 0 && count <= 3 ) { - return forms[count]; - } - if ( count == 6 ) { - return forms[4]; - } - return forms[5]; -}; diff --git a/resources/mediawiki.language/languages/dsb.js b/resources/mediawiki.language/languages/dsb.js index c3eaa1fc..a42a8f7f 100644 --- a/resources/mediawiki.language/languages/dsb.js +++ b/resources/mediawiki.language/languages/dsb.js @@ -2,17 +2,18 @@ * Lower Sorbian (Dolnoserbski) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - switch ( Math.abs( count ) % 100 ) { - case 1: - return forms[0]; - case 2: - return forms[1]; - case 3: - case 4: - return forms[2]; - default: - return forms[3]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'dsb', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } + switch ( form ) { + case 'instrumental': // instrumental + word = 'z ' + word; + break; + case 'lokatiw': // lokatiw + word = 'wo ' + word; + break; + } + return word; }; diff --git a/resources/mediawiki.language/languages/fi.js b/resources/mediawiki.language/languages/fi.js new file mode 100644 index 00000000..374698dc --- /dev/null +++ b/resources/mediawiki.language/languages/fi.js @@ -0,0 +1,44 @@ +/** + * Finnish (Suomi) language functions + * @author Santhosh Thottingal + */ + +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'fi', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; + } + + // vowel harmony flag + var aou = word.match( /[aou][^äöy]*$/i ); + var origWord = word; + if ( word.match( /wiki$/i ) ) { + aou = false; + } + //append i after final consonant + if ( word.match( /[bcdfghjklmnpqrstvwxz]$/i ) ) + word += 'i'; + + switch ( form ) { + case 'genitive': + word += 'n'; + break; + case 'elative': + word += ( aou ? 'sta' : 'stä' ); + break; + case 'partitive': + word += ( aou ? 'a' : 'ä' ); + break; + case 'illative': + // Double the last letter and add 'n' + word += word.substr( word.length-1 ) + 'n'; + break; + case 'inessive': + word += ( aou ? 'ssa' : 'ssä' ); + break; + default: + word = origWord; + break; + } + return word; +}; diff --git a/resources/mediawiki.language/languages/fr.js b/resources/mediawiki.language/languages/fr.js deleted file mode 100644 index bfb52d28..00000000 --- a/resources/mediawiki.language/languages/fr.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * French (Français) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/ga.js b/resources/mediawiki.language/languages/ga.js index 39280138..a27b489d 100644 --- a/resources/mediawiki.language/languages/ga.js +++ b/resources/mediawiki.language/languages/ga.js @@ -2,13 +2,36 @@ * Irish (Gaeilge) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'ga', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - if ( count == 2 ) { - return forms[1]; + switch ( form ) { + case 'ainmlae': + switch ( word ) { + case 'an Domhnach': + word = 'Dé Domhnaigh'; + break; + case 'an Luan': + word = 'Dé Luain'; + break; + case 'an Mháirt': + word = 'Dé Mháirt'; + break; + case 'an Chéadaoin': + word = 'Dé Chéadaoin'; + break; + case 'an Déardaoin': + word = 'Déardaoin'; + break; + case 'an Aoine': + word = 'Dé hAoine'; + break; + case 'an Satharn': + word = 'Dé Sathairn'; + break; + } } - return forms[2]; + return word; }; diff --git a/resources/mediawiki.language/languages/gd.js b/resources/mediawiki.language/languages/gd.js deleted file mode 100644 index 5ac20077..00000000 --- a/resources/mediawiki.language/languages/gd.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Scots Gaelic (Gàidhlig) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - count = Math.abs( count ); - if ( count === 1 ) { - return forms[0]; - } - if ( count === 2 ) { - return forms[1]; - } - if ( count >= 3 && count <= 10 ) { - return forms[2]; - } - return forms[3]; -}; diff --git a/resources/mediawiki.language/languages/gv.js b/resources/mediawiki.language/languages/gv.js deleted file mode 100644 index afc2b5d4..00000000 --- a/resources/mediawiki.language/languages/gv.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Manx (Gaelg) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - if ( count > 0 && ( count % 20 ) === 0 ) { - return forms[0]; - } - switch ( count % 10 ) { - case 1: - return forms[1]; - case 2: - return forms[2]; - default: - return forms[3]; - } -}; diff --git a/resources/mediawiki.language/languages/he.js b/resources/mediawiki.language/languages/he.js index b74440e4..d35f77ed 100644 --- a/resources/mediawiki.language/languages/he.js +++ b/resources/mediawiki.language/languages/he.js @@ -2,13 +2,28 @@ * Hebrew (עברית) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'he', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - if ( count == 2 && forms[2] ) { - return forms[2]; + switch ( form ) { + case 'prefixed': + case 'תחילית': // the same word in Hebrew + // Duplicate prefixed "Waw", but only if it's not already double + if ( word.substr( 0, 1 ) === "ו" && word.substr( 0, 2 ) !== "וו" ) { + word = "ו" + word; + } + + // Remove the "He" if prefixed + if ( word.substr( 0, 1 ) === "ה" ) { + word = word.substr( 1, word.length ); + } + + // Add a hyphen (maqaf) before numbers and non-Hebrew letters + if ( word.substr( 0, 1 ) < "א" || word.substr( 0, 1 ) > "ת" ) { + word = "־" + word; + } } - return forms[1]; + return word; }; diff --git a/resources/mediawiki.language/languages/hi.js b/resources/mediawiki.language/languages/hi.js deleted file mode 100644 index a22a0e17..00000000 --- a/resources/mediawiki.language/languages/hi.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Hindi (हिन्दी) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/hr.js b/resources/mediawiki.language/languages/hr.js deleted file mode 100644 index e3ce291b..00000000 --- a/resources/mediawiki.language/languages/hr.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Croatian (hrvatski) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/hsb.js b/resources/mediawiki.language/languages/hsb.js index 8651fe41..211d67b5 100644 --- a/resources/mediawiki.language/languages/hsb.js +++ b/resources/mediawiki.language/languages/hsb.js @@ -2,17 +2,18 @@ * Upper Sorbian (Hornjoserbsce) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - switch ( Math.abs( count ) % 100 ) { - case 1: - return forms[0]; - case 2: - return forms[1]; - case 3: - case 4: - return forms[2]; - default: - return forms[3]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms =mw.language.getData( 'hsb', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } + switch ( form ) { + case 'instrumental': // instrumental + word = 'z ' + word; + break; + case 'lokatiw': // lokatiw + word = 'wo ' + word; + break; + } + return word; }; diff --git a/resources/mediawiki.language/languages/hu.js b/resources/mediawiki.language/languages/hu.js new file mode 100644 index 00000000..eb3f1f3a --- /dev/null +++ b/resources/mediawiki.language/languages/hu.js @@ -0,0 +1,23 @@ +/** + * Hungarian language functions + * @author Santhosh Thottingal + */ + +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'hu', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; + } + switch ( form ) { + case 'rol': + word += 'ról'; + break; + case 'ba': + word += 'ba'; + break; + case 'k': + word += 'k'; + break; + } + return word; +}; diff --git a/resources/mediawiki.language/languages/hy.js b/resources/mediawiki.language/languages/hy.js index 734c26df..215e7504 100644 --- a/resources/mediawiki.language/languages/hy.js +++ b/resources/mediawiki.language/languages/hy.js @@ -2,7 +2,26 @@ * Armenian (Հայերեն) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( Math.abs( count ) <= 1 ) ? forms[0] : forms[1]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'hy', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; + } + + // These rules are not perfect, but they are currently only used for site names so it doesn't + // matter if they are wrong sometimes. Just add a special case for your site name if necessary. + + switch ( form ) { + case 'genitive': // սեռական հոլով + if ( word.substr( -1 ) === 'ա' ) + word = word.substr( 0, word.length -1 ) + 'այի'; + else if ( word.substr( -1 ) === 'ո' ) + word = word.substr( 0, word.length - 1 ) + 'ոյի'; + else if ( word.substr( -4 ) === 'գիրք' ) + word = word.substr( 0, word.length - 4 ) + 'գրքի'; + else + word = word + 'ի'; + break; + } + return word; }; diff --git a/resources/mediawiki.language/languages/ksh.js b/resources/mediawiki.language/languages/ksh.js deleted file mode 100644 index ed907325..00000000 --- a/resources/mediawiki.language/languages/ksh.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Ripuarian (Ripoarƒósh) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 0 ) { - return forms[2]; - } - return forms[1]; -}; diff --git a/resources/mediawiki.language/languages/la.js b/resources/mediawiki.language/languages/la.js new file mode 100644 index 00000000..313bb1c2 --- /dev/null +++ b/resources/mediawiki.language/languages/la.js @@ -0,0 +1,50 @@ +/** + * Latin (lingua Latina) language functions + * @author Santhosh Thottingal + */ + +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'la', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; + } + switch ( form ) { + case 'genitive': + // only a few declensions, and even for those mostly the singular only + word = word.replace( /u[ms]$/i, 'i' ); // 2nd declension singular + word = word.replace( /ommunia$/i, 'ommunium' ); // 3rd declension neuter plural (partly) + word = word.replace( /a$/i, 'ae' ); // 1st declension singular + word = word.replace( /libri$/i,'librorum' ); // 2nd declension plural (partly) + word = word.replace( /nuntii$/i, 'nuntiorum' ); // 2nd declension plural (partly) + word = word.replace( /tio$/i,'tionis' ); // 3rd declension singular (partly) + word = word.replace( /ns$/i, 'ntis' ); + word = word.replace( /as$/i, 'atis' ); + word = word.replace( /es$/i ,'ei' ); // 5th declension singular + break; + case 'accusative': + // only a few declensions, and even for those mostly the singular only + word = word.replace( /u[ms]$/i, 'um' ); // 2nd declension singular + word = word.replace( /ommunia$/i, 'am' ); // 3rd declension neuter plural (partly) + word = word.replace( /a$/i, 'ommunia' ); // 1st declension singular + word = word.replace( /libri$/i,'libros' ); // 2nd declension plural (partly) + word = word.replace( /nuntii$/i, 'nuntios' );// 2nd declension plural (partly) + word = word.replace( /tio$/i,'tionem' ); // 3rd declension singular (partly) + word = word.replace( /ns$/i, 'ntem' ); + word = word.replace( /as$/i, 'atem'); + word = word.replace( /es$/i ,'em' ); // 5th declension singular + break; + case 'ablative': + // only a few declensions, and even for those mostly the singular only + word = word.replace( /u[ms]$/i, 'o' ); // 2nd declension singular + word = word.replace( /ommunia$/i, 'ommunibus' ); // 3rd declension neuter plural (partly) + word = word.replace( /a$/i, 'a' ); // 1st declension singular + word = word.replace( /libri$/i,'libris' ); // 2nd declension plural (partly) + word = word.replace( /nuntii$/i, 'nuntiis' ); // 2nd declension plural (partly) + word = word.replace( /tio$/i,'tione' ); // 3rd declension singular (partly) + word = word.replace( /ns$/i, 'nte' ); + word = word.replace( /as$/i, 'ate'); + word = word.replace( /es$/i ,'e' ); // 5th declension singular + break; + } + return word; +}; diff --git a/resources/mediawiki.language/languages/ln.js b/resources/mediawiki.language/languages/ln.js deleted file mode 100644 index 9b9e456a..00000000 --- a/resources/mediawiki.language/languages/ln.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Lingala (Lingála) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/lt.js b/resources/mediawiki.language/languages/lt.js deleted file mode 100644 index 04964aff..00000000 --- a/resources/mediawiki.language/languages/lt.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Lithuanian (Lietuvių) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - // if the number is not mentioned in message, then use $form[0] for singular and $form[1] for plural or zero - if ( forms.length == 2 ) { - return count == 1 ? forms[0] : forms[1]; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count % 10 == 1 && count % 100 != 11 ) { - return forms[0]; - } - if ( count % 10 >= 2 && ( count % 100 < 10 || count % 100 >= 20 ) ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/lv.js b/resources/mediawiki.language/languages/lv.js deleted file mode 100644 index d4bec44a..00000000 --- a/resources/mediawiki.language/languages/lv.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Latvian (Latviešu) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( ( count % 10 == 1 ) && ( count % 100 != 11 ) ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/mg.js b/resources/mediawiki.language/languages/mg.js deleted file mode 100644 index b65e96e3..00000000 --- a/resources/mediawiki.language/languages/mg.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Malagasy (Malagasy) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/mk.js b/resources/mediawiki.language/languages/mk.js deleted file mode 100644 index c89bbac5..00000000 --- a/resources/mediawiki.language/languages/mk.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Macedonian (Македонски) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( ( count % 10 === 1 ) && ( count % 100 !== 11 ) ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/mo.js b/resources/mediawiki.language/languages/mo.js deleted file mode 100644 index 0c4b696a..00000000 --- a/resources/mediawiki.language/languages/mo.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Moldavian (Молдовеняскэ) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 0 || count % 100 < 20 ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/mt.js b/resources/mediawiki.language/languages/mt.js deleted file mode 100644 index 44c7bfff..00000000 --- a/resources/mediawiki.language/languages/mt.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Maltese (Malti) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 4 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 0 || ( count % 100 > 1 && count % 100 < 11 ) ) { - return forms[1]; - } - if ( count % 100 > 10 && count % 100 < 20 ) { - return forms[2]; - } - return forms[3]; -}; diff --git a/resources/mediawiki.language/languages/nl.js b/resources/mediawiki.language/languages/nl.js deleted file mode 100644 index a2b22f4b..00000000 --- a/resources/mediawiki.language/languages/nl.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Dutch (Nederlands) language functions - */ - -mediaWiki.language.digitTransformTable = { - '.' : ',', - ',' : '.' -}; diff --git a/resources/mediawiki.language/languages/nso.js b/resources/mediawiki.language/languages/nso.js deleted file mode 100644 index 30cf1892..00000000 --- a/resources/mediawiki.language/languages/nso.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Northern Sotho (Sesotho sa Leboa) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/os.js b/resources/mediawiki.language/languages/os.js new file mode 100644 index 00000000..431e38c8 --- /dev/null +++ b/resources/mediawiki.language/languages/os.js @@ -0,0 +1,69 @@ +/** + * Ossetian (Ирон) language functions + * @author Santhosh Thottingal + */ + + +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'os', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; + } + // Ending for allative case + var end_allative = 'мæ'; + // Variable for 'j' beetwen vowels + var jot = ''; + // Variable for "-" for not Ossetic words + var hyphen = ''; + // Variable for ending + var ending = ''; + // Checking if the $word is in plural form + if ( word.match( /тæ$/i ) ) { + word = word.substring( 0, word.length - 1 ); + end_allative = 'æм'; + } + // Works if word is in singular form. + // Checking if word ends on one of the vowels: е, ё, и, о, ы, э, ю, я. + else if ( word.match( /[аæеёиоыэюя]$/i ) ) { + jot = 'й'; + } + // Checking if word ends on 'у'. 'У' can be either consonant 'W' or vowel 'U' in cyrillic Ossetic. + // Examples: {{grammar:genitive|аунеу}} = аунеуы, {{grammar:genitive|лæппу}} = лæппуйы. + else if ( word.match( /у$/i ) ) { + if ( ! word.substring( word.length-2, word.length-1 ).match( /[аæеёиоыэюя]$/i ) ) { + jot = 'й'; + } + } else if ( !word.match( /[бвгджзйклмнопрстфхцчшщьъ]$/i ) ) { + hyphen = '-'; + } + + switch ( form ) { + case 'genitive': + ending = hyphen + jot + 'ы'; + break; + case 'dative': + ending = hyphen + jot + 'æн'; + break; + case 'allative': + ending = hyphen + end_allative; + break; + case 'ablative': + if ( jot == 'й' ) { + ending = hyphen + jot + 'æ'; + } + else { + ending = hyphen + jot + 'æй'; + } + break; + case 'superessive': + ending = hyphen + jot + 'ыл'; + break; + case 'equative': + ending = hyphen + jot + 'ау'; + break; + case 'comitative': + ending = hyphen + 'имæ'; + break; + } + return word + ending; +}; diff --git a/resources/mediawiki.language/languages/pl.js b/resources/mediawiki.language/languages/pl.js deleted file mode 100644 index 03e0d842..00000000 --- a/resources/mediawiki.language/languages/pl.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Polish (polski) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - count = Math.abs( count ); - if ( count == 1 ) { - return forms[0]; - } - switch ( count % 10 ) { - case 2: - case 3: - case 4: - if ( count / 10 % 10 != 1 ) { - return forms[1]; - } - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/pt-br.js b/resources/mediawiki.language/languages/pt-br.js deleted file mode 100644 index 2457e247..00000000 --- a/resources/mediawiki.language/languages/pt-br.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Brazilian Portugese (Portuguêsi do Brasil) language functions - */ - -mediaWiki.language.digitTransformTable = { - '.' : ',', - ',' : ' ' -}; diff --git a/resources/mediawiki.language/languages/pt.js b/resources/mediawiki.language/languages/pt.js deleted file mode 100644 index 1b8fc72f..00000000 --- a/resources/mediawiki.language/languages/pt.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Portugese language functions - */ - -mediaWiki.language.digitTransformTable = { - '.' : ',', - ',' : ' ' -}; diff --git a/resources/mediawiki.language/languages/ro.js b/resources/mediawiki.language/languages/ro.js deleted file mode 100644 index 42610fe6..00000000 --- a/resources/mediawiki.language/languages/ro.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Romanian (Română) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 0 || count % 100 < 20 ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/ru.js b/resources/mediawiki.language/languages/ru.js index 17c92931..cfdbfc3b 100644 --- a/resources/mediawiki.language/languages/ru.js +++ b/resources/mediawiki.language/languages/ru.js @@ -2,22 +2,28 @@ * Russian (Русский) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - if ( forms.length === 2 ) { - return count == 1 ? forms[0] : forms[1]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'ru', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; + switch ( form ) { + case 'genitive': // родительный падеж + if ( ( word.substr( word.length - 4 ) == 'вики' ) || ( word.substr( word.length - 4 ) == 'Вики' ) ) { + } + else if ( word.substr( word.length - 1 ) == 'ь' ) + word = word.substr(0, word.length - 1 ) + 'я'; + else if ( word.substr( word.length - 2 ) == 'ия' ) + word = word.substr(0, word.length - 2 ) + 'ии'; + else if ( word.substr( word.length - 2 ) == 'ка' ) + word = word.substr(0, word.length - 2 ) + 'ки'; + else if ( word.substr( word.length - 2 ) == 'ти' ) + word = word.substr(0, word.length - 2 ) + 'тей'; + else if ( word.substr( word.length - 2 ) == 'ды' ) + word = word.substr(0, word.length - 2 ) + 'дов'; + else if ( word.substr( word.length - 3 ) == 'ник' ) + word = word.substr(0, word.length - 3 ) + 'ника'; + break; } + return word; }; diff --git a/resources/mediawiki.language/languages/se.js b/resources/mediawiki.language/languages/se.js deleted file mode 100644 index e1ae5b9a..00000000 --- a/resources/mediawiki.language/languages/se.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Northern Sami (Sámegiella) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - if ( !forms || forms.length === 0 ) { - return ''; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 2 ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/sh.js b/resources/mediawiki.language/languages/sh.js deleted file mode 100644 index 719b0c57..00000000 --- a/resources/mediawiki.language/languages/sh.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Serbo-Croatian (Srpskohrvatski / Српскохрватски) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - if ( forms.length === 2 ) { - return count == 1 ? forms[0] : forms[1]; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/sk.js b/resources/mediawiki.language/languages/sk.js deleted file mode 100644 index 21a22b21..00000000 --- a/resources/mediawiki.language/languages/sk.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Slovak (Slovenƒçina) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 2 || count == 3 || count == 4 ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/sl.js b/resources/mediawiki.language/languages/sl.js index a887e0e0..acd00bfd 100644 --- a/resources/mediawiki.language/languages/sl.js +++ b/resources/mediawiki.language/languages/sl.js @@ -2,19 +2,18 @@ * Slovenian (Slovenščina) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 5 ); - if ( count % 100 == 1 ) { - return forms[0]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'sl', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - if ( count % 100 == 2 ) { - return forms[1]; + switch ( form ) { + case 'mestnik': // locative + word = 'o ' + word; + break; + case 'orodnik': // instrumental + word = 'z ' + word; + break; } - if ( count % 100 == 3 || count % 100 == 4 ) { - return forms[2]; - } - if ( count != 0 ) { - return forms[3]; - } - return forms[4]; + return word; }; diff --git a/resources/mediawiki.language/languages/sma.js b/resources/mediawiki.language/languages/sma.js deleted file mode 100644 index 8163c42c..00000000 --- a/resources/mediawiki.language/languages/sma.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Southern Sami (Åarjelsaemien) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - if ( !forms || forms.length === 0 ) { - return ''; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count == 1 ) { - return forms[0]; - } - if ( count == 2 ) { - return forms[1]; - } - return forms[2]; -}; diff --git a/resources/mediawiki.language/languages/sr-ec.js b/resources/mediawiki.language/languages/sr-ec.js deleted file mode 100644 index af48eb07..00000000 --- a/resources/mediawiki.language/languages/sr-ec.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Serbian (cyrillic script) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/sr-el.js b/resources/mediawiki.language/languages/sr-el.js deleted file mode 100644 index fd6ca012..00000000 --- a/resources/mediawiki.language/languages/sr-el.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Serbian (latin script) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/sr.js b/resources/mediawiki.language/languages/sr.js deleted file mode 100644 index 25cdc8a2..00000000 --- a/resources/mediawiki.language/languages/sr.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Serbian (Српски / Srpski) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - if ( forms.length === 2 ) { - return ( count == 1 ) ? forms[0] : forms[1]; - } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; - } -}; diff --git a/resources/mediawiki.language/languages/ti.js b/resources/mediawiki.language/languages/ti.js deleted file mode 100644 index 69b6d9b8..00000000 --- a/resources/mediawiki.language/languages/ti.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Tigrinya (ትግርኛ) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural(forms, 2); - return (count <= 1) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/tl.js b/resources/mediawiki.language/languages/tl.js deleted file mode 100644 index 29219fdb..00000000 --- a/resources/mediawiki.language/languages/tl.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Tagalog (Tagalog) language functions - */ -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/languages/uk.js b/resources/mediawiki.language/languages/uk.js index 5c1294e7..ee110b06 100644 --- a/resources/mediawiki.language/languages/uk.js +++ b/resources/mediawiki.language/languages/uk.js @@ -2,22 +2,34 @@ * Ukrainian (Українська) language functions */ -mediaWiki.language.convertPlural = function( count, forms ) { - if ( forms.length === 2 ) { - return count == 1 ? forms[0] : forms[1]; +mediaWiki.language.convertGrammar = function( word, form ) { + var grammarForms = mw.language.getData( 'uk', 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] ; } - forms = mediaWiki.language.preConvertPlural( forms, 3 ); - if ( count > 10 && Math.floor( ( count % 100 ) / 10 ) == 1 ) { - return forms[2]; - } - switch ( count % 10 ) { - case 1: - return forms[0]; - case 2: - case 3: - case 4: - return forms[1]; - default: - return forms[2]; + switch ( form ) { + case 'genitive': // родовий відмінок + if ( ( word.substr( word.length - 4 ) == 'вікі' ) || ( word.substr( word.length - 4 ) == 'Вікі' ) ) { + } + else if ( word.substr( word.length - 1 ) == 'ь' ) + word = word.substr(0, word.length - 1 ) + 'я'; + else if ( word.substr( word.length - 2 ) == 'ія' ) + word = word.substr(0, word.length - 2 ) + 'ії'; + else if ( word.substr( word.length - 2 ) == 'ка' ) + word = word.substr(0, word.length - 2 ) + 'ки'; + else if ( word.substr( word.length - 2 ) == 'ти' ) + word = word.substr(0, word.length - 2 ) + 'тей'; + else if ( word.substr( word.length - 2 ) == 'ды' ) + word = word.substr(0, word.length - 2 ) + 'дов'; + else if ( word.substr( word.length - 3 ) == 'ник' ) + word = word.substr(0, word.length - 3 ) + 'ника'; + break; + case 'accusative': // знахідний відмінок + if ( ( word.substr( word.length - 4 ) == 'вікі' ) || ( word.substr( word.length - 4 ) == 'Вікі' ) ) { + } + else if ( word.substr( word.length - 2 ) == 'ія' ) + word = word.substr(0, word.length - 2 ) + 'ію'; + break; } + return word; }; diff --git a/resources/mediawiki.language/languages/wa.js b/resources/mediawiki.language/languages/wa.js deleted file mode 100644 index e1773759..00000000 --- a/resources/mediawiki.language/languages/wa.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Walloon (Walon) language functions - */ - -mediaWiki.language.convertPlural = function( count, forms ) { - forms = mediaWiki.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; -}; diff --git a/resources/mediawiki.language/mediawiki.cldr.js b/resources/mediawiki.language/mediawiki.cldr.js new file mode 100644 index 00000000..6660eca4 --- /dev/null +++ b/resources/mediawiki.language/mediawiki.cldr.js @@ -0,0 +1,28 @@ +/** + * CLDR related utility methods + */ +( function( mw ) { + "use strict"; + + var cldr = { + /** + * For the number, get the plural for index + * In case none of the rules passed, we return pluralRules.length + * That means it is the "other" form. + * @param number + * @param pluralRules + * @return plural form index + */ + getPluralForm: function( number, pluralRules ) { + var pluralFormIndex = 0; + for ( pluralFormIndex = 0; pluralFormIndex < pluralRules.length; pluralFormIndex++ ) { + if ( mw.libs.pluralRuleParser( pluralRules[pluralFormIndex], number ) ) { + break; + } + } + return pluralFormIndex; + } + }; + + mw.cldr = cldr; +} )( mediaWiki ); diff --git a/resources/mediawiki.language/mediawiki.language.init.js b/resources/mediawiki.language/mediawiki.language.init.js new file mode 100644 index 00000000..30307a37 --- /dev/null +++ b/resources/mediawiki.language/mediawiki.language.init.js @@ -0,0 +1,61 @@ +/** + * Base language object with methods for storing and getting + * language data. + */ +( function ( mw, $ ) { + + var language = { + /** + * @var data {Object} Language related data (keyed by language, + * contains instances of mw.Map). + * @example Set data + * <code> + * // Override, extend or create the language data object of 'nl' + * mw.language.setData( 'nl', 'myKey', 'My value' ); + * + * // Set multiple values at once + * mw.language.setData( 'nl', { 'foo': 'X', 'bar': 'Y' } ); + * </code> + * @example Get GrammarForms data for language 'nl': + * <code> + * var grammarForms = mw.language.getData( 'nl', 'grammarForms' ); + * </code> + */ + data: {}, + + /** + * Convenience method for retreiving language data by language code and data key, + * covering for the potential inexistance of a data object for this langiage. + * @param langCode {String} + * @param dataKey {String} + * @return {mixed} Value stored in the mw.Map (or undefined if there is no map for + the specified langCode). + */ + getData: function ( langCode, dataKey ) { + var langData = language.data; + if ( langData && langData[langCode] instanceof mw.Map ) { + return langData[langCode].get( dataKey ); + } + return undefined; + }, + + /** + * Convenience method for setting language data by language code and data key. + * Creates the data mw.Map if there isn't one for the specified language already. + * + * @param langCode {String} + * @param dataKey {String|Object} Key or object of key/values. + * @param value {mixed} Value for dataKey, ignored if dataKey is an object. + */ + setData: function ( langCode, dataKey, value ) { + var langData = language.data; + if ( !( langData[langCode] instanceof mw.Map ) ) { + langData[langCode] = new mw.Map(); + } + langData[langCode].set( dataKey, value ); + } + }; + + mw.language = language; + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.language/mediawiki.language.js b/resources/mediawiki.language/mediawiki.language.js index 4abfa4ba..935d4ff6 100644 --- a/resources/mediawiki.language/mediawiki.language.js +++ b/resources/mediawiki.language/mediawiki.language.js @@ -1,25 +1,24 @@ /** - * Base language object - * * Localized Language support attempts to mirror some of the functionality of - * Language.php in MediaWiki. This object contains methods for loading and - * transforming message text. + * Language.php in MediaWiki. + * This adds methods for transforming message text. */ -( function( $, mw ) { +( function ( mw, $ ) { + +var language = { -mw.language = { /** * Process the PLURAL template substitution * * @param {object} template Template object * @format template - * { - * 'title': [title of template], - * 'parameters': [template parameters] - * } + * { + * 'title': [title of template], + * 'parameters': [template parameters] + * } * @example {{Template:title|params}} */ - 'procPLURAL': function( template ) { + procPLURAL: function ( template ) { if ( template.title && template.parameters && mw.language.convertPlural ) { // Check if we have forms to replace if ( template.parameters.length === 0 ) { @@ -27,7 +26,7 @@ mw.language = { } // Restore the count into a Number ( if it got converted earlier ) var count = mw.language.convertNumber( template.title, true ); - // Do convertPlural call + // Do convertPlural call return mw.language.convertPlural( parseInt( count, 10 ), template.parameters ); } // Could not process plural return first form or nothing @@ -36,6 +35,7 @@ mw.language = { } return ''; }, + /** * Plural form transformations, needed for some languages. * @@ -43,12 +43,21 @@ mw.language = { * @param forms array List of plural forms * @return string Correct form for quantifier in this language */ - 'convertPlural': function( count, forms ){ + convertPlural: function( count, forms ) { + var pluralFormIndex = 0; if ( !forms || forms.length === 0 ) { return ''; } - return ( parseInt( count, 10 ) == 1 ) ? forms[0] : forms[1]; + var pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); + if ( !pluralRules ) { + // default fallback. + return ( count === 1 ) ? forms[0] : forms[1]; + } + pluralFormIndex = mw.cldr.getPluralForm( count, pluralRules ); + pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 ); + return forms[pluralFormIndex]; }, + /** * Pads an array to a specific length by copying the last one element. * @@ -56,38 +65,41 @@ mw.language = { * @param count integer Number of forms required * @return array Padded array of forms */ - 'preConvertPlural': function( forms, count ) { + preConvertPlural: function ( forms, count ) { while ( forms.length < count ) { forms.push( forms[ forms.length-1 ] ); } return forms; }, + /** * Converts a number using digitTransformTable. * * @param {num} number Value to be converted * @param {boolean} integer Convert the return value to an integer */ - 'convertNumber': function( num, integer ) { + convertNumber: function( num, integer ) { + var i, tmp, transformTable; + if ( !mw.language.digitTransformTable ) { return num; } // Set the target Transform table: - var transformTable = mw.language.digitTransformTable; + transformTable = mw.language.digitTransformTable; // Check if the "restore" to Latin number flag is set: if ( integer ) { - if ( parseInt( num, 10 ) == num ) { + if ( parseInt( num, 10 ) === num ) { return num; } - var tmp = []; - for ( var i in transformTable ) { + tmp = []; + for ( i in transformTable ) { tmp[ transformTable[ i ] ] = i; } transformTable = tmp; } - var numberString = '' + num; + var numberString = '' + num; var convertedNumber = ''; - for ( var i = 0; i < numberString.length; i++ ) { + for ( i = 0; i < numberString.length; i++ ) { if ( transformTable[ numberString[i] ] ) { convertedNumber += transformTable[numberString[i]]; } else { @@ -96,11 +108,12 @@ mw.language = { } return integer ? parseInt( convertedNumber, 10 ) : convertedNumber; }, + /** * Provides an alternative text depending on specified gender. * Usage {{gender:[gender|user object]|masculine|feminine|neutral}}. * If second or third parameter are not specified, masculine is used. - * + * * These details may be overriden per language. * * @param gender string male, female, or anything else for neutral. @@ -108,7 +121,7 @@ mw.language = { * * @return string */ - 'gender': function( gender, forms ) { + gender: function( gender, forms ) { if ( !forms || forms.length === 0 ) { return ''; } @@ -122,7 +135,28 @@ mw.language = { return ( forms.length === 3 ) ? forms[2] : forms[0]; }, + /** + * Grammatical transformations, needed for inflected languages. + * Invoked by putting {{grammar:form|word}} in a message. + * The rules can be defined in $wgGrammarForms global or grammar + * forms can be computed dynamically by overriding this method per language + * + * @param word {String} + * @param form {String} + * @return {String} + */ + convertGrammar: function ( word, form ) { + var grammarForms = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'grammarForms' ); + if ( grammarForms && grammarForms[form] ) { + return grammarForms[form][word] || word; + } + return word; + }, + // Digit Transform Table, populated by language classes where applicable - 'digitTransformTable': null + digitTransformTable: mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'digitTransformTable' ) }; -} )( jQuery, mediaWiki ); + +$.extend( mw.language, language ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.libs/CLDRPluralRuleParser.js b/resources/mediawiki.libs/CLDRPluralRuleParser.js new file mode 100644 index 00000000..91bdc07d --- /dev/null +++ b/resources/mediawiki.libs/CLDRPluralRuleParser.js @@ -0,0 +1,314 @@ +/* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */ + +/** +* cldrpluralparser.js +* A parser engine for CLDR plural rules. +* +* Copyright 2012 GPLV3+, Santhosh Thottingal +* +* @version 0.1.0-alpha +* @source https://github.com/santhoshtr/CLDRPluralRuleParser +* @author Santhosh Thottingal <santhosh.thottingal@gmail.com> +* @author Timo Tijhof +* @author Amir Aharoni +*/ + +/** + * Evaluates a plural rule in CLDR syntax for a number + * @param rule + * @param number + * @return true|false|null + */ +( function( mw ) { + +function pluralRuleParser(rule, number) { + /* + Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules + ----------------------------------------------------------------- + + condition = and_condition ('or' and_condition)* + and_condition = relation ('and' relation)* + relation = is_relation | in_relation | within_relation | 'n' <EOL> + is_relation = expr 'is' ('not')? value + in_relation = expr ('not')? 'in' range_list + within_relation = expr ('not')? 'within' range_list + expr = 'n' ('mod' value)? + range_list = (range | value) (',' range_list)* + value = digit+ + digit = 0|1|2|3|4|5|6|7|8|9 + range = value'..'value + + */ + // Indicates current position in the rule as we parse through it. + // Shared among all parsing functions below. + var pos = 0; + + var whitespace = makeRegexParser(/^\s+/); + var digits = makeRegexParser(/^\d+/); + + var _n_ = makeStringParser('n'); + var _is_ = makeStringParser('is'); + var _mod_ = makeStringParser('mod'); + var _not_ = makeStringParser('not'); + var _in_ = makeStringParser('in'); + var _within_ = makeStringParser('within'); + var _range_ = makeStringParser('..'); + var _comma_ = makeStringParser(','); + var _or_ = makeStringParser('or'); + var _and_ = makeStringParser('and'); + + function debug() { + /* console.log.apply(console, arguments);*/ + } + + debug('pluralRuleParser', rule, number); + + // Try parsers until one works, if none work return null + function choice(parserSyntax) { + return function () { + for (var i = 0; i < parserSyntax.length; i++) { + var result = parserSyntax[i](); + if (result !== null) { + return result; + } + } + return null; + }; + } + + // Try several parserSyntax-es in a row. + // All must succeed; otherwise, return null. + // This is the only eager one. + function sequence(parserSyntax) { + var originalPos = pos; + var result = []; + for (var i = 0; i < parserSyntax.length; i++) { + var res = parserSyntax[i](); + if (res === null) { + pos = originalPos; + return null; + } + result.push(res); + } + return result; + } + + // Run the same parser over and over until it fails. + // Must succeed a minimum of n times; otherwise, return null. + function nOrMore(n, p) { + return function () { + var originalPos = pos; + var result = []; + var parsed = p(); + while (parsed !== null) { + result.push(parsed); + parsed = p(); + } + if (result.length < n) { + pos = originalPos; + return null; + } + return result; + }; + } + + // Helpers -- just make parserSyntax out of simpler JS builtin types + + function makeStringParser(s) { + var len = s.length; + return function () { + var result = null; + if (rule.substr(pos, len) === s) { + result = s; + pos += len; + } + return result; + }; + } + + function makeRegexParser(regex) { + return function () { + var matches = rule.substr(pos).match(regex); + if (matches === null) { + return null; + } + pos += matches[0].length; + return matches[0]; + }; + } + + function n() { + var result = _n_(); + if (result === null) { + debug(" -- failed n"); + return result; + } + result = parseInt(number, 10); + debug(" -- passed n ", result); + return result; + } + + var expression = choice([mod, n]); + + function mod() { + var result = sequence([n, whitespace, _mod_, whitespace, digits]); + if (result === null) { + debug(" -- failed mod"); + return null; + } + debug(" -- passed mod"); + return parseInt(result[0], 10) % parseInt(result[4], 10); + } + + function not() { + var result = sequence([whitespace, _not_]); + if (result === null) { + debug(" -- failed not"); + return null; + } else { + return result[1]; + } + } + + function is() { + var result = sequence([expression, whitespace, _is_, nOrMore(0, not), whitespace, digits]); + if (result !== null) { + debug(" -- passed is"); + if (result[3][0] === 'not') { + return result[0] !== parseInt(result[5], 10); + } else { + return result[0] === parseInt(result[5], 10); + } + } + debug(" -- failed is"); + return null; + } + + function rangeList() { + // range_list = (range | value) (',' range_list)* + var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]); + var resultList = []; + if (result !== null) { + resultList = resultList.concat(result[0], result[1][0]); + return resultList; + } + debug(" -- failed rangeList"); + return null; + } + + function rangeTail() { + // ',' range_list + var result = sequence([_comma_, rangeList]); + if (result !== null) { + return result[1]; + } + debug(" -- failed rangeTail"); + return null; + } + + function range() { + var result = sequence([digits, _range_, digits]); + if (result !== null) { + debug(" -- passed range"); + var array = []; + var left = parseInt(result[0], 10); + var right = parseInt(result[2], 10); + for ( i = left; i <= right; i++) { + array.push(i); + } + return array; + } + debug(" -- failed range"); + return null; + } + + function _in() { + // in_relation = expr ('not')? 'in' range_list + var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]); + if (result !== null) { + debug(" -- passed _in"); + var range_list = result[5]; + for (var i = 0; i < range_list.length; i++) { + if (parseInt(range_list[i], 10) === result[0]) { + return (result[1][0] !== 'not'); + } + } + return (result[1][0] === 'not'); + } + debug(" -- failed _in "); + return null; + } + + function within() { + var result = sequence([expression, whitespace, _within_, whitespace, rangeList]); + if (result !== null) { + debug(" -- passed within "); + var range_list = result[4]; + return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10)); + } + debug(" -- failed within "); + return null; + } + + + var relation = choice([is, _in, within]); + + function and() { + var result = sequence([relation, whitespace, _and_, whitespace, condition]); + if (result) { + debug(" -- passed and"); + return result[0] && result[4]; + } + debug(" -- failed and"); + return null; + } + + function or() { + var result = sequence([relation, whitespace, _or_, whitespace, condition]); + if (result) { + debug(" -- passed or"); + return result[0] || result[4]; + } + debug(" -- failed or"); + return null; + } + + var condition = choice([and, or, relation]); + + function isInt(n) { + return parseFloat(n) % 1 === 0; + } + + + function start() { + if (!isInt(number)) { + return false; + } + var result = condition(); + return result; + } + + + var result = start(); + + /* + * For success, the pos must have gotten to the end of the rule + * and returned a non-null. + * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. + */ + if (result === null || pos !== rule.length) { + // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result); + } + + return result; +} + +/* For module loaders, e.g. NodeJS, NPM */ +if (typeof module !== 'undefined' && module.exports) { + module.exports = pluralRuleParser; +} + +/* pluralRuleParser ends here */ +mw.libs.pluralRuleParser = pluralRuleParser; + +} )( mediaWiki ); diff --git a/resources/mediawiki.page/mediawiki.page.startup.js b/resources/mediawiki.page/mediawiki.page.startup.js index a5541ef9..6a11d3e1 100644 --- a/resources/mediawiki.page/mediawiki.page.startup.js +++ b/resources/mediawiki.page/mediawiki.page.startup.js @@ -1,4 +1,4 @@ -( function ( $ ) { +( function ( mw, $ ) { mw.page = {}; @@ -15,4 +15,4 @@ // is defined for them. $( mw.util.init ); -} )( jQuery ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.watch.ajax.js b/resources/mediawiki.page/mediawiki.page.watch.ajax.js new file mode 100644 index 00000000..a7e059c4 --- /dev/null +++ b/resources/mediawiki.page/mediawiki.page.watch.ajax.js @@ -0,0 +1,175 @@ +/** + * Animate watch/unwatch links to use asynchronous API requests to + * watch pages, rather than navigating to a different URI. + */ +( function ( mw, $ ) { + /** + * The name of the page to watch or unwatch. + */ + var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) ); + + /** + * Update the link text, link href attribute and (if applicable) + * "loading" class. + * + * @param $link {jQuery} Anchor tag of (un)watch link. + * @param action {String} One of 'watch', 'unwatch'. + * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'. + */ + function updateWatchLink( $link, action, state ) { + var accesskeyTip, msgKey, $li, otherAction; + + // message keys 'watch', 'watching', 'unwatch' or 'unwatching'. + msgKey = state === 'loading' ? action + 'ing' : action; + otherAction = action === 'watch' ? 'unwatch' : 'watch'; + accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp ); + $li = $link.closest( 'li' ); + /** + * Trigger a 'watchpage' event for this List item. + * Announce the otherAction value as the first param. + * Used to monitor the state of watch link. + * TODO: Revise when system wide hooks are implemented + */ + if( state === undefined ) { + $li.trigger( 'watchpage.mw', otherAction ); + } + + $link + .text( mw.msg( msgKey ) ) + .attr( 'title', mw.msg( 'tooltip-ca-' + action ) + + ( accesskeyTip ? ' ' + accesskeyTip[0] : '' ) + ) + .attr( 'href', mw.util.wikiScript() + '?' + $.param({ + title: title, + action: action + }) + ); + + // Most common ID style + if ( $li.prop( 'id' ) === 'ca-' + otherAction ) { + $li.prop( 'id', 'ca-' + action ); + } + + // Special case for vector icon + if ( $li.hasClass( 'icon' ) ) { + if ( state === 'loading' ) { + $link.addClass( 'loading' ); + } else { + $link.removeClass( 'loading' ); + } + } + } + + /** + * @todo This should be moved somewhere more accessible. + * @param url {String} + * @return {String} The extracted action, defaults to 'view'. + */ + function mwUriGetAction( url ) { + var action, actionPaths, key, i, m, parts; + + actionPaths = mw.config.get( 'wgActionPaths' ); + + // @todo: Does MediaWiki give action path or query param + // precedence ? If the former, move this to the bottom + action = mw.util.getParamValue( 'action', url ); + if ( action !== null ) { + return action; + } + + for ( key in actionPaths ) { + if ( actionPaths.hasOwnProperty( key ) ) { + parts = actionPaths[key].split( '$1' ); + for ( i = 0; i < parts.length; i += 1 ) { + parts[i] = $.escapeRE( parts[i] ); + } + m = new RegExp( parts.join( '(.+)' ) ).exec( url ); + if ( m && m[1] ) { + return key; + } + + } + } + + return 'view'; + } + + // Expose local methods + mw.page.watch = { + 'updateWatchLink': updateWatchLink + }; + + $( document ).ready( function () { + var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + + '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + + '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); + + // Allowing people to add inline animated links is a little scary + $links = $links.filter( ':not( #bodyContent *, #content * )' ); + + $links.click( function ( e ) { + var action, api, $link; + + action = mwUriGetAction( this.href ); + + if ( action !== 'watch' && action !== 'unwatch' ) { + // Could not extract target action from link url, + // let native browsing handle it further + return true; + } + e.preventDefault(); + e.stopPropagation(); + + $link = $( this ); + + updateWatchLink( $link, action, 'loading' ); + + api = new mw.Api(); + api[action]( + title, + // Success + function ( watchResponse ) { + var $li, otherAction; + + otherAction = action === 'watch' ? 'unwatch' : 'watch'; + $li = $link.closest( 'li' ); + + mw.notify( $.parseHTML( watchResponse.message ), { tag: 'watch-self' } ); + + // Set link to opposite + updateWatchLink( $link, otherAction ); + + // Bug 12395 - update the watch checkbox on edit pages when the + // page is watched or unwatched via the tab. + if ( watchResponse.watched !== undefined ) { + $( '#wpWatchthis' ).prop( 'checked', true ); + } else { + $( '#wpWatchthis' ).removeProp( 'checked' ); + } + }, + // Error + function () { + var cleanTitle, msg, link; + + // Reset link to non-loading mode + updateWatchLink( $link, action ); + + // Format error message + cleanTitle = title.replace( /_/g, ' ' ); + link = mw.html.element( + 'a', { + href: mw.util.wikiGetlink( title ), + title: cleanTitle + }, cleanTitle + ); + msg = mw.messsage( 'watcherrortext', link ); + + // Report to user about the error + mw.notify( msg, { tag: 'watch-self' } ); + + } + ); + }); + }); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/images/arrow-collapsed-ltr.png b/resources/mediawiki.special/images/arrow-collapsed-ltr.png Binary files differnew file mode 100644 index 00000000..467a555a --- /dev/null +++ b/resources/mediawiki.special/images/arrow-collapsed-ltr.png diff --git a/resources/mediawiki.special/images/arrow-collapsed-rtl.png b/resources/mediawiki.special/images/arrow-collapsed-rtl.png Binary files differnew file mode 100644 index 00000000..2246254f --- /dev/null +++ b/resources/mediawiki.special/images/arrow-collapsed-rtl.png diff --git a/resources/mediawiki.special/images/arrow-expanded.png b/resources/mediawiki.special/images/arrow-expanded.png Binary files differnew file mode 100644 index 00000000..58a9fc66 --- /dev/null +++ b/resources/mediawiki.special/images/arrow-expanded.png diff --git a/resources/mediawiki.special/mediawiki.special.changeemail.css b/resources/mediawiki.special/mediawiki.special.changeemail.css index 3d53e8db..9461fbdc 100644 --- a/resources/mediawiki.special/mediawiki.special.changeemail.css +++ b/resources/mediawiki.special/mediawiki.special.changeemail.css @@ -5,6 +5,8 @@ border-bottom-right-radius: 0.8em; border-top-right-radius: 0.8em; } + +/** colors also used in mediawiki.special.preferences.css */ #mw-emailaddress-validity.valid { border: 1px solid #80FF80; background-color: #C0FFC0; diff --git a/resources/mediawiki.special/mediawiki.special.changeemail.js b/resources/mediawiki.special/mediawiki.special.changeemail.js index 6b4ed81d..cab0bbd3 100644 --- a/resources/mediawiki.special/mediawiki.special.changeemail.js +++ b/resources/mediawiki.special/mediawiki.special.changeemail.js @@ -1,12 +1,12 @@ /* * JavaScript for Special:ChangeEmail */ -( function( $, mw ) { +( function ( mw, $ ) { /** * Given an email validity status (true, false, null) update the label CSS class */ -var updateMailValidityLabel = function( mail ) { +function updateMailValidityLabel( mail ) { var isValid = mw.util.validateEmail( mail ), $label = $( '#mw-emailaddress-validity' ); @@ -22,19 +22,21 @@ var updateMailValidityLabel = function( mail ) { } else { $label.text( mw.msg( 'email-address-validity-invalid' ) ).addClass( 'invalid' ).removeClass( 'valid' ); } -}; +} -// Lame tip to let user know if its email is valid. See bug 22449 -// Only bind once for 'blur' so that the user can fill it in without errors -// After that look at every keypress for direct feedback if it was invalid onblur -$( '#wpNewEmail' ).one( 'blur', function() { - if ( $( '#mw-emailaddress-validity' ).length === 0 ) { - $(this).after( '<label for="wpNewEmail" id="mw-emailaddress-validity"></label>' ); - } - updateMailValidityLabel( $(this).val() ); - $(this).keyup( function() { +$( document ).ready( function () { + // Lame tip to let user know if its email is valid. See bug 22449 + // Only bind once for 'blur' so that the user can fill it in without errors + // After that look at every keypress for direct feedback if it was invalid onblur + $( '#wpNewEmail' ).one( 'blur', function () { + if ( $( '#mw-emailaddress-validity' ).length === 0 ) { + $(this).after( '<label for="wpNewEmail" id="mw-emailaddress-validity"></label>' ); + } updateMailValidityLabel( $(this).val() ); + $(this).keyup( function () { + updateMailValidityLabel( $(this).val() ); + } ); } ); } ); -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.changeslist.css b/resources/mediawiki.special/mediawiki.special.changeslist.css index 42afbcd7..8a5421e8 100644 --- a/resources/mediawiki.special/mediawiki.special.changeslist.css +++ b/resources/mediawiki.special/mediawiki.special.changeslist.css @@ -31,20 +31,33 @@ table.mw-enhanced-rc td.mw-enhanced-rc-nested { float: none; } -/** - * If JS is disabled, the arrow is still needed - * for spacing, but ideally shouldn't be shown - */ -.mw-enhanced-rc .mw-rc-openarrow { - visibility: hidden; +/* If JS is disabled, the arrow shouldn't be shown */ +.client-nojs .mw-enhancedchanges-arrow.mw-collapsible-toggle { + display: none; } -.mw-enhanced-rc.mw-made-collapsible .mw-rc-openarrow, -.mw-enhanced-rc .mw-rc-closearrow { - visibility: visible; - display: none; +.mw-enhancedchanges-arrow { + display: inline-block; + *display: inline; /* IE7 and below */ + zoom: 1; + width: 15px; + height: 15px; +} + +.mw-enhancedchanges-arrow.mw-enhancedchanges-arrow-space { + background: none; } -.mw-enhanced-rc.mw-made-collapsible .mw-collapsible-toggle-collapsed .mw-rc-openarrow, -.mw-enhanced-rc.mw-made-collapsible .mw-collapsible-toggle-expanded .mw-rc-closearrow { - display: inline; + +.mw-enhancedchanges-arrow.mw-collapsible-toggle-collapsed { + /* @embed */ + background: url(images/arrow-collapsed-ltr.png) no-repeat left center; +} + +.mw-enhancedchanges-arrow.mw-collapsible-toggle-expanded { + /* @embed */ + background: url(images/arrow-expanded.png) no-repeat left center; +} + +.mw-changeslist-line-watched .mw-title { + font-weight: bold; } diff --git a/resources/mediawiki.special/mediawiki.special.javaScriptTest.js b/resources/mediawiki.special/mediawiki.special.javaScriptTest.js index d413f602..808d5fe8 100644 --- a/resources/mediawiki.special/mediawiki.special.javaScriptTest.js +++ b/resources/mediawiki.special/mediawiki.special.javaScriptTest.js @@ -1,33 +1,37 @@ -/* +/** * JavaScript for Special:JavaScriptTest */ -jQuery( document ).ready( function( $ ) { +( function ( mw, $ ) { + $( function () { - // Create useskin dropdown menu and reload onchange to the selected skin - // (only if a framework was found, not on error pages). - $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function() { + // Create useskin dropdown menu and reload onchange to the selected skin + // (only if a framework was found, not on error pages). + $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function () { - var $html = $( '<p><label for="useskin">' - + mw.message( 'javascripttest-pagetext-skins' ).escaped() - + ' ' - + '</label></p>' ), - select = '<select name="useskin" id="useskin">'; + var $html = $( '<p><label for="useskin">' + + mw.message( 'javascripttest-pagetext-skins' ).escaped() + + ' ' + + '</label></p>' ), + select = '<select name="useskin" id="useskin">'; - // Build <select> further - $.each( mw.config.get( 'wgAvailableSkins' ), function( id ) { - select += '<option value="' + id + '"' - + ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' ) - + '>' + mw.message( 'skinname-' + id ).escaped() + '</option>'; - } ); - select += '</select>'; + // Build <select> further + $.each( mw.config.get( 'wgAvailableSkins' ), function ( id ) { + select += '<option value="' + id + '"' + + ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' ) + + '>' + mw.message( 'skinname-' + id ).escaped() + '</option>'; + } ); + select += '</select>'; - // Bind onchange event handler and append to form - $html.append( - $( select ).change( function() { - window.location = QUnit.url( { useskin: $(this).val() } ); - } ) - ); + // Bind onchange event handler and append to form + $html.append( + $( select ).change( function () { + window.location = QUnit.url( { useskin: $(this).val() } ); + } ) + ); - return $html; + return $html; + } ); } ); -} ); + +}( mediaWiki, jQuery ) ); + diff --git a/resources/mediawiki.special/mediawiki.special.preferences.css b/resources/mediawiki.special/mediawiki.special.preferences.css new file mode 100644 index 00000000..161efde3 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.preferences.css @@ -0,0 +1,11 @@ +/** Reuses colors from mediawiki.special.changeemail.css */ +.mw-email-not-authenticated .mw-input, +.mw-email-none .mw-input{ + border: 1px solid #FF8080; + background-color: #FFC0C0; + color: black; +} +/** Authenticated email field has its own class too. Unstyled by default */ +/* +.mw-email-authenticated .mw-input { } +*/ diff --git a/resources/mediawiki.special/mediawiki.special.preferences.js b/resources/mediawiki.special/mediawiki.special.preferences.js index b7200826..47872907 100644 --- a/resources/mediawiki.special/mediawiki.special.preferences.js +++ b/resources/mediawiki.special/mediawiki.special.preferences.js @@ -1,7 +1,7 @@ /* * JavaScript for Special:Preferences */ -( function( $, mw ) { +jQuery( document ).ready( function ( $ ) { $( '#prefsubmit' ).attr( 'id', 'prefcontrol' ); var $preftoc = $('<ul id="preftoc"></ul>'); var $preferences = $( '#preferences' ) @@ -15,8 +15,35 @@ var $fieldsets = $preferences.children( 'fieldset' ) var $legends = $fieldsets.children( 'legend' ) .addClass( 'mainLegend' ); +/** + * It uses document.getElementById for security reasons (html injections in + * jQuery()). + * + * @param String name: the name of a tab without the prefix ("mw-prefsection-") + * @param String mode: [optional] A hash will be set according to the current + * open section. Set mode 'noHash' to surpress this. + */ +function switchPrefTab( name, mode ) { + var $tab, scrollTop; + // Handle hash manually to prevent jumping, + // therefore save and restore scrollTop to prevent jumping. + scrollTop = $( window ).scrollTop(); + if ( mode !== 'noHash' ) { + window.location.hash = '#mw-prefsection-' + name; + } + $( window ).scrollTop( scrollTop ); + + $preftoc.find( 'li' ).removeClass( 'selected' ); + $tab = $( document.getElementById( 'preftab-' + name ) ); + if ( $tab.length ) { + $tab.parent().addClass( 'selected' ); + $preferences.children( 'fieldset' ).hide(); + $( document.getElementById( 'mw-prefsection-' + name ) ).show(); + } +} + // Populate the prefToc -$legends.each( function( i, legend ) { +$legends.each( function ( i, legend ) { var $legend = $(legend); if ( i === 0 ) { $legend.parent().show(); @@ -30,18 +57,6 @@ $legends.each( function( i, legend ) { text : $legend.text(), id : ident.replace( 'mw-prefsection', 'preftab' ), href : '#' + ident - }).click( function( e ) { - e.preventDefault(); - // Handle hash manually to prevent jumping - // Therefore save and restore scrollTop to prevent jumping - var scrollTop = $(window).scrollTop(); - window.location.hash = $(this).attr('href'); - $(window).scrollTop(scrollTop); - - $preftoc.find( 'li' ).removeClass( 'selected' ); - $(this).parent().addClass( 'selected' ); - $( '#preferences > fieldset' ).hide(); - $( '#' + ident ).show(); }); $li.append( $a ); $preftoc.append( $li ); @@ -49,15 +64,35 @@ $legends.each( function( i, legend ) { // If we've reloaded the page or followed an open-in-new-window, // make the selected tab visible. -// On document ready: -$( function() { - var hash = window.location.hash; - if( hash.match( /^#mw-prefsection-[\w-]+/ ) ) { - var $tab = $( hash.replace( 'mw-prefsection', 'preftab' ) ); - $tab.click(); - } -} ); +var hash = window.location.hash; +if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) { + switchPrefTab( hash.replace( '#mw-prefsection-' , '' ) ); +} +// In browsers that support the onhashchange event we will not bind click +// handlers and instead let the browser do the default behavior (clicking the +// <a href="#.."> will naturally set the hash, handled by onhashchange. +// But other things that change the hash will also be catched (e.g. using +// the Back and Forward browser navigation). +if ( 'onhashchange' in window ) { + $(window).on( 'hashchange' , function () { + var hash = window.location.hash; + if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) { + switchPrefTab( hash.replace( '#mw-prefsection-', '' ) ); + } else if ( hash === '' ) { + switchPrefTab( 'personal', 'noHash' ); + } + }); +// In older browsers we'll bind a click handler as fallback. +// We must not have onhashchange *and* the click handlers, other wise +// the click handler calls switchPrefTab() which sets the hash value, +// which triggers onhashcange and calls switchPrefTab() again. +} else { + $preftoc.on( 'click', 'li a', function ( e ) { + switchPrefTab( $( this ).attr( 'href' ).replace( '#mw-prefsection-', '' ) ); + e.preventDefault(); + }); +} /** * Timezone functions. @@ -71,7 +106,7 @@ var $localtimeHolder = $( '#wpLocalTime' ); var servertime = parseInt( $( 'input[name=wpServerTime]' ).val(), 10 ); var minuteDiff = 0; -var minutesToHours = function( min ) { +var minutesToHours = function ( min ) { var tzHour = Math.floor( Math.abs( min ) / 60 ); var tzMin = Math.abs( min ) % 60; var tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour + @@ -79,7 +114,7 @@ var minutesToHours = function( min ) { return tzString; }; -var hoursToMinutes = function( hour ) { +var hoursToMinutes = function ( hour ) { var arr = hour.split( ':' ); arr[0] = parseInt( arr[0], 10 ); @@ -102,7 +137,7 @@ var hoursToMinutes = function( hour ) { } }; -var updateTimezoneSelection = function() { +var updateTimezoneSelection = function () { var type = $tzSelect.val(); if ( type == 'guess' ) { // Get browser timezone & fill it in @@ -133,8 +168,8 @@ var updateTimezoneSelection = function() { }; if ( $tzSelect.length && $tzTextbox.length ) { - $tzSelect.change( function() { updateTimezoneSelection(); } ); - $tzTextbox.blur( function() { updateTimezoneSelection(); } ); + $tzSelect.change( function () { updateTimezoneSelection(); } ); + $tzTextbox.blur( function () { updateTimezoneSelection(); } ); updateTimezoneSelection(); } -} )( jQuery, mediaWiki );
\ No newline at end of file +} ); diff --git a/resources/mediawiki.special/mediawiki.special.recentchanges.js b/resources/mediawiki.special/mediawiki.special.recentchanges.js index 3d520f5e..7996d935 100644 --- a/resources/mediawiki.special/mediawiki.special.recentchanges.js +++ b/resources/mediawiki.special/mediawiki.special.recentchanges.js @@ -1,5 +1,5 @@ /* JavaScript for Special:RecentChanges */ -( function( $ ) { +( function ( mw, $ ) { var checkboxes = [ 'nsassociated', 'nsinvert' ]; @@ -14,17 +14,17 @@ * Handler to disable/enable the namespace selector checkboxes when the * special 'all' namespace is selected/unselected respectively. */ - updateCheckboxes: function() { + updateCheckboxes: function () { // The option element for the 'all' namespace has an empty value - var isAllNS = ('' === $select.find('option:selected').val() ); + var isAllNS = $select.find('option:selected').val() === ''; // Iterates over checkboxes and propagate the selected option - $.each( checkboxes, function( i, id ) { + $.each( checkboxes, function ( i, id ) { $( '#' + id ).prop( 'disabled', isAllNS ); }); }, - init: function() { + init: function () { // Populate $select = $( '#namespace' ); @@ -36,4 +36,4 @@ // Run when document is ready $( rc.init ); -})( jQuery ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.search.js b/resources/mediawiki.special/mediawiki.special.search.js index 8865d04c..04954e8d 100644 --- a/resources/mediawiki.special/mediawiki.special.search.js +++ b/resources/mediawiki.special/mediawiki.special.search.js @@ -1,21 +1,33 @@ /* * JavaScript for Special:Search */ -jQuery( function( $ ) { +( function( $, mw ) { $( function() { // Emulate HTML5 autofocus behavior in non HTML5 compliant browsers if ( !( 'autofocus' in document.createElement( 'input' ) ) ) { $( 'input[autofocus]:first' ).focus(); } -// Bind check all/none button +// Create check all/none button var $checkboxes = $('#powersearch input[id^=mw-search-ns]'); -$('#mw-search-toggleall').click( function() { - $checkboxes.prop("checked", true); -} ); -$('#mw-search-togglenone').click( function() { - $checkboxes.prop("checked", false); -} ); +$('#mw-search-togglebox').append( + $('<label />') + .text(mw.msg('powersearch-togglelabel')) +).append( + $('<input type="button" />') + .attr('id', 'mw-search-toggleall') + .attr('value', mw.msg('powersearch-toggleall')) + .click( function() { + $checkboxes.prop('checked', true); + } ) +).append( + $('<input type="button" />') + .attr('id', 'mw-search-togglenone') + .attr('value', mw.msg('powersearch-togglenone')) + .click( function() { + $checkboxes.prop('checked', false); + } ) +); // Change the header search links to what user entered var headerLinks = $('.search-types a'); @@ -34,4 +46,4 @@ $('#searchText, #powerSearchText').change(function() { }); }).trigger('change'); -} ); +} ); } )( jQuery, mediaWiki ); diff --git a/resources/mediawiki.special/mediawiki.special.upload.js b/resources/mediawiki.special/mediawiki.special.upload.js index 85b3f3f5..63e89713 100644 --- a/resources/mediawiki.special/mediawiki.special.upload.js +++ b/resources/mediawiki.special/mediawiki.special.upload.js @@ -1,281 +1,299 @@ -/* +/** * JavaScript for Special:Upload * Note that additional code still lives in skins/common/upload.js */ - -/** - * Add a preview to the upload form - */ -jQuery( function( $ ) { +( function ( mw, $ ) { /** - * Is the FileAPI available with sufficient functionality? + * Add a preview to the upload form */ - function hasFileAPI(){ - return typeof window.FileReader !== 'undefined'; - } + $( function ( $ ) { + /** + * Is the FileAPI available with sufficient functionality? + */ + function hasFileAPI() { + return typeof window.FileReader !== 'undefined'; + } - /** - * Check if this is a recognizable image type... - * Also excludes files over 10M to avoid going insane on memory usage. - * - * @todo is there a way we can ask the browser what's supported in <img>s? - * @todo put SVG back after working around Firefox 7 bug <https://bugzilla.wikimedia.org/show_bug.cgi?id=31643> - * - * @param {File} file - * @return boolean - */ - function fileIsPreviewable( file ) { - var known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'], - tooHuge = 10 * 1024 * 1024; - return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge; - } + /** + * Check if this is a recognizable image type... + * Also excludes files over 10M to avoid going insane on memory usage. + * + * @todo is there a way we can ask the browser what's supported in <img>s? + * @todo put SVG back after working around Firefox 7 bug <https://bugzilla.wikimedia.org/show_bug.cgi?id=31643> + * + * @param {File} file + * @return boolean + */ + function fileIsPreviewable( file ) { + var known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'], + tooHuge = 10 * 1024 * 1024; + return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge; + } - /** - * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload - * in browsers supporting HTML5 FileAPI. - * - * As of this writing, known good: - * - Firefox 3.6+ - * - Chrome 7.something - * - * @todo check file size limits and warn of likely failures - * - * @param {File} file - */ - function showPreview( file ) { - var previewSize = 180, - thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' + - '<div class="thumbinner">' + - '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' + - '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' + - '</div>' + - '</div>' ); - thumb.find( '.filename' ).text( file.name ).end() - .find( '.fileinfo' ).text( prettySize( file.size ) ).end(); + /** + * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload + * in browsers supporting HTML5 FileAPI. + * + * As of this writing, known good: + * - Firefox 3.6+ + * - Chrome 7.something + * + * @todo check file size limits and warn of likely failures + * + * @param {File} file + */ + function showPreview( file ) { + var previewSize = 180, + thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' + + '<div class="thumbinner">' + + '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' + + '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' + + '</div>' + + '</div>' ); + thumb.find( '.filename' ).text( file.name ).end() + .find( '.fileinfo' ).text( prettySize( file.size ) ).end(); + + var $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>'), + ctx = $canvas[0].getContext( '2d' ); + $( '#mw-htmlform-source' ).parent().prepend( thumb ); + + var meta; + fetchPreview( file, function( dataURL ) { + var img = new Image(), + rotation = 0; - var $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>'), - ctx = $canvas[0].getContext( '2d' ); - $( '#mw-htmlform-source' ).parent().prepend( thumb ); + if ( meta && meta.tiff && meta.tiff.Orientation ) { + rotation = ( 360 - ( function () { + // See includes/media/Bitmap.php + switch ( meta.tiff.Orientation.value ) { + case 8: + return 90; + case 3: + return 180; + case 6: + return 270; + default: + return 0; + } + }() ) ) % 360; + } - var meta; - fetchPreview( file, function( dataURL ) { - var img = new Image(), - rotation = 0; + img.onload = function () { + var width, height, x, y, dx, dy, logicalWidth, logicalHeight; + // Fit the image within the previewSizexpreviewSize box + if ( img.width > img.height ) { + width = previewSize; + height = img.height / img.width * previewSize; + } else { + height = previewSize; + width = img.width / img.height * previewSize; + } + // Determine the offset required to center the image + dx = (180 - width) / 2; + dy = (180 - height) / 2; + switch ( rotation ) { + // If a rotation is applied, the direction of the axis + // changes as well. You can derive the values below by + // drawing on paper an axis system, rotate it and see + // where the positive axis direction is + case 0: + x = dx; + y = dy; + logicalWidth = img.width; + logicalHeight = img.height; + break; + case 90: - if ( meta && meta.tiff && meta.tiff.Orientation ) { - rotation = (360 - function () { - // See includes/media/Bitmap.php - switch ( meta.tiff.Orientation.value ) { - case 8: - return 90; - case 3: - return 180; - case 6: - return 270; - default: - return 0; + x = dx; + y = dy - previewSize; + logicalWidth = img.height; + logicalHeight = img.width; + break; + case 180: + x = dx - previewSize; + y = dy - previewSize; + logicalWidth = img.width; + logicalHeight = img.height; + break; + case 270: + x = dx - previewSize; + y = dy; + logicalWidth = img.height; + logicalHeight = img.width; + break; } - }() ) % 360; - } - img.onload = function() { - var width, height, x, y, dx, dy, logicalWidth, logicalHeight; - // Fit the image within the previewSizexpreviewSize box - if ( img.width > img.height ) { - width = previewSize; - height = img.height / img.width * previewSize; - } else { - height = previewSize; - width = img.width / img.height * previewSize; - } - // Determine the offset required to center the image - dx = (180 - width) / 2; - dy = (180 - height) / 2; - switch ( rotation ) { - // If a rotation is applied, the direction of the axis - // changes as well. You can derive the values below by - // drawing on paper an axis system, rotate it and see - // where the positive axis direction is - case 0: - x = dx; - y = dy; - logicalWidth = img.width; - logicalHeight = img.height; - break; - case 90: + ctx.clearRect( 0, 0, 180, 180 ); + ctx.rotate( rotation / 180 * Math.PI ); + ctx.drawImage( img, x, y, width, height ); + thumb.find('.mw-small-spinner').replaceWith($canvas); - x = dx; - y = dy - previewSize; - logicalWidth = img.height; - logicalHeight = img.width; - break; - case 180: - x = dx - previewSize; - y = dy - previewSize; - logicalWidth = img.width; - logicalHeight = img.height; - break; - case 270: - x = dx - previewSize; - y = dy; - logicalWidth = img.height; - logicalHeight = img.width; - break; + // Image size + var info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) + + ', ' + prettySize( file.size ); + $( '#mw-upload-thumbnail .fileinfo' ).text( info ); + }; + img.src = dataURL; + }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) { + try { + meta = mw.libs.jpegmeta( data, file.fileName ); + meta._binary_data = null; + } catch ( e ) { + meta = null; } + } : null ); + } - ctx.clearRect( 0, 0, 180, 180 ); - ctx.rotate( rotation / 180 * Math.PI ); - ctx.drawImage( img, x, y, width, height ); - thumb.find('.mw-small-spinner').replaceWith($canvas); - - // Image size - var info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) + - ', ' + prettySize( file.size ); - $( '#mw-upload-thumbnail .fileinfo' ).text( info ); - }; - img.src = dataURL; - }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) { - try { - meta = mw.libs.jpegmeta( data, file.fileName ); - meta._binary_data = null; - } catch ( e ) { - meta = null; - } - } : null ); - } + /** + * Start loading a file into memory; when complete, pass it as a + * data URL to the callback function. If the callbackBinary is set it will + * first be read as binary and afterwards as data URL. Useful if you want + * to do preprocessing on the binary data first. + * + * @param {File} file + * @param {function} callback + * @param {function} callbackBinary + */ + function fetchPreview( file, callback, callbackBinary ) { + var reader = new FileReader(); + if ( callbackBinary && 'readAsBinaryString' in reader ) { + // To fetch JPEG metadata we need a binary string; start there. + // todo: + reader.onload = function() { + callbackBinary( reader.result ); - /** - * Start loading a file into memory; when complete, pass it as a - * data URL to the callback function. If the callbackBinary is set it will - * first be read as binary and afterwards as data URL. Useful if you want - * to do preprocessing on the binary data first. - * - * @param {File} file - * @param {function} callback - * @param {function} callbackBinary - */ - function fetchPreview( file, callback, callbackBinary ) { - var reader = new FileReader(); - if ( callbackBinary ) { - // To fetch JPEG metadata we need a binary string; start there. - // todo: - reader.onload = function() { - callbackBinary( reader.result ); + // Now run back through the regular code path. + fetchPreview( file, callback ); + }; + reader.readAsBinaryString( file ); + } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) { + // readAsArrayBuffer replaces readAsBinaryString + // However, our JPEG metadata library wants a string. + // So, this is going to be an ugly conversion. + reader.onload = function() { + var buffer = new Uint8Array( reader.result ), + string = ''; + for ( var i = 0; i < buffer.byteLength; i++ ) { + string += String.fromCharCode( buffer[i] ); + } + callbackBinary( string ); - // Now run back through the regular code path. - fetchPreview(file, callback ); - }; - reader.readAsBinaryString( file ); - } else if ('URL' in window && 'createObjectURL' in window.URL) { - // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL> - // WebKit has it in a namespace for now but that's ok. ;) - // - // Lifetime of this URL is until document close, which is fine - // for Special:Upload -- if this code gets used on longer-running - // pages, add a revokeObjectURL() when it's no longer needed. - // - // Prefer this over readAsDataURL for Firefox 7 due to bug reading - // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165> - callback(window.URL.createObjectURL(file)); - } else { - // This ends up decoding the file to base-64 and back again, which - // feels horribly inefficient. - reader.onload = function() { - callback( reader.result ); - }; - reader.readAsDataURL( file ); + // Now run back through the regular code path. + fetchPreview( file, callback ); + }; + reader.readAsArrayBuffer( file ); + } else if ( 'URL' in window && 'createObjectURL' in window.URL ) { + // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL> + // WebKit has it in a namespace for now but that's ok. ;) + // + // Lifetime of this URL is until document close, which is fine + // for Special:Upload -- if this code gets used on longer-running + // pages, add a revokeObjectURL() when it's no longer needed. + // + // Prefer this over readAsDataURL for Firefox 7 due to bug reading + // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165> + callback( window.URL.createObjectURL( file ) ); + } else { + // This ends up decoding the file to base-64 and back again, which + // feels horribly inefficient. + reader.onload = function() { + callback( reader.result ); + }; + reader.readAsDataURL( file ); + } } - } - /** - * Format a file size attractively. - * @todo match numeric formatting - * - * @param {number} s - * @return string - */ - function prettySize( s ) { - var sizes = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes']; - while ( s >= 1024 && sizes.length > 1 ) { - s /= 1024; - sizes = sizes.slice( 1 ); + /** + * Format a file size attractively. + * @todo match numeric formatting + * + * @param {number} s + * @return string + */ + function prettySize( s ) { + var sizeMsgs = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes']; + while ( s >= 1024 && sizeMsgs.length > 1 ) { + s /= 1024; + sizeMsgs = sizeMsgs.slice( 1 ); + } + return mw.msg( sizeMsgs[0], Math.round( s ) ); } - return mw.msg( sizes[0], Math.round( s ) ); - } - /** - * Clear the file upload preview area. - */ - function clearPreview() { - $( '#mw-upload-thumbnail' ).remove(); - } + /** + * Clear the file upload preview area. + */ + function clearPreview() { + $( '#mw-upload-thumbnail' ).remove(); + } - /** - * Check if the file does not exceed the maximum size - */ - function checkMaxUploadSize( file ) { - function getMaxUploadSize( type ) { - var sizes = mw.config.get( 'wgMaxUploadSize' ); - if ( sizes[type] !== undefined ) { - return sizes[type]; + /** + * Check if the file does not exceed the maximum size + */ + function checkMaxUploadSize( file ) { + function getMaxUploadSize( type ) { + var sizes = mw.config.get( 'wgMaxUploadSize' ); + if ( sizes[type] !== undefined ) { + return sizes[type]; + } + return sizes['*']; } - return sizes['*']; - } - $( '.mw-upload-source-error' ).remove(); + $( '.mw-upload-source-error' ).remove(); - var maxSize = getMaxUploadSize( 'file' ); - if ( file.size > maxSize ) { - var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' + - mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' ); - $( '#wpUploadFile' ).after( error ); - return false; + var maxSize = getMaxUploadSize( 'file' ); + if ( file.size > maxSize ) { + var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' + + mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' ); + $( '#wpUploadFile' ).after( error ); + return false; + } + return true; } - return true; - } - /** - * Initialization - */ - if ( hasFileAPI() ) { - // Update thumbnail when the file selection control is updated. - $( '#wpUploadFile' ).change( function() { - clearPreview(); - if ( this.files && this.files.length ) { - // Note: would need to be updated to handle multiple files. - var file = this.files[0]; + /** + * Initialization + */ + if ( hasFileAPI() ) { + // Update thumbnail when the file selection control is updated. + $( '#wpUploadFile' ).change( function () { + clearPreview(); + if ( this.files && this.files.length ) { + // Note: would need to be updated to handle multiple files. + var file = this.files[0]; - if ( !checkMaxUploadSize( file ) ) { - return; - } + if ( !checkMaxUploadSize( file ) ) { + return; + } - if ( fileIsPreviewable( file ) ) { - showPreview( file ); + if ( fileIsPreviewable( file ) ) { + showPreview( file ); + } } - } - } ); - } -} ); + } ); + } + } ); -/** - * Disable all upload source fields except the selected one - */ -jQuery( function ( $ ) { - var rows = $( '.mw-htmlform-field-UploadSourceField' ); - for ( var i = rows.length; i; i-- ) { - var row = rows[i - 1]; - $( 'input[name="wpSourceType"]', row ).change( function () { - var currentRow = row; // Store current row in our own scope - return function () { - $( '.mw-upload-source-error' ).remove(); - if ( this.checked ) { - // Disable all inputs - $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', 'disabled' ); - // Re-enable the current one - $( 'input', currentRow ).prop( 'disabled', false ); - } - }; - }() ); - } -} ); + /** + * Disable all upload source fields except the selected one + */ + $( function ( $ ) { + var i, row, + rows = $( '.mw-htmlform-field-UploadSourceField' ); + for ( i = rows.length; i; i-- ) { + row = rows[i - 1]; + $( 'input[name="wpSourceType"]', row ).change( ( function () { + var currentRow = row; // Store current row in our own scope + return function () { + $( '.mw-upload-source-error' ).remove(); + if ( this.checked ) { + // Disable all inputs + $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', true ); + // Re-enable the current one + $( 'input', currentRow ).prop( 'disabled', false ); + } + }; + }() ) ); + } + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.Title.js b/resources/mediawiki/mediawiki.Title.js index 8d7996cb..33cca585 100644 --- a/resources/mediawiki/mediawiki.Title.js +++ b/resources/mediawiki/mediawiki.Title.js @@ -7,7 +7,7 @@ * * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink */ -( function( $ ) { +( function ( mw, $ ) { /* Local space */ @@ -20,19 +20,25 @@ * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is. * @return {Title} this */ -var Title = function( title, namespace ) { - this._ns = 0; // integer namespace id - this._name = null; // name in canonical 'database' form - this._ext = null; // extension + function Title( title, namespace ) { + this.ns = 0; // integer namespace id + this.name = null; // name in canonical 'database' form + this.ext = null; // extension if ( arguments.length === 2 ) { setNameAndExtension( this, title ); - this._ns = fixNsId( namespace ); + this.ns = fixNsId( namespace ); } else if ( arguments.length === 1 ) { setAll( this, title ); } return this; - }, + } + +var + /** + * Public methods (defined later) + */ + fn, /** * Strip some illegal chars: control chars, colon, less than, greater than, @@ -41,7 +47,7 @@ var Title = function( title, namespace ) { * @param s {String} * @return {String} */ - clean = function( s ) { + clean = function ( s ) { if ( s !== undefined ) { return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' ); } @@ -63,14 +69,14 @@ var Title = function( title, namespace ) { /** * Sanitize name. */ - fixName = function( s ) { + fixName = function ( s ) { return clean( $.trim( s ) ); }, /** * Sanitize name. */ - fixExt = function( s ) { + fixExt = function ( s ) { return clean( s ); }, @@ -79,7 +85,7 @@ var Title = function( title, namespace ) { * @param id {Number} Namespace id. * @return {Number|Boolean} The id as-is or boolean false if invalid. */ - fixNsId = function( id ) { + fixNsId = function ( id ) { // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] ) var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()]; @@ -98,9 +104,13 @@ var Title = function( title, namespace ) { * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored). * @return {Number|Boolean} Namespace id or boolean false if unrecognized. */ - getNsIdByName = function( ns ) { - // toLowerCase throws exception on null/undefined. Return early. - if ( ns == null ) { + getNsIdByName = function ( ns ) { + // Don't cast non-strings to strings, because null or undefined + // should not result in returning the id of a potential namespace + // called "Null:" (e.g. on nullwiki.example.org) + // Also, toLowerCase throws exception on null/undefined, because + // it is a String.prototype method. + if ( typeof ns !== 'string' ) { return false; } ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize @@ -119,22 +129,22 @@ var Title = function( title, namespace ) { * @param raw {String} * @return {mw.Title} */ - setAll = function( title, s ) { + setAll = function ( title, s ) { // In normal browsers the match-array contains null/undefined if there's no match, // IE returns an empty string. - var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w{1,5}))?$/ ), + var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w+))?$/ ), ns_match = getNsIdByName( matches[1] ); // Namespace must be valid, and title must be a non-empty string. if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) { - title._ns = ns_match; - title._name = fixName( matches[2] ); + title.ns = ns_match; + title.name = fixName( matches[2] ); if ( typeof matches[3] === 'string' && matches[3] !== '' ) { - title._ext = fixExt( matches[3] ); + title.ext = fixExt( matches[3] ); } } else { // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace. - title._ns = 0; + title.ns = 0; setNameAndExtension( title, s ); } return title; @@ -147,16 +157,16 @@ var Title = function( title, namespace ) { * @param raw {String} * @return {mw.Title} */ - setNameAndExtension = function( title, raw ) { + setNameAndExtension = function ( title, raw ) { // In normal browsers the match-array contains null/undefined if there's no match, // IE returns an empty string. - var matches = raw.match( /^(?:)?(.*?)(?:\.(\w{1,5}))?$/ ); + var matches = raw.match( /^(?:)?(.*?)(?:\.(\w+))?$/ ); // Title must be a non-empty string. if ( typeof matches[1] === 'string' && matches[1] !== '' ) { - title._name = fixName( matches[1] ); + title.name = fixName( matches[1] ); if ( typeof matches[2] === 'string' && matches[2] !== '' ) { - title._ext = fixExt( matches[2] ); + title.ext = fixExt( matches[2] ); } } else { throw new Error( 'mw.Title: Could not parse title "' + raw + '"' ); @@ -172,7 +182,7 @@ var Title = function( title, namespace ) { * @param title {mixed} prefixed db-key name (string) or instance of Title * @return {mixed} Boolean true/false if the information is available. Otherwise null. */ - Title.exists = function( title ) { + Title.exists = function ( title ) { var type = $.type( title ), obj = Title.exist.pages, match; if ( type === 'string' ) { match = obj[title]; @@ -203,7 +213,7 @@ var Title = function( title, namespace ) { * @param state {Boolean} (optional) State of the given titles. Defaults to true. * @return {Boolean} */ - set: function( titles, state ) { + set: function ( titles, state ) { titles = $.isArray( titles ) ? titles : [titles]; state = state === undefined ? true : !!state; var pages = this.pages, i, len = titles.length; @@ -216,15 +226,15 @@ var Title = function( title, namespace ) { /* Public methods */ - var fn = { + fn = { constructor: Title, /** * Get the namespace number. * @return {Number} */ - getNamespaceId: function(){ - return this._ns; + getNamespaceId: function (){ + return this.ns; }, /** @@ -232,19 +242,19 @@ var Title = function( title, namespace ) { * In NS_MAIN this is '', otherwise namespace name plus ':' * @return {String} */ - getNamespacePrefix: function(){ - return mw.config.get( 'wgFormattedNamespaces' )[this._ns].replace( / /g, '_' ) + (this._ns === 0 ? '' : ':'); + getNamespacePrefix: function (){ + return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':'); }, /** * The name, like "Foo_bar" * @return {String} */ - getName: function() { - if ( $.inArray( this._ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) { - return this._name; + getName: function () { + if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) { + return this.name; } else { - return $.ucFirst( this._name ); + return $.ucFirst( this.name ); } }, @@ -252,7 +262,7 @@ var Title = function( title, namespace ) { * The name, like "Foo bar" * @return {String} */ - getNameText: function() { + getNameText: function () { return text( this.getName() ); }, @@ -260,7 +270,7 @@ var Title = function( title, namespace ) { * Get full name in prefixed DB form, like File:Foo_bar.jpg, * most useful for API calls, anything that must identify the "title". */ - getPrefixedDb: function() { + getPrefixedDb: function () { return this.getNamespacePrefix() + this.getMain(); }, @@ -268,7 +278,7 @@ var Title = function( title, namespace ) { * Get full name in text form, like "File:Foo bar.jpg". * @return {String} */ - getPrefixedText: function() { + getPrefixedText: function () { return text( this.getPrefixedDb() ); }, @@ -276,7 +286,7 @@ var Title = function( title, namespace ) { * The main title (without namespace), like "Foo_bar.jpg" * @return {String} */ - getMain: function() { + getMain: function () { return this.getName() + this.getDotExtension(); }, @@ -284,7 +294,7 @@ var Title = function( title, namespace ) { * The "text" form, like "Foo bar.jpg" * @return {String} */ - getMainText: function() { + getMainText: function () { return text( this.getMain() ); }, @@ -292,23 +302,23 @@ var Title = function( title, namespace ) { * Get the extension (returns null if there was none) * @return {String|null} extension */ - getExtension: function() { - return this._ext; + getExtension: function () { + return this.ext; }, /** * Convenience method: return string like ".jpg", or "" if no extension * @return {String} */ - getDotExtension: function() { - return this._ext === null ? '' : '.' + this._ext; + getDotExtension: function () { + return this.ext === null ? '' : '.' + this.ext; }, /** * Return the URL to this title * @return {String} */ - getUrl: function() { + getUrl: function () { return mw.util.wikiGetlink( this.toString() ); }, @@ -316,7 +326,7 @@ var Title = function( title, namespace ) { * Whether this title exists on the wiki. * @return {mixed} Boolean true/false if the information is available. Otherwise null. */ - exists: function() { + exists: function () { return Title.exists( this ); } }; @@ -331,4 +341,4 @@ var Title = function( title, namespace ) { // Expose mw.Title = Title; -})(jQuery); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.Uri.js b/resources/mediawiki/mediawiki.Uri.js index 26fdfa9e..bd12b214 100644 --- a/resources/mediawiki/mediawiki.Uri.js +++ b/resources/mediawiki/mediawiki.Uri.js @@ -56,7 +56,7 @@ * */ -( function( $, mw ) { +( function ( mw, $ ) { /** * Function that's useful when constructing the URI string -- we frequently encounter the pattern of @@ -70,9 +70,8 @@ function cat( pre, val, post, raw ) { if ( val === undefined || val === null || val === '' ) { return ''; - } else { - return pre + ( raw ? val : mw.Uri.encode( val ) ) + post; } + return pre + ( raw ? val : mw.Uri.encode( val ) ) + post; } // Regular expressions to parse many common URIs. @@ -98,13 +97,16 @@ * We use a factory to inject a document location, for relative URLs, including protocol-relative URLs. * so the library is still testable & purely functional. */ - mw.UriRelative = function( documentLocation ) { + mw.UriRelative = function ( documentLocation ) { + var defaultUri; /** * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse. * @constructor * @param {Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). * Object must have non-blank 'protocol', 'host', and 'path' properties. + * This parameter is optional. If omitted (or set to undefined, null or empty string), then an object will be created + * for the default uri of this constructor (e.g. document.location for mw.Uri in MediaWiki core). * @param {Object|Boolean} Object with options, or (backwards compatibility) a boolean for strictMode * - strictMode {Boolean} Trigger strict mode parsing of the url. Default: false * - overrideKeys {Boolean} Wether to let duplicate query parameters override eachother (true) or automagically @@ -117,25 +119,48 @@ overrideKeys: false }, options ); - if ( uri !== undefined && uri !== null || uri !== '' ) { + if ( uri !== undefined && uri !== null && uri !== '' ) { if ( typeof uri === 'string' ) { - this._parse( uri, options ); + this.parse( uri, options ); } else if ( typeof uri === 'object' ) { - var _this = this; - $.each( properties, function( i, property ) { - _this[property] = uri[property]; - } ); - if ( this.query === undefined ) { + // Copy data over from existing URI object + for ( var prop in uri ) { + // Only copy direct properties, not inherited ones + if ( uri.hasOwnProperty( prop ) ) { + // Deep copy object properties + if ( $.isArray( uri[prop] ) || $.isPlainObject( uri[prop] ) ) { + this[prop] = $.extend( true, {}, uri[prop] ); + } else { + this[prop] = uri[prop]; + } + } + } + if ( !this.query ) { this.query = {}; } } + } else { + // If we didn't get a URI in the constructor, use the default one. + return defaultUri.clone(); } // protocol-relative URLs if ( !this.protocol ) { - this.protocol = defaultProtocol; + this.protocol = defaultUri.protocol; + } + // No host given: + if ( !this.host ) { + this.host = defaultUri.host; + // port ? + if ( !this.port ) { + this.port = defaultUri.port; + } + } + if ( this.path && this.path.charAt( 0 ) !== '/' ) { + // A real relative URL, relative to defaultUri.path. We can't really handle that since we cannot + // figure out whether the last path compoennt of defaultUri.path is a directory or a file. + throw new Error( 'Bad constructor arguments' ); } - if ( !( this.protocol && this.host && this.path ) ) { throw new Error( 'Bad constructor arguments' ); } @@ -147,7 +172,7 @@ * @param {String} string * @return {String} encoded for URI */ - Uri.encode = function( s ) { + Uri.encode = function ( s ) { return encodeURIComponent( s ) .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28') .replace( /\)/g, '%29').replace( /\*/g, '%2A') @@ -159,7 +184,7 @@ * @param {String} string encoded for URI * @return {String} decoded string */ - Uri.decode = function( s ) { + Uri.decode = function ( s ) { return decodeURIComponent( s.replace( /\+/g, '%20' ) ); }; @@ -171,23 +196,25 @@ * @param {Object} options * @return {Boolean} success */ - _parse: function( str, options ) { - var matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str ); - var uri = this; - $.each( properties, function( i, property ) { + parse: function ( str, options ) { + var q, + uri = this, + matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str ); + $.each( properties, function ( i, property ) { uri[ property ] = matches[ i+1 ]; } ); // uri.query starts out as the query string; we will parse it into key-val pairs then make // that object the "query" property. // we overwrite query in uri way to make cloning easier, it can use the same list of properties. - var q = {}; + q = {}; // using replace to iterate over a string if ( uri.query ) { uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { + var k, v; if ( $1 ) { - var k = Uri.decode( $1 ); - var v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 ); + k = Uri.decode( $1 ); + v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 ); // If overrideKeys, always (re)set top level value. // If not overrideKeys but this key wasn't set before, then we set it as well. @@ -215,7 +242,7 @@ * Returns user and password portion of a URI. * @return {String} */ - getUserInfo: function() { + getUserInfo: function () { return cat( '', this.user, cat( ':', this.password, '' ) ); }, @@ -223,7 +250,7 @@ * Gets host and port portion of a URI. * @return {String} */ - getHostPort: function() { + getHostPort: function () { return this.host + cat( ':', this.port, '' ); }, @@ -232,7 +259,7 @@ * In most real-world URLs, this is simply the hostname, but it is more general. * @return {String} */ - getAuthority: function() { + getAuthority: function () { return cat( '', this.getUserInfo(), '@' ) + this.getHostPort(); }, @@ -241,12 +268,12 @@ * Does not preserve the order of arguments passed into the URI. Does handle escaping. * @return {String} */ - getQueryString: function() { + getQueryString: function () { var args = []; - $.each( this.query, function( key, val ) { - var k = Uri.encode( key ); - var vals = val === null ? [ null ] : $.makeArray( val ); - $.each( vals, function( i, v ) { + $.each( this.query, function ( key, val ) { + var k = Uri.encode( key ), + vals = $.isArray( val ) ? val : [ val ]; + $.each( vals, function ( i, v ) { args.push( k + ( v === null ? '' : '=' + Uri.encode( v ) ) ); } ); } ); @@ -257,7 +284,7 @@ * Returns everything after the authority section of the URI * @return {String} */ - getRelativePath: function() { + getRelativePath: function () { return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' ); }, @@ -265,7 +292,7 @@ * Gets the entire URI string. May not be precisely the same as input due to order of query arguments. * @return {String} the URI string */ - toString: function() { + toString: function () { return this.protocol + '://' + this.getAuthority() + this.getRelativePath(); }, @@ -273,7 +300,7 @@ * Clone this URI * @return {Object} new URI object with same properties */ - clone: function() { + clone: function () { return new Uri( this ); }, @@ -282,20 +309,20 @@ * @param {Object} query parameters in key-val form to override or add * @return {Object} this URI object */ - extend: function( parameters ) { + extend: function ( parameters ) { $.extend( this.query, parameters ); return this; } }; - var defaultProtocol = ( new Uri( documentLocation ) ).protocol; + defaultUri = new Uri( documentLocation ); - return Uri; + return Uri; }; // if we are running in a browser, inject the current document location, for relative URLs - if ( document && document.location && document.location.href ) { + if ( document && document.location && document.location.href ) { mw.Uri = mw.UriRelative( document.location.href ); } -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.debug.css b/resources/mediawiki/mediawiki.debug.css index 923d4a47..149e1bff 100644 --- a/resources/mediawiki/mediawiki.debug.css +++ b/resources/mediawiki/mediawiki.debug.css @@ -6,7 +6,6 @@ } .mw-debug pre { - font-family: Monaco, "Consolas", "Lucida Console", "Courier New", monospace; font-size: 11px; padding: 0; margin: 0; diff --git a/resources/mediawiki/mediawiki.debug.js b/resources/mediawiki/mediawiki.debug.js index a2bfbcbe..1ad1a623 100644 --- a/resources/mediawiki/mediawiki.debug.js +++ b/resources/mediawiki/mediawiki.debug.js @@ -5,12 +5,13 @@ * @since 1.19 */ -( function ( $, mw, undefined ) { -"use strict"; +( function ( mw, $ ) { + 'use strict'; - var hovzer = $.getFootHovzer(); + var debug, + hovzer = $.getFootHovzer(); - var debug = mw.Debug = { + debug = mw.Debug = { /** * Toolbar container element * @@ -93,7 +94,7 @@ * Constructs the HTML for the debugging toolbar */ buildHtml: function () { - var $container, $bits, panes, id; + var $container, $bits, panes, id, gitInfo; $container = $( '<div id="mw-debug-toolbar" class="mw-debug"></div>' ); @@ -106,9 +107,9 @@ * @return {jQuery} */ function bitDiv( id ) { - return $( '<div>' ).attr({ + return $( '<div>' ).prop({ id: 'mw-debug-' + id, - 'class': 'mw-debug-bit' + className: 'mw-debug-bit' }) .appendTo( $bits ); } @@ -122,8 +123,8 @@ */ function paneLabel( id, text ) { return $( '<a>' ) - .attr({ - 'class': 'mw-debug-panelabel', + .prop({ + className: 'mw-debug-panelabel', href: '#mw-debug-pane-' + id }) .text( text ); @@ -138,12 +139,12 @@ * @return {jQuery} */ function paneTriggerBitDiv( id, text, count ) { - if( count ) { + if ( count ) { text = text + ' (' + count + ')'; } - return $( '<div>' ).attr({ + return $( '<div>' ).prop({ id: 'mw-debug-' + id, - 'class': 'mw-debug-bit mw-debug-panelink' + className: 'mw-debug-bit mw-debug-panelink' }) .append( paneLabel( id, text ) ) .appendTo( $bits ); @@ -159,9 +160,24 @@ paneTriggerBitDiv( 'includes', 'PHP includes', this.data.includes.length ); + gitInfo = ''; + if ( this.data.gitRevision !== false ) { + gitInfo = '(' + this.data.gitRevision.substring( 0, 7 ) + ')'; + if ( this.data.gitViewUrl !== false ) { + gitInfo = $( '<a>' ) + .attr( 'href', this.data.gitViewUrl ) + .text( gitInfo ); + } + } + bitDiv( 'mwversion' ) - .append( $( '<a href="//www.mediawiki.org/"></a>' ).text( 'MediaWiki' ) ) - .append( ': ' + this.data.mwVersion ); + .append( $( '<a href="//www.mediawiki.org/">MediaWiki</a>' ) ) + .append( document.createTextNode( ': ' + this.data.mwVersion + ' ' ) ) + .append( gitInfo ); + + if ( this.data.gitBranch !== false ) { + bitDiv( 'gitbranch' ).text( 'Git branch: ' + this.data.gitBranch ); + } bitDiv( 'phpversion' ) .append( $( '<a href="//www.php.net/"></a>' ).text( 'PHP' ) ) @@ -191,8 +207,8 @@ } $( '<div>' ) - .attr({ - 'class': 'mw-debug-pane', + .prop({ + className: 'mw-debug-pane', id: 'mw-debug-pane-' + id }) .append( panes[id] ) @@ -210,9 +226,9 @@ $table = $( '<table id="mw-debug-console">' ); - $('<colgroup>').css( 'width', /*padding=*/20 + ( 10*/*fontSize*/11 ) ).appendTo( $table ); - $('<colgroup>').appendTo( $table ); - $('<colgroup>').css( 'width', 350 ).appendTo( $table ); + $( '<colgroup>' ).css( 'width', /* padding = */ 20 + ( 10 * /* fontSize = */ 11 ) ).appendTo( $table ); + $( '<colgroup>' ).appendTo( $table ); + $( '<colgroup>' ).css( 'width', 350 ).appendTo( $table ); entryTypeText = function( entryType ) { @@ -235,7 +251,7 @@ $( '<tr>' ) .append( $( '<td>' ) .text( entry.typeText ) - .attr( 'class', 'mw-debug-console-' + entry.type ) + .addClass( 'mw-debug-console-' + entry.type ) ) .append( $( '<td>' ).html( entry.msg ) ) .append( $( '<td>' ).text( entry.caller ) ) @@ -254,10 +270,10 @@ $table = $( '<table id="mw-debug-querylist"></table>' ); $( '<tr>' ) - .append( $('<th>#</th>').css( 'width', '4em' ) ) - .append( $('<th>SQL</th>') ) - .append( $('<th>Time</th>').css( 'width', '8em' ) ) - .append( $('<th>Call</th>').css( 'width', '18em' ) ) + .append( $( '<th>#</th>' ).css( 'width', '4em' ) ) + .append( $( '<th>SQL</th>' ) ) + .append( $( '<th>Time</th>' ).css( 'width', '8em' ) ) + .append( $( '<th>Call</th>' ).css( 'width', '18em' ) ) .appendTo( $table ); for ( i = 0, length = this.data.queries.length; i < length; i += 1 ) { @@ -285,7 +301,7 @@ for ( i = 0, length = this.data.debugLog.length; i < length; i += 1 ) { line = this.data.debugLog[i]; $( '<li>' ) - .html( mw.html.escape( line ).replace( /\n/g, "<br />\n" ) ) + .html( mw.html.escape( line ).replace( /\n/g, '<br />\n' ) ) .appendTo( $list ); } @@ -348,4 +364,4 @@ } }; -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.feedback.js b/resources/mediawiki/mediawiki.feedback.js index 9a4a7298..634d02b1 100644 --- a/resources/mediawiki/mediawiki.feedback.js +++ b/resources/mediawiki/mediawiki.feedback.js @@ -22,22 +22,22 @@ * Minimal example in how to use it: * * var feedback = new mw.Feedback(); - * $( '#myButton' ).click( function() { feedback.launch(); } ); + * $( '#myButton' ).click( function () { feedback.launch(); } ); * * You can also launch the feedback form with a prefilled subject and body. * See the docs for the launch() method. */ -( function( mw, $, undefined ) { +( function ( mw, $ ) { /** * Thingy for collecting user feedback on a wiki page * @param {Array} options -- optional, all properties optional. - * api: {mw.Api} if omitted, will just create a standard API - * title: {mw.Title} the title of the page where you collect feedback. Defaults to "Feedback". - * dialogTitleMessageKey: {String} message key for the title of the dialog box - * bugsLink: {mw.Uri|String} url where bugs can be posted - * bugsListLink: {mw.Uri|String} url where bugs can be listed + * api: {mw.Api} if omitted, will just create a standard API + * title: {mw.Title} the title of the page where you collect feedback. Defaults to "Feedback". + * dialogTitleMessageKey: {String} message key for the title of the dialog box + * bugsLink: {mw.Uri|String} url where bugs can be posted + * bugsListLink: {mw.Uri|String} url where bugs can be listed */ - mw.Feedback = function( options ) { + mw.Feedback = function ( options ) { if ( options === undefined ) { options = {}; } @@ -67,64 +67,69 @@ }; mw.Feedback.prototype = { - setup: function() { - var _this = this; + setup: function () { + var fb = this; - var $feedbackPageLink = $( '<a></a>' ) - .attr( { 'href': _this.title.getUrl(), 'target': '_blank' } ) + var $feedbackPageLink = $( '<a>' ) + .attr( { 'href': fb.title.getUrl(), 'target': '_blank' } ) .css( { 'white-space': 'nowrap' } ); - var $bugNoteLink = $( '<a></a>' ).attr( { 'href': '#' } ).click( function() { _this.displayBugs(); } ); + var $bugNoteLink = $( '<a>' ).attr( { 'href': '#' } ).click( function () { + fb.displayBugs(); + } ); - var $bugsListLink = $( '<a></a>' ).attr( { 'href': _this.bugsListLink, 'target': '_blank' } ); + var $bugsListLink = $( '<a>' ).attr( { 'href': fb.bugsListLink, 'target': '_blank' } ); + // TODO: Use a stylesheet instead of these inline styles this.$dialog = - $( '<div style="position:relative;"></div>' ).append( + $( '<div style="position: relative;"></div>' ).append( $( '<div class="feedback-mode feedback-form"></div>' ).append( - $( '<small></small>' ).append( - $( '<p></p>' ).msg( + $( '<small>' ).append( + $( '<p>' ).msg( 'feedback-bugornote', $bugNoteLink, - _this.title.getNameText(), + fb.title.getNameText(), $feedbackPageLink.clone() ) ), - $( '<div style="margin-top:1em;"></div>' ).append( + $( '<div style="margin-top: 1em;"></div>' ).append( mw.msg( 'feedback-subject' ), - $( '<br/>' ), - $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width:99%;"/>' ) + $( '<br>' ), + $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width: 99%;"/>' ) ), - $( '<div style="margin-top:0.4em;"></div>' ).append( + $( '<div style="margin-top: 0.4em;"></div>' ).append( mw.msg( 'feedback-message' ), - $( '<br/>' ), - $( '<textarea name="message" class="feedback-message" style="width:99%;" rows="5" cols="60"></textarea>' ) + $( '<br>' ), + $( '<textarea name="message" class="feedback-message" style="width: 99%;" rows="5" cols="60"></textarea>' ) ) ), $( '<div class="feedback-mode feedback-bugs"></div>' ).append( $( '<p>' ).msg( 'feedback-bugcheck', $bugsListLink ) ), - $( '<div class="feedback-mode feedback-submitting" style="text-align:center;margin:3em 0;"></div>' ).append( + $( '<div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' ).append( mw.msg( 'feedback-adding' ), $( '<br/>' ), $( '<span class="feedback-spinner"></span>' ) ), - $( '<div class="feedback-mode feedback-thanks" style="text-align:center;margin:1em"></div>' ).msg( - 'feedback-thanks', _this.title.getNameText(), $feedbackPageLink.clone() + $( '<div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>' ).msg( + 'feedback-thanks', fb.title.getNameText(), $feedbackPageLink.clone() ), - $( '<div class="feedback-mode feedback-error" style="position:relative;"></div>' ).append( - $( '<div class="feedback-error-msg style="color:#990000;margin-top:0.4em;"></div>' ) + $( '<div class="feedback-mode feedback-error" style="position: relative;"></div>' ).append( + $( '<div class="feedback-error-msg style="color: #990000; margin-top: 0.4em;"></div>' ) ) ); // undo some damage from dialog css - this.$dialog.find( 'a' ).css( { 'color': '#0645ad' } ); + this.$dialog.find( 'a' ).css( { + color: '#0645ad' + } ); this.$dialog.dialog({ width: 500, autoOpen: false, title: mw.msg( this.dialogTitleMessageKey ), modal: true, - buttons: _this.buttons + buttons: fb.buttons }); this.subjectInput = this.$dialog.find( 'input.feedback-subject' ).get(0); @@ -132,98 +137,119 @@ }, - display: function( s ) { + display: function ( s ) { this.$dialog.dialog( { buttons:{} } ); // hide the buttons this.$dialog.find( '.feedback-mode' ).hide(); // hide everything this.$dialog.find( '.feedback-' + s ).show(); // show the desired div }, - displaySubmitting: function() { + displaySubmitting: function () { this.display( 'submitting' ); }, - displayBugs: function() { - var _this = this; + displayBugs: function () { + var fb = this; this.display( 'bugs' ); var bugsButtons = {}; - bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function() { window.open( _this.bugsLink, '_blank' ); }; - bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function() { _this.cancel(); }; - this.$dialog.dialog( { buttons: bugsButtons } ); + bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function () { + window.open( fb.bugsLink, '_blank' ); + }; + bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function () { + fb.cancel(); + }; + this.$dialog.dialog( { + buttons: bugsButtons + } ); }, - displayThanks: function() { - var _this = this; + displayThanks: function () { + var fb = this; this.display( 'thanks' ); var closeButton = {}; - closeButton[ mw.msg( 'feedback-close' ) ] = function() { _this.$dialog.dialog( 'close' ); }; - this.$dialog.dialog( { buttons: closeButton } ); + closeButton[ mw.msg( 'feedback-close' ) ] = function () { + fb.$dialog.dialog( 'close' ); + }; + this.$dialog.dialog( { + buttons: closeButton + } ); }, /** * Display the feedback form * @param {Object} optional prefilled contents for the feedback form. Object with properties: - * subject: {String} - * message: {String} + * subject: {String} + * message: {String} */ - displayForm: function( contents ) { - var _this = this; - this.subjectInput.value = (contents && contents.subject) ? contents.subject : ''; - this.messageInput.value = (contents && contents.message) ? contents.message : ''; + displayForm: function ( contents ) { + var fb = this; + this.subjectInput.value = ( contents && contents.subject ) ? contents.subject : ''; + this.messageInput.value = ( contents && contents.message ) ? contents.message : ''; this.display( 'form' ); // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized var formButtons = {}; - formButtons[ mw.msg( 'feedback-submit' ) ] = function() { _this.submit(); }; - formButtons[ mw.msg( 'feedback-cancel' ) ] = function() { _this.cancel(); }; + formButtons[ mw.msg( 'feedback-submit' ) ] = function () { + fb.submit(); + }; + formButtons[ mw.msg( 'feedback-cancel' ) ] = function () { + fb.cancel(); + }; this.$dialog.dialog( { buttons: formButtons } ); // put the buttons back }, - displayError: function( message ) { - var _this = this; + displayError: function ( message ) { + var fb = this; this.display( 'error' ); this.$dialog.find( '.feedback-error-msg' ).msg( message ); var closeButton = {}; - closeButton[ mw.msg( 'feedback-close' ) ] = function() { _this.$dialog.dialog( 'close' ); }; + closeButton[ mw.msg( 'feedback-close' ) ] = function () { + fb.$dialog.dialog( 'close' ); + }; this.$dialog.dialog( { buttons: closeButton } ); }, - cancel: function() { + cancel: function () { this.$dialog.dialog( 'close' ); }, - submit: function() { - var _this = this; - - // get the values to submit - var subject = this.subjectInput.value; - - var message = "<small>User agent: " + navigator.userAgent + "</small>\n\n" - + this.messageInput.value; - if ( message.indexOf( '~~~' ) == -1 ) { - message += " ~~~~"; - } - - this.displaySubmitting(); + submit: function () { + var subject, message, + fb = this; - var ok = function( result ) { + function ok( result ) { if ( result.edit !== undefined ) { if ( result.edit.result === 'Success' ) { - _this.displayThanks(); + fb.displayThanks(); } else { - _this.displayError( 'feedback-error1' ); // unknown API result + // unknown API result + fb.displayError( 'feedback-error1' ); } } else { - _this.displayError( 'feedback-error2' ); // edit failed + // edit failed + fb.displayError( 'feedback-error2' ); } - }; + } - var err = function( code, info ) { - _this.displayError( 'feedback-error3' ); // ajax request failed - }; + function err( code, info ) { + // ajax request failed + fb.displayError( 'feedback-error3' ); + } + + // Get the values to submit. + subject = this.subjectInput.value; + + // We used to include "mw.html.escape( navigator.userAgent )" but there are legal issues + // with posting this without their explicit consent + message = this.messageInput.value; + if ( message.indexOf( '~~~' ) === -1 ) { + message += ' ~~~~'; + } + + this.displaySubmitting(); this.api.newSection( this.title, subject, message, ok, err ); - }, // close submit button function + }, /** * Modify the display form, and then open it, focusing interface on the subject. @@ -231,7 +257,7 @@ * subject: {String} * message: {String} */ - launch: function( contents ) { + launch: function ( contents ) { this.displayForm( contents ); this.$dialog.dialog( 'open' ); this.subjectInput.focus(); @@ -239,4 +265,4 @@ }; -} )( window.mediaWiki, jQuery ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.htmlform.js b/resources/mediawiki/mediawiki.htmlform.js index 17a02cf4..a4753b99 100644 --- a/resources/mediawiki/mediawiki.htmlform.js +++ b/resources/mediawiki/mediawiki.htmlform.js @@ -1,7 +1,7 @@ /** * Utility functions for jazzing up HTMLForm elements */ -( function( $ ) { +( function ( $ ) { /** * jQuery plugin to fade or snap to visible state. @@ -9,7 +9,7 @@ * @param boolean instantToggle (optional) * @return jQuery */ -$.fn.goIn = function( instantToggle ) { +$.fn.goIn = function ( instantToggle ) { if ( instantToggle === true ) { return $(this).show(); } @@ -22,7 +22,7 @@ $.fn.goIn = function( instantToggle ) { * @param boolean instantToggle (optional) * @return jQuery */ -$.fn.goOut = function( instantToggle ) { +$.fn.goOut = function ( instantToggle ) { if ( instantToggle === true ) { return $(this).hide(); } @@ -31,24 +31,24 @@ $.fn.goOut = function( instantToggle ) { /** * Bind a function to the jQuery object via live(), and also immediately trigger - * the function on the objects with an 'instant' paramter set to true - * @param callback function taking one paramter, which is Bool true when the event + * the function on the objects with an 'instant' parameter set to true + * @param callback function taking one parameter, which is Bool true when the event * is called immediately, and the EventArgs object when triggered from an event */ -$.fn.liveAndTestAtStart = function( callback ){ +$.fn.liveAndTestAtStart = function ( callback ){ $(this) .live( 'change', callback ) - .each( function( index, element ){ + .each( function ( index, element ){ callback.call( this, true ); } ); }; // Document ready: -$( function() { +$( function () { // Animate the SelectOrOther fields, to only show the text field when // 'other' is selected. - $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function( instant ) { + $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) { var $other = $( '#' + $(this).attr( 'id' ) + '-other' ); $other = $other.add( $other.siblings( 'br' ) ); if ( $(this).val() === 'other' ) { @@ -61,4 +61,4 @@ $( function() { }); -})( jQuery ); +}( jQuery ) ); diff --git a/resources/mediawiki/mediawiki.jqueryMsg.js b/resources/mediawiki/mediawiki.jqueryMsg.js index 6c00bd15..86af31ff 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/mediawiki/mediawiki.jqueryMsg.js @@ -1,22 +1,27 @@ /** - * Experimental advanced wikitext parser-emitter. - * See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs - * - * @author neilk@wikimedia.org - */ - -( function( mw, $, undefined ) { - - mw.jqueryMsg = {}; +* Experimental advanced wikitext parser-emitter. +* See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs +* +* @author neilk@wikimedia.org +*/ +( function ( mw, $ ) { + var slice = Array.prototype.slice, + parserDefaults = { + magic : { + 'SITENAME' : mw.config.get( 'wgSiteName' ) + }, + messages : mw.messages, + language : mw.language + }; /** * Given parser options, return a function that parses a key and replacements, returning jQuery object * @param {Object} parser options * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery} */ - function getFailableParserFn( options ) { - var parser = new mw.jqueryMsg.parser( options ); - /** + function getFailableParserFn( options ) { + var parser = new mw.jqueryMsg.parser( options ); + /** * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes. * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it. @@ -24,24 +29,23 @@ * @param {Array} first element is the key, replacements may be in array in 2nd element, or remaining elements. * @return {jQuery} */ - return function( args ) { + return function ( args ) { var key = args[0]; - var argsArray = $.isArray( args[1] ) ? args[1] : $.makeArray( args ).slice( 1 ); - var escapedArgsArray = $.map( argsArray, function( arg ) { - return typeof arg === 'string' ? mw.html.escape( arg ) : arg; - } ); + var argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 ); try { - return parser.parse( key, escapedArgsArray ); + return parser.parse( key, argsArray ); } catch ( e ) { - return $( '<span></span>' ).append( key + ': ' + e.message ); + return $( '<span>' ).append( key + ': ' + e.message ); } }; } + mw.jqueryMsg = {}; + /** - * Class method. + * Class method. * Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements). - * e.g. + * e.g. * window.gM = mediaWiki.parser.getMessageFunction( options ); * $( 'p#headline' ).html( gM( 'hello-user', username ) ); * @@ -51,75 +55,68 @@ * @param {Array} parser options * @return {Function} function suitable for assigning to window.gM */ - mw.jqueryMsg.getMessageFunction = function( options ) { + mw.jqueryMsg.getMessageFunction = function ( options ) { var failableParserFn = getFailableParserFn( options ); - /** + /** * N.B. replacements are variadic arguments or an array in second parameter. In other words: - * somefunction(a, b, c, d) - * is equivalent to + * somefunction(a, b, c, d) + * is equivalent to * somefunction(a, [b, c, d]) * * @param {String} message key * @param {Array} optional replacements (can also specify variadically) * @return {String} rendered HTML as string */ - return function( /* key, replacements */ ) { + return function ( /* key, replacements */ ) { return failableParserFn( arguments ).html(); }; }; /** - * Class method. - * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to - * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links. - * e.g. + * Class method. + * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to + * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links. + * e.g. * $.fn.msg = mediaWiki.parser.getJqueryPlugin( options ); - * var userlink = $( '<a>' ).click( function() { alert( "hello!!") } ); + * var userlink = $( '<a>' ).click( function () { alert( "hello!!") } ); * $( 'p#headline' ).msg( 'hello-user', userlink ); * * @param {Array} parser options * @return {Function} function suitable for assigning to jQuery plugin, such as $.fn.msg */ - mw.jqueryMsg.getPlugin = function( options ) { + mw.jqueryMsg.getPlugin = function ( options ) { var failableParserFn = getFailableParserFn( options ); - /** + /** * N.B. replacements are variadic arguments or an array in second parameter. In other words: - * somefunction(a, b, c, d) - * is equivalent to + * somefunction(a, b, c, d) + * is equivalent to * somefunction(a, [b, c, d]) - * + * * We append to 'this', which in a jQuery plugin context will be the selected elements. * @param {String} message key * @param {Array} optional replacements (can also specify variadically) * @return {jQuery} this */ - return function( /* key, replacements */ ) { + return function ( /* key, replacements */ ) { var $target = this.empty(); - $.each( failableParserFn( arguments ).contents(), function( i, node ) { + $.each( failableParserFn( arguments ).contents(), function ( i, node ) { $target.append( node ); } ); return $target; }; }; - var parserDefaults = { - 'magic' : {}, - 'messages' : mw.messages, - 'language' : mw.language - }; - /** * The parser itself. * Describes an object, whose primary duty is to .parse() message keys. * @param {Array} options */ - mw.jqueryMsg.parser = function( options ) { + mw.jqueryMsg.parser = function ( options ) { this.settings = $.extend( {}, parserDefaults, options ); this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic ); }; mw.jqueryMsg.parser.prototype = { - // cache, map of mediaWiki message key to the AST of the message. In most cases, the message is a string so this is identical. // (This is why we would like to move this functionality server-side). astCache: {}, @@ -132,51 +129,46 @@ * @param {Array} replacements for $1, $2... $n * @return {jQuery} */ - parse: function( key, replacements ) { + parse: function ( key, replacements ) { return this.emitter.emit( this.getAst( key ), replacements ); }, - /** - * Fetch the message string associated with a key, return parsed structure. Memoized. - * Note that we pass '[' + key + ']' back for a missing message here. - * @param {String} key + * Fetch the message string associated with a key, return parsed structure. Memoized. + * Note that we pass '[' + key + ']' back for a missing message here. + * @param {String} key * @return {String|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing */ - getAst: function( key ) { - if ( this.astCache[ key ] === undefined ) { + getAst: function ( key ) { + if ( this.astCache[ key ] === undefined ) { var wikiText = this.settings.messages.get( key ); if ( typeof wikiText !== 'string' ) { wikiText = "\\[" + key + "\\]"; } this.astCache[ key ] = this.wikiTextToAst( wikiText ); } - return this.astCache[ key ]; + return this.astCache[ key ]; }, - /* * Parses the input wikiText into an abstract syntax tree, essentially an s-expression. * * CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already. * n.b. We want to move this functionality to the server. Nothing here is required to be on the client. - * + * * @param {String} message string wikitext * @throws Error * @return {Mixed} abstract syntax tree */ - wikiTextToAst: function( input ) { - - // Indicates current position in input as we parse through it. - // Shared among all parsing functions below. - var pos = 0; + wikiTextToAst: function ( input ) { + // Indicates current position in input as we parse through it. + // Shared among all parsing functions below. + var pos = 0; // ========================================================= // parsing combinators - could be a library on its own // ========================================================= - - - // Try parsers until one works, if none work return null + // Try parsers until one works, if none work return null function choice( ps ) { - return function() { + return function () { for ( var i = 0; i < ps.length; i++ ) { var result = ps[i](); if ( result !== null ) { @@ -186,27 +178,25 @@ return null; }; } - // try several ps in a row, all must succeed or return null // this is the only eager one function sequence( ps ) { var originalPos = pos; var result = []; - for ( var i = 0; i < ps.length; i++ ) { + for ( var i = 0; i < ps.length; i++ ) { var res = ps[i](); if ( res === null ) { pos = originalPos; return null; - } + } result.push( res ); } return result; } - // run the same parser over and over until it fails. // must succeed a minimum of n times or return null function nOrMore( n, p ) { - return function() { + return function () { var originalPos = pos; var result = []; var parsed = p(); @@ -217,26 +207,23 @@ if ( result.length < n ) { pos = originalPos; return null; - } + } return result; }; } - // There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null. // But using this as a combinator seems to cause problems when combined with nOrMore(). // May be some scoping issue function transform( p, fn ) { - return function() { + return function () { var result = p(); return result === null ? null : fn( result ); }; } - // Helpers -- just make ps out of simpler JS builtin types - - function makeStringParser( s ) { + function makeStringParser( s ) { var len = s.length; - return function() { + return function () { var result = null; if ( input.substr( pos, len ) === s ) { result = s; @@ -245,105 +232,87 @@ return result; }; } - function makeRegexParser( regex ) { - return function() { + return function () { var matches = input.substr( pos ).match( regex ); - if ( matches === null ) { + if ( matches === null ) { return null; - } + } pos += matches[0].length; return matches[0]; }; } - - /** - * =================================================================== + /** + * =================================================================== * General patterns above this line -- wikitext specific parsers below - * =================================================================== + * =================================================================== */ - // Parsing functions follow. All parsing functions work like this: // They don't accept any arguments. // Instead, they just operate non destructively on the string 'input' // As they can consume parts of the string, they advance the shared variable pos, // and return tokens (or whatever else they want to return). - // some things are defined as closures and other things as ordinary functions // converting everything to a closure makes it a lot harder to debug... errors pop up // but some debuggers can't tell you exactly where they come from. Also the mutually // recursive functions seem not to work in all browsers then. (Tested IE6-7, Opera, Safari, FF) // This may be because, to save code, memoization was removed - - - var regularLiteral = makeRegexParser( /^[^{}[\]$\\]/ ); - var regularLiteralWithoutBar = makeRegexParser(/^[^{}[\]$\\|]/); - var regularLiteralWithoutSpace = makeRegexParser(/^[^{}[\]$\s]/); - + var regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ ); + var regularLiteralWithoutBar = makeRegexParser(/^[^{}\[\]$\\|]/); + var regularLiteralWithoutSpace = makeRegexParser(/^[^{}\[\]$\s]/); var backslash = makeStringParser( "\\" ); var anyCharacter = makeRegexParser( /^./ ); - function escapedLiteral() { var result = sequence( [ - backslash, + backslash, anyCharacter ] ); return result === null ? null : result[1]; } - var escapedOrLiteralWithoutSpace = choice( [ escapedLiteral, regularLiteralWithoutSpace ] ); - var escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] ); - - var escapedOrRegularLiteral = choice( [ + var escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] ); - // Used to define "literals" without spaces, in space-delimited situations function literalWithoutSpace() { var result = nOrMore( 1, escapedOrLiteralWithoutSpace )(); return result === null ? null : result.join(''); } - - // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default + // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default // it is not a literal in the parameter function literalWithoutBar() { var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); return result === null ? null : result.join(''); } - function literal() { var result = nOrMore( 1, escapedOrRegularLiteral )(); return result === null ? null : result.join(''); } - - var whitespace = makeRegexParser( /^\s+/ ); + var whitespace = makeRegexParser( /^\s+/ ); var dollar = makeStringParser( '$' ); - var digits = makeRegexParser( /^\d+/ ); + var digits = makeRegexParser( /^\d+/ ); function replacement() { var result = sequence( [ dollar, digits ] ); - if ( result === null ) { + if ( result === null ) { return null; } return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ]; } - - var openExtlink = makeStringParser( '[' ); var closeExtlink = makeStringParser( ']' ); - // this extlink MUST have inner text, e.g. [foo] not allowed; [foo bar] is allowed function extlink() { var result = null; @@ -359,10 +328,23 @@ } return result; } - + // this is the same as the above extlink, except that the url is being passed on as a parameter + function extLinkParam() { + var result = sequence( [ + openExtlink, + dollar, + digits, + whitespace, + expression, + closeExtlink + ] ); + if ( result === null ) { + return null; + } + return [ 'LINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ]; + } var openLink = makeStringParser( '[[' ); var closeLink = makeStringParser( ']]' ); - function link() { var result = null; var parsedResult = sequence( [ @@ -375,16 +357,14 @@ } return result; } - - var templateName = transform( + var templateName = transform( // see $wgLegalTitleChars // not allowing : due to the need to catch "PLURAL:$1" - makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+-]+/ ), - function( result ) { return result.toString(); } + makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ), + function ( result ) { return result.toString(); } ); - function templateParam() { - var result = sequence( [ + var result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] ); @@ -395,9 +375,7 @@ // use a "CONCAT" operator if there are multiple nodes, otherwise return the first node, raw. return expr.length > 1 ? [ "CONCAT" ].concat( expr ) : expr[0]; } - var pipe = makeStringParser( '|' ); - function templateWithReplacement() { var result = sequence( [ templateName, @@ -406,21 +384,29 @@ ] ); return result === null ? null : [ result[0], result[2] ]; } - + function templateWithOutReplacement() { + var result = sequence( [ + templateName, + colon, + paramExpression + ] ); + return result === null ? null : [ result[0], result[2] ]; + } var colon = makeStringParser(':'); - var templateContents = choice( [ - function() { + function () { var res = sequence( [ - templateWithReplacement, + // templates can have placeholders for dynamic replacement eg: {{PLURAL:$1|one car|$1 cars}} + // or no placeholders eg: {{GRAMMAR:genitive|{{SITENAME}}} + choice( [ templateWithReplacement, templateWithOutReplacement ] ), nOrMore( 0, templateParam ) ] ); return res === null ? null : res[0].concat( res[1] ); }, - function() { + function () { var res = sequence( [ templateName, - nOrMore( 0, templateParam ) + nOrMore( 0, templateParam ) ] ); if ( res === null ) { return null; @@ -428,10 +414,8 @@ return [ res[0] ].concat( res[1] ); } ] ); - var openTemplate = makeStringParser('{{'); var closeTemplate = makeStringParser('}}'); - function template() { var result = sequence( [ openTemplate, @@ -440,31 +424,30 @@ ] ); return result === null ? null : result[1]; } - var nonWhitespaceExpression = choice( [ - template, + template, link, + extLinkParam, extlink, replacement, literalWithoutSpace ] ); - var paramExpression = choice( [ - template, + template, link, + extLinkParam, extlink, replacement, literalWithoutBar ] ); - - var expression = choice( [ + var expression = choice( [ template, link, + extLinkParam, extlink, replacement, - literal + literal ] ); - function start() { var result = nOrMore( 0, expression )(); if ( result === null ) { @@ -472,16 +455,13 @@ } return [ "CONCAT" ].concat( result ); } - // everything above this point is supposed to be stateless/static, but // I am deferring the work of turning it into prototypes & objects. It's quite fast enough - // finally let's do some actual work... - var result = start(); - + /* - * For success, the p must have gotten to the end of the input + * For success, the p must have gotten to the end of the input * and returned a non-null. * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. */ @@ -490,20 +470,19 @@ } return result; } - - }; + }; /** * htmlEmitter - object which primarily exists to emit HTML from parser ASTs */ - mw.jqueryMsg.htmlEmitter = function( language, magic ) { + mw.jqueryMsg.htmlEmitter = function ( language, magic ) { this.language = language; - var _this = this; - - $.each( magic, function( key, val ) { - _this[ key.toLowerCase() ] = function() { return val; }; + var jmsg = this; + $.each( magic, function ( key, val ) { + jmsg[ key.toLowerCase() ] = function () { + return val; + }; } ); - /** * (We put this method definition here, and not in prototype, to make sure it's not overwritten by any magic.) * Walk entire node structure, applying replacements and template functions when appropriate @@ -511,23 +490,23 @@ * @param {Array} replacements for $1, $2, ... $n * @return {Mixed} single-string node or array of nodes suitable for jQuery appending */ - this.emit = function( node, replacements ) { + this.emit = function ( node, replacements ) { var ret = null; - var _this = this; - switch( typeof node ) { + var jmsg = this; + switch ( typeof node ) { case 'string': case 'number': ret = node; break; case 'object': // node is an array of nodes - var subnodes = $.map( node.slice( 1 ), function( n ) { - return _this.emit( n, replacements ); + var subnodes = $.map( node.slice( 1 ), function ( n ) { + return jmsg.emit( n, replacements ); } ); var operation = node[0].toLowerCase(); - if ( typeof _this[operation] === 'function' ) { - ret = _this[ operation ]( subnodes, replacements ); + if ( typeof jmsg[operation] === 'function' ) { + ret = jmsg[ operation ]( subnodes, replacements ); } else { - throw new Error( 'unknown operation "' + operation + '"' ); + throw new Error( 'Unknown operation "' + operation + '"' ); } break; case 'undefined': @@ -537,21 +516,18 @@ ret = ''; break; default: - throw new Error( 'unexpected type in AST: ' + typeof node ); + throw new Error( 'Unexpected type in AST: ' + typeof node ); } return ret; }; - }; - // For everything in input that follows double-open-curly braces, there should be an equivalent parser - // function. For instance {{PLURAL ... }} will be processed by 'plural'. + // function. For instance {{PLURAL ... }} will be processed by 'plural'. // If you have 'magic words' then configure the parser to have them upon creation. // // An emitter method takes the parent node, the array of subnodes and the array of replacements (the values that $1, $2... should translate to). // Note: all such functions must be pure, with the exception of referring to other pure functions via this.language (convertPlural and so on) mw.jqueryMsg.htmlEmitter.prototype = { - /** * Parsing has been applied depth-first we can assume that all nodes here are single nodes * Must return a single node to parents -- a jQuery with synthetic span @@ -559,23 +535,23 @@ * @param {Array} nodes - mixed, some single nodes, some arrays of nodes * @return {jQuery} */ - concat: function( nodes ) { - var span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' ); - $.each( nodes, function( i, node ) { + concat: function ( nodes ) { + var $span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' ); + $.each( nodes, function ( i, node ) { if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) { - $.each( node.contents(), function( j, childNode ) { - span.append( childNode ); + $.each( node.contents(), function ( j, childNode ) { + $span.append( childNode ); } ); } else { // strings, integers, anything else - span.append( node ); + $span.append( node ); } } ); - return span; + return $span; }, /** - * Return replacement of correct index, or string if unavailable. + * Return escaped replacement of correct index, or string if unavailable. * Note that we expect the parsed parameter to be zero-based. i.e. $1 should have become [ 0 ]. * if the specified parameter is not found return the same string * (e.g. "$99" -> parameter 98 -> not found -> return "$99" ) @@ -583,17 +559,29 @@ * @param {Array} of one element, integer, n >= 0 * @return {String} replacement */ - replace: function( nodes, replacements ) { + replace: function ( nodes, replacements ) { var index = parseInt( nodes[0], 10 ); - return index < replacements.length ? replacements[index] : '$' + ( index + 1 ); + + if ( index < replacements.length ) { + if ( typeof arg === 'string' ) { + // replacement is a string, escape it + return mw.html.escape( replacements[index] ); + } else { + // replacement is no string, don't touch! + return replacements[index]; + } + } else { + // index not found, fallback to displaying variable + return '$' + ( index + 1 ); + } }, - /** + /** * Transform wiki-link - * TODO unimplemented + * TODO unimplemented */ - wlink: function( nodes ) { - return "unimplemented"; + wlink: function ( nodes ) { + return 'unimplemented'; }, /** @@ -601,14 +589,14 @@ * If the href is a jQuery object, treat it as "enclosing" the link text. * ... function, treat it as the click handler * ... string, treat it as a URI - * TODO: throw an error if nodes.length > 2 ? + * TODO: throw an error if nodes.length > 2 ? * @param {Array} of two elements, {jQuery|Function|String} and {String} * @return {jQuery} */ - link: function( nodes ) { + link: function ( nodes ) { var arg = nodes[0]; var contents = nodes[1]; - var $el; + var $el; if ( arg instanceof jQuery ) { $el = arg; } else { @@ -619,19 +607,39 @@ $el.attr( 'href', arg.toString() ); } } - $el.append( contents ); + $el.append( contents ); return $el; }, /** + * This is basically use a combination of replace + link (link with parameter + * as url), but we don't want to run the regular replace here-on: inserting a + * url as href-attribute of a link will automatically escape it already, so + * we don't want replace to (manually) escape it as well. + * TODO throw error if nodes.length > 1 ? + * @param {Array} of one element, integer, n >= 0 + * @return {String} replacement + */ + linkparam: function ( nodes, replacements ) { + var replacement, + index = parseInt( nodes[0], 10 ); + if ( index < replacements.length) { + replacement = replacements[index]; + } else { + replacement = '$' + ( index + 1 ); + } + return this.link( [ replacement, nodes[1] ] ); + }, + + /** * Transform parsed structure into pluralization * n.b. The first node may be a non-integer (for instance, a string representing an Arabic number). * So convert it back with the current language's convertNumber. - * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ] + * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ] * @return {String} selected pluralized form according to current language */ - plural: function( nodes ) { - var count = parseInt( this.language.convertNumber( nodes[0], true ), 10 ); + plural: function ( nodes ) { + var count = parseFloat( this.language.convertNumber( nodes[0], true ) ); var forms = nodes.slice(1); return forms.length ? this.language.convertPlural( count, forms ) : ''; }, @@ -639,10 +647,10 @@ /** * Transform parsed structure into gender * Usage {{gender:[gender| mw.user object ] | masculine|feminine|neutral}}. - * @param {Array} of nodes, [ {String|mw.User}, {String}, {String} , {String} ] + * @param {Array} of nodes, [ {String|mw.User}, {String}, {String} , {String} ] * @return {String} selected gender form according to current language */ - gender: function( nodes ) { + gender: function ( nodes ) { var gender; if ( nodes[0] && nodes[0].options instanceof mw.Map ){ gender = nodes[0].options.get( 'gender' ); @@ -651,35 +659,40 @@ } var forms = nodes.slice(1); return this.language.gender( gender, forms ); - } + }, + /** + * Transform parsed structure into grammar conversion. + * Invoked by putting {{grammar:form|word}} in a message + * @param {Array} of nodes [{Grammar case eg: genitive}, {String word}] + * @return {String} selected grammatical form according to current language + */ + grammar: function ( nodes ) { + var form = nodes[0]; + var word = nodes[1]; + return word && form && this.language.convertGrammar( word, form ); + } }; - - // TODO figure out a way to make magic work with common globals like wgSiteName, without requiring init from library users... - // var options = { magic: { 'SITENAME' : mw.config.get( 'wgSiteName' ) } }; - - // deprecated! don't rely on gM existing. - // the window.gM ought not to be required - or if required, not required here. But moving it to extensions breaks it (?!) + // Deprecated! don't rely on gM existing. + // The window.gM ought not to be required - or if required, not required here. + // But moving it to extensions breaks it (?!) // Need to fix plugin so it could do attributes as well, then will be okay to remove this. - window.gM = mw.jqueryMsg.getMessageFunction(); - + window.gM = mw.jqueryMsg.getMessageFunction(); $.fn.msg = mw.jqueryMsg.getPlugin(); - + // Replace the default message parser with jqueryMsg var oldParser = mw.Message.prototype.parser; - mw.Message.prototype.parser = function() { + mw.Message.prototype.parser = function () { // TODO: should we cache the message function so we don't create a new one every time? Benchmark this maybe? // Caching is somewhat problematic, because we do need different message functions for different maps, so // we'd have to cache the parser as a member of this.map, which sounds a bit ugly. - // Do not use mw.jqueryMsg unless required if ( this.map.get( this.key ).indexOf( '{{' ) < 0 ) { // Fall back to mw.msg's simple parser return oldParser.apply( this ); } - var messageFunction = mw.jqueryMsg.getMessageFunction( { 'messages': this.map } ); return messageFunction( this.key, this.parameters ); }; -} )( mediaWiki, jQuery ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.jqueryMsg.peg b/resources/mediawiki/mediawiki.jqueryMsg.peg index 74c57e4b..e059ed1d 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.peg +++ b/resources/mediawiki/mediawiki.jqueryMsg.peg @@ -22,11 +22,15 @@ template templateContents = twr:templateWithReplacement p:templateParam* { return twr.concat(p) } + / twr:templateWithOutReplacement p:templateParam* { return twr.concat(p) } / t:templateName p:templateParam* { return p.length ? [ t, p ] : [ t ] } templateWithReplacement = t:templateName ":" r:replacement { return [ t, r ] } +templateWithOutReplacement + = t:templateName ":" p:paramExpression { return [ t, p ] } + templateParam = "|" e:paramExpression* { return e.length > 1 ? [ "CONCAT" ].concat(e) : e[0]; } diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index 121d5399..1a72ed13 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -3,11 +3,13 @@ */ var mw = ( function ( $, undefined ) { -"use strict"; + "use strict"; /* Private Members */ - var hasOwn = Object.prototype.hasOwnProperty; + var hasOwn = Object.prototype.hasOwnProperty, + slice = Array.prototype.slice; + /* Object constructors */ /** @@ -43,13 +45,15 @@ var mw = ( function ( $, undefined ) { var results, i; if ( $.isArray( selection ) ) { - selection = $.makeArray( selection ); + selection = slice.call( selection ); results = {}; for ( i = 0; i < selection.length; i += 1 ) { results[selection[i]] = this.get( selection[i], fallback ); } return results; - } else if ( typeof selection === 'string' ) { + } + + if ( typeof selection === 'string' ) { if ( this.values[selection] === undefined ) { if ( fallback !== undefined ) { return fallback; @@ -58,11 +62,13 @@ var mw = ( function ( $, undefined ) { } return this.values[selection]; } + if ( selection === undefined ) { return this.values; - } else { - return null; // invalid selection key } + + // invalid selection key + return null; }, /** @@ -80,7 +86,8 @@ var mw = ( function ( $, undefined ) { this.values[s] = selection[s]; } return true; - } else if ( typeof selection === 'string' && value !== undefined ) { + } + if ( typeof selection === 'string' && value !== undefined ) { this.values[selection] = value; return true; } @@ -103,9 +110,8 @@ var mw = ( function ( $, undefined ) { } } return true; - } else { - return this.values[selection] !== undefined; } + return this.values[selection] !== undefined; } }; @@ -124,7 +130,7 @@ var mw = ( function ( $, undefined ) { this.format = 'plain'; this.map = map; this.key = key; - this.parameters = parameters === undefined ? [] : $.makeArray( parameters ); + this.parameters = parameters === undefined ? [] : slice.call( parameters ); return this; } @@ -132,17 +138,17 @@ var mw = ( function ( $, undefined ) { /** * Simple message parser, does $N replacement and nothing else. * This may be overridden to provide a more complex message parser. - * + * * This function will not be called for nonexistent messages. */ - parser: function() { + parser: function () { var parameters = this.parameters; return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) { var index = parseInt( match, 10 ) - 1; return parameters[index] !== undefined ? parameters[index] : '$' + match; } ); }, - + /** * Appends (does not replace) parameters for replacement to the .parameters property. * @@ -162,7 +168,7 @@ var mw = ( function ( $, undefined ) { * * @return string Message as a string in the current form or <key> if key does not exist. */ - toString: function() { + toString: function () { var text; if ( !this.exists() ) { @@ -186,7 +192,7 @@ var mw = ( function ( $, undefined ) { text = this.parser(); text = mw.html.escape( text ); } - + if ( this.format === 'parse' ) { text = this.parser(); } @@ -199,7 +205,7 @@ var mw = ( function ( $, undefined ) { * * @return {string} String form of parsed message */ - parse: function() { + parse: function () { this.format = 'parse'; return this.toString(); }, @@ -209,7 +215,7 @@ var mw = ( function ( $, undefined ) { * * @return {string} String form of plain message */ - plain: function() { + plain: function () { this.format = 'plain'; return this.toString(); }, @@ -219,7 +225,7 @@ var mw = ( function ( $, undefined ) { * * @return {string} String form of html escaped message */ - escaped: function() { + escaped: function () { this.format = 'escaped'; return this.toString(); }, @@ -229,7 +235,7 @@ var mw = ( function ( $, undefined ) { * * @return {string} String form of parsed message */ - exists: function() { + exists: function () { return this.map.exists( this.key ); } }; @@ -241,8 +247,8 @@ var mw = ( function ( $, undefined ) { * Dummy function which in debug mode can be replaced with a function that * emulates console.log in console-less environments. */ - log: function() { }, - + log: function () { }, + /** * @var constructor Make the Map constructor publicly available. */ @@ -252,7 +258,7 @@ var mw = ( function ( $, undefined ) { * @var constructor Make the Message constructor publicly available. */ Message: Message, - + /** * List of configuration values * @@ -261,25 +267,25 @@ var mw = ( function ( $, undefined ) { * in the global window object. */ config: null, - + /** * @var object * * Empty object that plugins can be installed in. */ libs: {}, - + /* Extension points */ - + legacy: {}, - + /** * Localization system */ messages: new Map(), - + /* Public Methods */ - + /** * Gets a message object, similar to wfMessage() * @@ -292,33 +298,33 @@ var mw = ( function ( $, undefined ) { var parameters; // Support variadic arguments if ( parameter_1 !== undefined ) { - parameters = $.makeArray( arguments ); + parameters = slice.call( arguments ); parameters.shift(); } else { parameters = []; } return new Message( mw.messages, key, parameters ); }, - + /** - * Gets a message string, similar to wfMsg() + * Gets a message string, similar to wfMessage() * * @param key string Key of message to get * @param parameters mixed First argument in a list of variadic arguments, * each a parameter for $N replacement in messages. * @return String. */ - msg: function ( key, parameters ) { + msg: function ( /* key, parameter_1, parameter_2, .. */ ) { return mw.message.apply( mw.message, arguments ).toString(); }, - + /** * Client-side module loader which integrates with the MediaWiki ResourceLoader */ - loader: ( function() { - + loader: ( function () { + /* Private Members */ - + /** * Mapping of registered modules * @@ -335,7 +341,7 @@ var mw = ( function ( $, undefined ) { * { * 'moduleName': { * 'version': ############## (unix timestamp), - * 'dependencies': ['required.foo', 'bar.also', ...], (or) function() {} + * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {} * 'group': 'somegroup', (or) null, * 'source': 'local', 'someforeignwiki', (or) null * 'state': 'registered', 'loading', 'loaded', 'ready', 'error' or 'missing' @@ -345,7 +351,7 @@ var mw = ( function ( $, undefined ) { * } * } */ - var registry = {}, + var registry = {}, /** * Mapping of sources, keyed by source-id, values are objects. * Format: @@ -362,68 +368,111 @@ var mw = ( function ( $, undefined ) { queue = [], // List of callback functions waiting for modules to be ready to be called jobs = [], - // Flag indicating that document ready has occured - ready = false, // Selector cache for the marker element. Use getMarker() to get/use the marker! $marker = null; - - /* Cache document ready status */ - - $(document).ready( function () { - ready = true; - } ); - + /* Private methods */ - + function getMarker() { // Cached ? if ( $marker ) { return $marker; - } else { - $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' ); - if ( $marker.length ) { - return $marker; - } - mw.log( 'getMarker> No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically.' ); - $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' ); + } + + $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' ); + if ( $marker.length ) { return $marker; } + mw.log( 'getMarker> No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically.' ); + $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' ); + + return $marker; + } + + /** + * Create a new style tag and add it to the DOM. + * + * @param text String: CSS text + * @param nextnode mixed: [optional] An Element or jQuery object for an element where + * the style tag should be inserted before. Otherwise appended to the <head>. + * @return HTMLStyleElement + */ + function addStyleTag( text, nextnode ) { + var s = document.createElement( 'style' ); + // Insert into document before setting cssText (bug 33305) + if ( nextnode ) { + // Must be inserted with native insertBefore, not $.fn.before. + // When using jQuery to insert it, like $nextnode.before( s ), + // then IE6 will throw "Access is denied" when trying to append + // to .cssText later. Some kind of weird security measure. + // http://stackoverflow.com/q/12586482/319266 + // Works: jsfiddle.net/zJzMy/1 + // Fails: jsfiddle.net/uJTQz + // Works again: http://jsfiddle.net/Azr4w/ (diff: the next 3 lines) + if ( nextnode.jquery ) { + nextnode = nextnode.get( 0 ); + } + nextnode.parentNode.insertBefore( s, nextnode ); + } else { + document.getElementsByTagName( 'head' )[0].appendChild( s ); + } + if ( s.styleSheet ) { + // IE + s.styleSheet.cssText = text; + } else { + // Other browsers. + // (Safari sometimes borks on non-string values, + // play safe by casting to a string, just in case.) + s.appendChild( document.createTextNode( String( text ) ) ); + } + return s; + } + + /** + * Checks if certain cssText is safe to append to + * a stylesheet. + * + * Right now it only makes sure that cssText containing @import + * rules will end up in a new stylesheet (as those only work when + * placed at the start of a stylesheet; bug 35562). + * This could later be extended to take care of other bugs, such as + * the IE cssRules limit - not the same as the IE styleSheets limit). + */ + function canExpandStylesheetWith( $style, cssText ) { + return cssText.indexOf( '@import' ) === -1; } - - function addInlineCSS( css, media ) { - var $style = getMarker().prev(), - $newStyle, - attrs = { 'type': 'text/css', 'media': media }; - if ( $style.is( 'style' ) && $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) { - // There's already a dynamic <style> tag present, append to it - // This recycling of <style> tags is for bug 31676 (can't have - // more than 32 <style> tags in IE) - - // Also, calling .append() on a <style> tag explodes with a JS error in IE, - // so if the .append() fails we fall back to building a new <style> tag and - // replacing the existing one - try { - // Do cdata sanitization on the provided CSS, and prepend a double newline - css = $( mw.html.element( 'style', {}, new mw.html.Cdata( "\n\n" + css ) ) ).html(); - $style.append( css ); - } catch ( e ) { - // Generate a new tag with the combined CSS - css = $style.html() + "\n\n" + css; - $newStyle = $( mw.html.element( 'style', attrs, new mw.html.Cdata( css ) ) ) - .data( 'ResourceLoaderDynamicStyleTag', true ); - // Prevent a flash of unstyled content by inserting the new tag - // before removing the old one - $style.after( $newStyle ); - $style.remove(); + + function addEmbeddedCSS( cssText ) { + var $style, styleEl; + $style = getMarker().prev(); + // Re-use <style> tags if possible, this to try to stay + // under the IE stylesheet limit (bug 31676). + // Also verify that the the element before Marker actually is one + // that came from ResourceLoader, and not a style tag that some + // other script inserted before our marker, or, more importantly, + // it may not be a style tag at all (could be <meta> or <script>). + if ( + $style.data( 'ResourceLoaderDynamicStyleTag' ) === true && + canExpandStylesheetWith( $style, cssText ) + ) { + // There's already a dynamic <style> tag present and + // canExpandStylesheetWith() gave a green light to append more to it. + styleEl = $style.get( 0 ); + if ( styleEl.styleSheet ) { + try { + styleEl.styleSheet.cssText += cssText; // IE + } catch ( e ) { + log( 'addEmbeddedCSS fail\ne.message: ' + e.message, e ); + } + } else { + styleEl.appendChild( document.createTextNode( String( cssText ) ) ); } } else { - // Create a new <style> tag and insert it - $style = $( mw.html.element( 'style', attrs, new mw.html.Cdata( css ) ) ); - $style.data( 'ResourceLoaderDynamicStyleTag', true ); - getMarker().before( $style ); + $( addStyleTag( cssText, getMarker() ) ) + .data( 'ResourceLoaderDynamicStyleTag', true ); } } - + function compare( a, b ) { var i; if ( a.length !== b.length ) { @@ -441,7 +490,7 @@ var mw = ( function ( $, undefined ) { } return true; } - + /** * Generates an ISO8601 "basic" string from a UNIX timestamp */ @@ -456,13 +505,23 @@ var mw = ( function ( $, undefined ) { pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z' ].join( '' ); } - + /** - * Recursively resolves dependencies and detects circular references + * Resolves dependencies and detects circular references. + * + * @param module String Name of the top-level module whose dependencies shall be + * resolved and sorted. + * @param resolved Array Returns a topological sort of the given module and its + * dependencies, such that later modules depend on earlier modules. The array + * contains the module names. If the array contains already some module names, + * this function appends its result to the pre-existing array. + * @param unresolved Object [optional] Hash used to track the current dependency + * chain; used to report loops in the dependency graph. + * @throws Error if any unregistered module or a dependency loop is encountered */ - function recurse( module, resolved, unresolved ) { + function sortDependencies( module, resolved, unresolved ) { var n, deps, len; - + if ( registry[module] === undefined ) { throw new Error( 'Unknown dependency: ' + module ); } @@ -474,12 +533,20 @@ var mw = ( function ( $, undefined ) { registry[module].dependencies = [registry[module].dependencies]; } } + if ( $.inArray( module, resolved ) !== -1 ) { + // Module already resolved; nothing to do. + return; + } + // unresolved is optional, supply it if not passed in + if ( !unresolved ) { + unresolved = {}; + } // Tracks down dependencies deps = registry[module].dependencies; len = deps.length; for ( n = 0; n < len; n += 1 ) { if ( $.inArray( deps[n], resolved ) === -1 ) { - if ( $.inArray( deps[n], unresolved ) !== -1 ) { + if ( unresolved[deps[n]] ) { throw new Error( 'Circular reference detected: ' + module + ' -> ' + deps[n] @@ -487,43 +554,43 @@ var mw = ( function ( $, undefined ) { } // Add to unresolved - unresolved[unresolved.length] = module; - recurse( deps[n], resolved, unresolved ); - // module is at the end of unresolved - unresolved.pop(); + unresolved[module] = true; + sortDependencies( deps[n], resolved, unresolved ); + delete unresolved[module]; } } resolved[resolved.length] = module; } - + /** - * Gets a list of module names that a module depends on in their proper dependency order + * Gets a list of module names that a module depends on in their proper dependency + * order. * * @param module string module name or array of string module names * @return list of dependencies, including 'module'. * @throws Error if circular reference is detected */ function resolve( module ) { - var modules, m, deps, n, resolved; - + var m, resolved; + // Allow calling with an array of module names if ( $.isArray( module ) ) { - modules = []; + resolved = []; for ( m = 0; m < module.length; m += 1 ) { - deps = resolve( module[m] ); - for ( n = 0; n < deps.length; n += 1 ) { - modules[modules.length] = deps[n]; - } + sortDependencies( module[m], resolved ); } - return modules; - } else if ( typeof module === 'string' ) { + return resolved; + } + + if ( typeof module === 'string' ) { resolved = []; - recurse( module, resolved, [] ); + sortDependencies( module, resolved ); return resolved; } + throw new Error( 'Invalid module argument: ' + module ); } - + /** * Narrows a list of module names down to those matching a specific * state (see comment on top of this scope for a list of valid states). @@ -537,7 +604,7 @@ var mw = ( function ( $, undefined ) { */ function filter( states, modules ) { var list, module, s, m; - + // Allow states to be given as a string if ( typeof states === 'string' ) { states = [states]; @@ -570,66 +637,127 @@ var mw = ( function ( $, undefined ) { } return list; } - + + /** + * Determine whether all dependencies are in state 'ready', which means we may + * execute the module or job now. + * + * @param dependencies Array dependencies (module names) to be checked. + * + * @return Boolean true if all dependencies are in state 'ready', false otherwise + */ + function allReady( dependencies ) { + return filter( 'ready', dependencies ).length === dependencies.length; + } + /** - * Automatically executes jobs and modules which are pending with satistifed dependencies. + * Log a message to window.console, if possible. Useful to force logging of some + * errors that are otherwise hard to detect (I.e., this logs also in production mode). + * Gets console references in each invocation, so that delayed debugging tools work + * fine. No need for optimization here, which would only result in losing logs. * - * This is used when dependencies are satisfied, such as when a module is executed. + * @param msg String text for the log entry. + * @param e Error [optional] to also log. + */ + function log( msg, e ) { + var console = window.console; + if ( console && console.log ) { + console.log( msg ); + // If we have an exception object, log it through .error() to trigger + // proper stacktraces in browsers that support it. There are no (known) + // browsers that don't support .error(), that do support .log() and + // have useful exception handling through .log(). + if ( e && console.error ) { + console.error( e ); + } + } + } + + /** + * A module has entered state 'ready', 'error', or 'missing'. Automatically update pending jobs + * and modules that depend upon this module. if the given module failed, propagate the 'error' + * state up the dependency tree; otherwise, execute all jobs/modules that now have all their + * dependencies satisfied. On jobs depending on a failed module, run the error callback, if any. + * + * @param module String name of module that entered one of the states 'ready', 'error', or 'missing'. */ function handlePending( module ) { - var j, r; - - try { - // Run jobs whose dependencies have just been met - for ( j = 0; j < jobs.length; j += 1 ) { - if ( compare( - filter( 'ready', jobs[j].dependencies ), - jobs[j].dependencies ) ) - { - var callback = jobs[j].ready; - jobs.splice( j, 1 ); - j -= 1; - if ( $.isFunction( callback ) ) { - callback(); + var j, job, hasErrors, m, stateChange; + + // Modules. + if ( $.inArray( registry[module].state, ['error', 'missing'] ) !== -1 ) { + // If the current module failed, mark all dependent modules also as failed. + // Iterate until steady-state to propagate the error state upwards in the + // dependency tree. + do { + stateChange = false; + for ( m in registry ) { + if ( $.inArray( registry[m].state, ['error', 'missing'] ) === -1 ) { + if ( filter( ['error', 'missing'], registry[m].dependencies ).length > 0 ) { + registry[m].state = 'error'; + stateChange = true; + } } } - } - // Execute modules whose dependencies have just been met - for ( r in registry ) { - if ( registry[r].state === 'loaded' ) { - if ( compare( - filter( ['ready'], registry[r].dependencies ), - registry[r].dependencies ) ) - { - execute( r ); + } while ( stateChange ); + } + + // Execute all jobs whose dependencies are either all satisfied or contain at least one failed module. + for ( j = 0; j < jobs.length; j += 1 ) { + hasErrors = filter( ['error', 'missing'], jobs[j].dependencies ).length > 0; + if ( hasErrors || allReady( jobs[j].dependencies ) ) { + // All dependencies satisfied, or some have errors + job = jobs[j]; + jobs.splice( j, 1 ); + j -= 1; + try { + if ( hasErrors ) { + throw new Error ("Module " + module + " failed."); + } else { + if ( $.isFunction( job.ready ) ) { + job.ready(); + } + } + } catch ( e ) { + if ( $.isFunction( job.error ) ) { + try { + job.error( e, [module] ); + } catch ( ex ) { + // A user-defined operation raised an exception. Swallow to protect + // our state machine! + log( 'Exception thrown by job.error()', ex ); + } } } } - } catch ( e ) { - // Run error callbacks of jobs affected by this condition - for ( j = 0; j < jobs.length; j += 1 ) { - if ( $.inArray( module, jobs[j].dependencies ) !== -1 ) { - if ( $.isFunction( jobs[j].error ) ) { - jobs[j].error( e, module ); - } - jobs.splice( j, 1 ); - j -= 1; + } + + if ( registry[module].state === 'ready' ) { + // The current module became 'ready'. Recursively execute all dependent modules that are loaded + // and now have all dependencies satisfied. + for ( m in registry ) { + if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) { + execute( m ); } } - throw e; } } - + /** * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation, - * depending on whether document-ready has occured yet and whether we are in async mode. + * depending on whether document-ready has occurred yet and whether we are in async mode. * * @param src String: URL to script, will be used as the src attribute in the script tag * @param callback Function: Optional callback which will be run when the script is done */ function addScript( src, callback, async ) { - var done = false, script, head; - if ( ready || async ) { + /*jshint evil:true */ + var script, head, + done = false; + + // Using isReady directly instead of storing it locally from + // a $.fn.ready callback (bug 31895). + if ( $.isReady || async ) { // jQuery's getScript method is NOT better than doing this the old-fashioned way // because jQuery will eval the script's code, and errors will not have sane // line numbers. @@ -638,8 +766,8 @@ var mw = ( function ( $, undefined ) { script.setAttribute( 'type', 'text/javascript' ); if ( $.isFunction( callback ) ) { // Attach handlers for all browsers (based on jQuery.ajax) - script.onload = script.onreadystatechange = function() { - + script.onload = script.onreadystatechange = function () { + if ( !done && ( @@ -647,11 +775,11 @@ var mw = ( function ( $, undefined ) { || /loaded|complete/.test( script.readyState ) ) ) { - + done = true; - + callback(); - + // Handle memory leak in IE. This seems to fail in // IE7 sometimes (Permission Denied error when // accessing script.parentNode) so wrap it in @@ -661,21 +789,21 @@ var mw = ( function ( $, undefined ) { if ( script.parentNode ) { script.parentNode.removeChild( script ); } - + // Dereference the script script = undefined; } catch ( e ) { } } }; } - + if ( window.opera ) { // Appending to the <head> blocks rendering completely in Opera, // so append to the <body> after document ready. This means the // scripts only start loading after the document has been rendered, // but so be it. Opera users don't deserve faster web pages if their // browser makes it impossible - $( function() { document.body.appendChild( script ); } ); + $( function () { document.body.appendChild( script ); } ); } else { // IE-safe way of getting the <head> . document.documentElement.head doesn't // work in scripts that run in the <head> @@ -693,15 +821,15 @@ var mw = ( function ( $, undefined ) { } } } - + /** * Executes a loaded module, making it ready to use * * @param module string module name to execute */ - function execute( module, callback ) { - var style, media, i, script, markModuleReady, nestedAddScript; - + function execute( module ) { + var key, value, media, i, urls, script, markModuleReady, nestedAddScript; + if ( registry[module] === undefined ) { throw new Error( 'Module has not been registered yet: ' + module ); } else if ( registry[module].state === 'registered' ) { @@ -711,38 +839,84 @@ var mw = ( function ( $, undefined ) { } else if ( registry[module].state === 'ready' ) { throw new Error( 'Module has already been loaded: ' + module ); } - - // Add styles + + /** + * Define loop-function here for efficiency + * and to avoid re-using badly scoped variables. + */ + function addLink( media, url ) { + var el = document.createElement( 'link' ); + getMarker().before( el ); // IE: Insert in dom before setting href + el.rel = 'stylesheet'; + if ( media && media !== 'all' ) { + el.media = media; + } + el.href = url; + } + + // Process styles (see also mw.loader.implement) + // * back-compat: { <media>: css } + // * back-compat: { <media>: [url, ..] } + // * { "css": [css, ..] } + // * { "url": { <media>: [url, ..] } } if ( $.isPlainObject( registry[module].style ) ) { - for ( media in registry[module].style ) { - style = registry[module].style[media]; - if ( $.isArray( style ) ) { - for ( i = 0; i < style.length; i += 1 ) { - getMarker().before( mw.html.element( 'link', { - 'type': 'text/css', - 'media': media, - 'rel': 'stylesheet', - 'href': style[i] - } ) ); + for ( key in registry[module].style ) { + value = registry[module].style[key]; + media = undefined; + + if ( key !== 'url' && key !== 'css' ) { + // Backwards compatibility, key is a media-type + if ( typeof value === 'string' ) { + // back-compat: { <media>: css } + // Ignore 'media' because it isn't supported (nor was it used). + // Strings are pre-wrapped in "@media". The media-type was just "" + // (because it had to be set to something). + // This is one of the reasons why this format is no longer used. + addEmbeddedCSS( value ); + } else { + // back-compat: { <media>: [url, ..] } + media = key; + key = 'bc-url'; + } + } + + // Array of css strings in key 'css', + // or back-compat array of urls from media-type + if ( $.isArray( value ) ) { + for ( i = 0; i < value.length; i += 1 ) { + if ( key === 'bc-url' ) { + // back-compat: { <media>: [url, ..] } + addLink( media, value[i] ); + } else if ( key === 'css' ) { + // { "css": [css, ..] } + addEmbeddedCSS( value[i] ); + } + } + // Not an array, but a regular object + // Array of urls inside media-type key + } else if ( typeof value === 'object' ) { + // { "url": { <media>: [url, ..] } } + for ( media in value ) { + urls = value[media]; + for ( i = 0; i < urls.length; i += 1 ) { + addLink( media, urls[i] ); + } } - } else if ( typeof style === 'string' ) { - addInlineCSS( style, media ); } } } + // Add localizations to message system if ( $.isPlainObject( registry[module].messages ) ) { mw.messages.set( registry[module].messages ); } + // Execute script try { script = registry[module].script; - markModuleReady = function() { + markModuleReady = function () { registry[module].state = 'ready'; handlePending( module ); - if ( $.isFunction( callback ) ) { - callback(); - } }; nestedAddScript = function ( arr, callback, async, i ) { // Recursively call addScript() in its own callback @@ -752,29 +926,29 @@ var mw = ( function ( $, undefined ) { callback(); return; } - - addScript( arr[i], function() { + + addScript( arr[i], function () { nestedAddScript( arr, callback, async, i + 1 ); }, async ); }; - + if ( $.isArray( script ) ) { registry[module].state = 'loading'; nestedAddScript( script, markModuleReady, registry[module].async, 0 ); } else if ( $.isFunction( script ) ) { + registry[module].state = 'ready'; script( $ ); - markModuleReady(); + handlePending( module ); } } catch ( e ) { // This needs to NOT use mw.log because these errors are common in production mode // and not in debug mode, such as when a symbol that should be global isn't exported - if ( window.console && typeof window.console.log === 'function' ) { - console.log( 'mw.loader::execute> Exception thrown by ' + module + ': ' + e.message ); - } + log( 'Exception thrown by ' + module + ': ' + e.message, e ); registry[module].state = 'error'; + handlePending( module ); } } - + /** * Adds a dependencies to the queue with optional callbacks to be run * when the dependencies are ready or fail @@ -787,22 +961,14 @@ var mw = ( function ( $, undefined ) { */ function request( dependencies, ready, error, async ) { var regItemDeps, regItemDepLen, n; - + // Allow calling by single module name if ( typeof dependencies === 'string' ) { dependencies = [dependencies]; - if ( registry[dependencies[0]] !== undefined ) { - // Cache repetitively accessed deep level object member - regItemDeps = registry[dependencies[0]].dependencies; - // Cache to avoid looped access to length property - regItemDepLen = regItemDeps.length; - for ( n = 0; n < regItemDepLen; n += 1 ) { - dependencies[dependencies.length] = regItemDeps[n]; - } - } } + // Add ready and error callbacks if they were given - if ( arguments.length > 1 ) { + if ( ready !== undefined || error !== undefined ) { jobs[jobs.length] = { 'dependencies': filter( ['registered', 'loading', 'loaded'], @@ -812,6 +978,7 @@ var mw = ( function ( $, undefined ) { 'error': error }; } + // Queue up any dependencies that are registered dependencies = filter( ['registered'], dependencies ); for ( n = 0; n < dependencies.length; n += 1 ) { @@ -823,10 +990,11 @@ var mw = ( function ( $, undefined ) { } } } + // Work the queue mw.loader.work(); } - + function sortQuery(o) { var sorted = {}, key, a = []; for ( key in o ) { @@ -840,7 +1008,7 @@ var mw = ( function ( $, undefined ) { } return sorted; } - + /** * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] } * to a query string of the form foo.bar,baz|bar.baz,quux @@ -853,7 +1021,7 @@ var mw = ( function ( $, undefined ) { } return arr.join( '|' ); } - + /** * Asynchronously append a script tag to the end of the body * that invokes load.php @@ -872,9 +1040,11 @@ var mw = ( function ( $, undefined ) { // Append &* to avoid triggering the IE6 extension check addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async ); } - + /* Public Methods */ return { + addStyleTag: addStyleTag, + /** * Requests dependencies from server, loading and executing when things when ready. */ @@ -883,7 +1053,7 @@ var mw = ( function ( $, undefined ) { source, group, g, i, modules, maxVersion, sourceLoadScript, currReqBase, currReqBaseLength, moduleMap, l, lastDotIndex, prefix, suffix, bytesAdded, async; - + // Build a list of request parameters common to all requests. reqBase = { skin: mw.config.get( 'skin' ), @@ -893,7 +1063,7 @@ var mw = ( function ( $, undefined ) { // Split module batch by source and by group. splits = {}; maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 ); - + // Appends a list of modules from the queue to the batch for ( q = 0; q < queue.length; q += 1 ) { // Only request modules which are registered @@ -910,14 +1080,14 @@ var mw = ( function ( $, undefined ) { if ( !batch.length ) { return; } - + // The queue has been processed into the batch, clear up the queue. queue = []; - + // Always order modules alphabetically to help reduce cache // misses for otherwise identical content. batch.sort(); - + // Split batch by source and by group. for ( b = 0; b < batch.length; b += 1 ) { bSource = registry[batch[b]].source; @@ -931,24 +1101,24 @@ var mw = ( function ( $, undefined ) { bSourceGroup = splits[bSource][bGroup]; bSourceGroup[bSourceGroup.length] = batch[b]; } - + // Clear the batch - this MUST happen before we append any // script elements to the body or it's possible that a script // will be locally cached, instantly load, and work the batch // again, all before we've cleared it causing each request to // include modules which are already loaded. batch = []; - + for ( source in splits ) { - + sourceLoadScript = sources[source].loadScript; - + for ( group in splits[source] ) { - + // Cache access to currently selected list of // modules for this group from this source. modules = splits[source][group]; - + // Calculate the highest timestamp maxVersion = 0; for ( g = 0; g < modules.length; g += 1 ) { @@ -956,16 +1126,20 @@ var mw = ( function ( $, undefined ) { maxVersion = registry[modules[g]].version; } } - + currReqBase = $.extend( { 'version': formatVersionNumber( maxVersion ) }, reqBase ); + // For user modules append a user name to the request. + if ( group === "user" && mw.config.get( 'wgUserName' ) !== null ) { + currReqBase.user = mw.config.get( 'wgUserName' ); + } currReqBaseLength = $.param( currReqBase ).length; async = true; // We may need to split up the request to honor the query string length limit, // so build it piece by piece. l = currReqBaseLength + 9; // '&modules='.length == 9 - + moduleMap = {}; // { prefix: [ suffixes ] } - + for ( i = 0; i < modules.length; i += 1 ) { // Determine how many bytes this module would add to the query string lastDotIndex = modules[i].lastIndexOf( '.' ); @@ -975,7 +1149,7 @@ var mw = ( function ( $, undefined ) { bytesAdded = moduleMap[prefix] !== undefined ? suffix.length + 3 // '%2C'.length == 3 : modules[i].length + 3; // '%7C'.length == 3 - + // If the request would become too long, create a new one, // but don't create empty requests if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) { @@ -1005,7 +1179,7 @@ var mw = ( function ( $, undefined ) { } } }, - + /** * Register a source. * @@ -1023,16 +1197,16 @@ var mw = ( function ( $, undefined ) { } return true; } - + if ( sources[id] !== undefined ) { throw new Error( 'source already registered: ' + id ); } - + sources[id] = props; - + return true; }, - + /** * Registers a module, letting the system know about it and its * properties. Startup modules contain calls to this function. @@ -1083,7 +1257,7 @@ var mw = ( function ( $, undefined ) { registry[module].dependencies = dependencies; } }, - + /** * Implements a module, giving the system a course of action to take * upon loading. Results of a request for one or more modules contain @@ -1091,12 +1265,20 @@ var mw = ( function ( $, undefined ) { * * All arguments are required. * - * @param module String: Name of module - * @param script Mixed: Function of module code or String of URL to be used as the src - * attribute when adding a script element to the body - * @param style Object: Object of CSS strings keyed by media-type or Object of lists of URLs - * keyed by media-type - * @param msgs Object: List of key/value pairs to be passed through mw.messages.set + * @param {String} module Name of module + * @param {Function|Array} script Function with module code or Array of URLs to + * be used as the src attribute of a new <script> tag. + * @param {Object} style Should follow one of the following patterns: + * { "css": [css, ..] } + * { "url": { <media>: [url, ..] } } + * And for backwards compatibility (needs to be supported forever due to caching): + * { <media>: css } + * { <media>: [url, ..] } + * + * The reason css strings are not concatenated anymore is bug 31676. We now check + * whether it's safe to extend the stylesheet (see canExpandStylesheetWith). + * + * @param {Object} msgs List of key/value pairs to be passed through mw.messages.set */ implement: function ( module, script, style, msgs ) { // Validate input @@ -1120,21 +1302,19 @@ var mw = ( function ( $, undefined ) { if ( registry[module] !== undefined && registry[module].script !== undefined ) { throw new Error( 'module already implemented: ' + module ); } - // Mark module as loaded - registry[module].state = 'loaded'; // Attach components registry[module].script = script; registry[module].style = style; registry[module].messages = msgs; - // Execute or queue callback - if ( compare( - filter( ['ready'], registry[module].dependencies ), - registry[module].dependencies ) ) - { - execute( module ); + // The module may already have been marked as erroneous + if ( $.inArray( registry[module].state, ['error', 'missing'] ) === -1 ) { + registry[module].state = 'loaded'; + if ( allReady( registry[module].dependencies ) ) { + execute( module ); + } } }, - + /** * Executes a function as soon as one or more required modules are ready * @@ -1155,25 +1335,23 @@ var mw = ( function ( $, undefined ) { } // Resolve entire dependency map dependencies = resolve( dependencies ); - // If all dependencies are met, execute ready immediately - if ( compare( filter( ['ready'], dependencies ), dependencies ) ) { + if ( allReady( dependencies ) ) { + // Run ready immediately if ( $.isFunction( ready ) ) { ready(); } - } - // If any dependencies have errors execute error immediately - else if ( filter( ['error'], dependencies ).length ) { + } else if ( filter( ['error', 'missing'], dependencies ).length ) { + // Execute error immediately if any dependencies have errors if ( $.isFunction( error ) ) { - error( new Error( 'one or more dependencies have state "error"' ), + error( new Error( 'one or more dependencies have state "error" or "missing"' ), dependencies ); } - } - // Since some dependencies are not yet ready, queue up a request - else { + } else { + // Not all dependencies are ready: queue up a request request( dependencies, ready, error ); } }, - + /** * Loads an external script or one or more modules for future use * @@ -1188,7 +1366,7 @@ var mw = ( function ( $, undefined ) { * be assumed if loading a URL, and false will be assumed otherwise. */ load: function ( modules, type, async ) { - var filtered, m; + var filtered, m, module; // Validate input if ( typeof modules !== 'object' && typeof modules !== 'string' ) { @@ -1209,7 +1387,8 @@ var mw = ( function ( $, undefined ) { href: modules } ) ); return; - } else if ( type === 'text/javascript' || type === undefined ) { + } + if ( type === 'text/javascript' || type === undefined ) { addScript( modules, null, async ); return; } @@ -1226,28 +1405,31 @@ var mw = ( function ( $, undefined ) { // an array of unrelated modules, whereas the modules passed to // using() are related and must all be loaded. for ( filtered = [], m = 0; m < modules.length; m += 1 ) { - if ( registry[modules[m]] !== undefined ) { - filtered[filtered.length] = modules[m]; + module = registry[modules[m]]; + if ( module !== undefined ) { + if ( $.inArray( module.state, ['error', 'missing'] ) === -1 ) { + filtered[filtered.length] = modules[m]; + } } } - // Resolve entire dependency map - filtered = resolve( filtered ); - // If all modules are ready, nothing dependency be done - if ( compare( filter( ['ready'], filtered ), filtered ) ) { + if ( filtered.length === 0 ) { return; } - // If any modules have errors - else if ( filter( ['error'], filtered ).length ) { + // Resolve entire dependency map + filtered = resolve( filtered ); + // If all modules are ready, nothing to be done + if ( allReady( filtered ) ) { return; } - // Since some modules are not yet ready, queue up a request - else { - request( filtered, null, null, async ); + // If any modules have errors: also quit. + if ( filter( ['error', 'missing'], filtered ).length ) { return; } + // Since some modules are not yet ready, queue up a request. + request( filtered, null, null, async ); }, - + /** * Changes the state of a module * @@ -1256,6 +1438,7 @@ var mw = ( function ( $, undefined ) { */ state: function ( module, state ) { var m; + if ( typeof module === 'object' ) { for ( m in module ) { mw.loader.state( m, module[m] ); @@ -1265,9 +1448,17 @@ var mw = ( function ( $, undefined ) { if ( registry[module] === undefined ) { mw.loader.register( module ); } - registry[module].state = state; + if ( $.inArray(state, ['ready', 'error', 'missing']) !== -1 + && registry[module].state !== state ) { + // Make sure pending modules depending on this one get executed if their + // dependencies are now fulfilled! + registry[module].state = state; + handlePending( module ); + } else { + registry[module].state = state; + } }, - + /** * Gets the version of a module * @@ -1279,14 +1470,14 @@ var mw = ( function ( $, undefined ) { } return null; }, - + /** * @deprecated since 1.18 use mw.loader.getVersion() instead */ version: function () { return mw.loader.getVersion.apply( mw.loader, arguments ); }, - + /** * Gets the state of a module * @@ -1298,7 +1489,7 @@ var mw = ( function ( $, undefined ) { } return null; }, - + /** * Get names of all registered modules. * @@ -1309,7 +1500,7 @@ var mw = ( function ( $, undefined ) { return key; } ); }, - + /** * For backwards-compatibility with Squid-cached pages. Loads mw.user */ @@ -1318,7 +1509,7 @@ var mw = ( function ( $, undefined ) { } }; }() ), - + /** HTML construction helper functions */ html: ( function () { function escapeCallback( s ) { @@ -1344,7 +1535,7 @@ var mw = ( function ( $, undefined ) { escape: function ( s ) { return s.replace( /['"<>&]/g, escapeCallback ); }, - + /** * Wrapper object for raw HTML passed to mw.html.element(). * @constructor @@ -1352,7 +1543,7 @@ var mw = ( function ( $, undefined ) { Raw: function ( value ) { this.value = value; }, - + /** * Wrapper object for CDATA element contents passed to mw.html.element() * @constructor @@ -1360,7 +1551,7 @@ var mw = ( function ( $, undefined ) { Cdata: function ( value ) { this.value = value; }, - + /** * Create an HTML element string, with safe escaping. * @@ -1382,7 +1573,7 @@ var mw = ( function ( $, undefined ) { */ element: function ( name, attrs, contents ) { var v, attrName, s = '<' + name; - + for ( attrName in attrs ) { v = attrs[attrName]; // Convert name=true, to name=name @@ -1429,7 +1620,7 @@ var mw = ( function ( $, undefined ) { return s; } }; - })(), + }() ), // Skeleton user object. mediawiki.user.js extends this user: { @@ -1437,8 +1628,8 @@ var mw = ( function ( $, undefined ) { tokens: new Map() } }; - -})( jQuery ); + +}( jQuery ) ); // Alias $j to jQuery for backwards compatibility window.$j = jQuery; @@ -1447,7 +1638,7 @@ window.$j = jQuery; window.mw = window.mediaWiki = mw; // Auto-register from pre-loaded startup scripts -if ( typeof startUp !== 'undefined' && jQuery.isFunction( startUp ) ) { - startUp(); - startUp = undefined; +if ( jQuery.isFunction( window.startUp ) ) { + window.startUp(); + window.startUp = undefined; } diff --git a/resources/mediawiki/mediawiki.log.js b/resources/mediawiki/mediawiki.log.js index ad4c73df..4ea1a881 100644 --- a/resources/mediawiki/mediawiki.log.js +++ b/resources/mediawiki/mediawiki.log.js @@ -6,7 +6,7 @@ * @author Trevor Parscal <tparscal@wikimedia.org> */ -( function ( $ ) { +( function ( mw, $ ) { /** * Logs a message to the console. @@ -17,7 +17,7 @@ * * @param {String} First in list of variadic messages to output to console. */ - mw.log = function( /* logmsg, logmsg, */ ) { + mw.log = function ( /* logmsg, logmsg, */ ) { // Turn arguments into an array var args = Array.prototype.slice.call( arguments ), // Allow log messages to use a configured prefix to identify the source window (ie. frame) @@ -33,7 +33,8 @@ // If there is no console, use our own log box mw.loader.using( 'jquery.footHovzer', function () { - var d = new Date(), + var hovzer, + d = new Date(), // Create HH:MM:SS.MIL timestamp time = ( d.getHours() < 10 ? '0' + d.getHours() : d.getHours() ) + ':' + ( d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes() ) + @@ -48,7 +49,7 @@ backgroundColor: 'white', borderTop: 'solid 2px #ADADAD' } ); - var hovzer = $.getFootHovzer(); + hovzer = $.getFootHovzer(); hovzer.$.append( $log ); hovzer.update(); } @@ -67,4 +68,4 @@ } ); }; -})( jQuery ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.notification.css b/resources/mediawiki/mediawiki.notification.css new file mode 100644 index 00000000..9a7b651d --- /dev/null +++ b/resources/mediawiki/mediawiki.notification.css @@ -0,0 +1,26 @@ +/** + * Stylesheet for mediawiki.notification module + */ + +#mw-notification-area { + position: absolute; + top: 1em; + right: 1em; + width: 20em; + line-height: 1.35; + z-index: 10000; +} + +.mw-notification { + padding: 0.25em 1em; + margin-bottom: 0.5em; + border: solid 1px #ddd; + background-color: #fcfcfc; + /* Message hides on-click */ + /* See also mediawiki.notification.js */ + cursor: pointer; +} + +.mw-notification-title { + font-weight: bold; +} diff --git a/resources/mediawiki/mediawiki.notification.js b/resources/mediawiki/mediawiki.notification.js new file mode 100644 index 00000000..58a3ab6a --- /dev/null +++ b/resources/mediawiki/mediawiki.notification.js @@ -0,0 +1,480 @@ +/** + * Implements mediaWiki.notification library + */ +( function ( mw, $ ) { + 'use strict'; + + var isPageReady = false, + isInitialized = false, + preReadyNotifQueue = [], + /** + * @var {jQuery} + * The #mw-notification-area div that all notifications are contained inside. + */ + $area = null; + + /** + * Creates a Notification object for 1 message. + * Does not insert anything into the document (see .start()). + * + * @constructor + * @see mw.notification.notify + */ + function Notification( message, options ) { + var $notification, $notificationTitle, $notificationContent; + + $notification = $( '<div class="mw-notification"></div>' ) + .data( 'mw.notification', this ) + .addClass( options.autoHide ? 'mw-notification-autohide' : 'mw-notification-noautohide' ); + + if ( options.tag ) { + // Sanitize options.tag before it is used by any code. (Including Notification class methods) + options.tag = options.tag.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' ); + if ( options.tag ) { + $notification.addClass( 'mw-notification-tag-' + options.tag ); + } else { + delete options.tag; + } + } + + if ( options.title ) { + $notificationTitle = $( '<div class="mw-notification-title"></div>' ) + .text( options.title ) + .appendTo( $notification ); + } + + $notificationContent = $( '<div class="mw-notification-content"></div>' ); + + if ( typeof message === 'object' ) { + // Handle mw.Message objects separately from DOM nodes and jQuery objects + if ( message instanceof mw.Message ) { + $notificationContent.html( message.parse() ); + } else { + $notificationContent.append( message ); + } + } else { + $notificationContent.text( message ); + } + + $notificationContent.appendTo( $notification ); + + // Private state parameters, meant for internal use only + // isOpen: Set to true after .start() is called to avoid double calls. + // Set back to false after .close() to avoid duplicating the close animation. + // isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts. + // Set to true initially so .start() can call .resume(). + // message: The message passed to the notification. Unused now but may be used in the future + // to stop replacement of a tagged notification with another notification using the same message. + // options: The options passed to the notification with a little sanitization. Used by various methods. + // $notification: jQuery object containing the notification DOM node. + this.isOpen = false; + this.isPaused = true; + this.message = message; + this.options = options; + this.$notification = $notification; + } + + /** + * Start the notification. + * This inserts it into the page, closes any matching tagged notifications, + * handles the fadeIn animations and repacement transitions, and starts autoHide timers. + */ + Notification.prototype.start = function () { + var + // Local references + $notification, options, + // Original opacity so that we can animate back to it later + opacity, + // Other notification elements matching the same tag + $tagMatches, + outerHeight, + placeholderHeight; + + if ( this.isOpen ) { + return; + } + + this.isOpen = true; + + options = this.options; + $notification = this.$notification; + + opacity = this.$notification.css( 'opacity' ); + + // Set the opacity to 0 so we can fade in later. + $notification.css( 'opacity', 0 ); + + if ( options.tag ) { + // Check to see if there are any tagged notifications with the same tag as the new one + $tagMatches = $area.find( '.mw-notification-tag-' + options.tag ); + } + + // If we found a tagged notification use the replacement pattern instead of the new + // notification fade-in pattern. + if ( options.tag && $tagMatches.length ) { + + // Iterate over the tag matches to find the outerHeight we should use + // for the placeholder. + outerHeight = 0; + $tagMatches.each( function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + // Use the notification's height + padding + border + margins + // as the placeholder height. + outerHeight = notif.$notification.outerHeight( true ); + if ( notif.$replacementPlaceholder ) { + // Grab the height of a placeholder that has not finished animating. + placeholderHeight = notif.$replacementPlaceholder.height(); + // Remove any placeholders added by a previous tagged + // notification that was in the middle of replacing another. + // This also makes sure that we only grab the placeholderHeight + // for the most recent notification. + notif.$replacementPlaceholder.remove(); + delete notif.$replacementPlaceholder; + } + // Close the previous tagged notification + // Since we're replacing it do this with a fast speed and don't output a placeholder + // since we're taking care of that transition ourselves. + notif.close( { speed: 'fast', placeholder: false } ); + } + } ); + if ( placeholderHeight !== undefined ) { + // If the other tagged notification was in the middle of replacing another + // tagged notification, continue from the placeholder's height instead of + // using the outerHeight of the notification. + outerHeight = placeholderHeight; + } + + $notification + // Insert the new notification before the tagged notification(s) + .insertBefore( $tagMatches.first() ) + .css( { + // Use an absolute position so that we can use a placeholder to gracefully push other notifications + // into the right spot. + position: 'absolute', + width: $notification.width() + } ) + // Fade-in the notification + .animate( { opacity: opacity }, + { + duration: 'slow', + complete: function () { + // After we've faded in clear the opacity and let css take over + $( this ).css( { opacity: '' } ); + } + } ); + + // Create a clear placeholder we can use to make the notifications around the notification that is being + // replaced expand or contract gracefully to fit the height of the new notification. + var self = this; + self.$replacementPlaceholder = $( '<div>' ) + // Set the height to the space the previous notification or placeholder took + .css( 'height', outerHeight ) + // Make sure that this placeholder is at the very end of this tagged notification group + .insertAfter( $tagMatches.eq( -1 ) ) + // Animate the placeholder height to the space that this new notification will take up + .animate( { height: $notification.outerHeight( true ) }, + { + // Do space animations fast + speed: 'fast', + complete: function () { + // Reset the notification position after we've finished the space animation + // However do not do it if the placeholder was removed because another tagged + // notification went and closed this one. + if ( self.$replacementPlaceholder ) { + $notification.css( 'position', '' ); + } + // Finally, remove the placeholder from the DOM + $( this ).remove(); + } + } ); + } else { + // Append to the notification area and fade in to the original opacity. + $notification + .appendTo( $area ) + .animate( { opacity: opacity }, + { + duration: 'fast', + complete: function () { + // After we've faded in clear the opacity and let css take over + $( this ).css( 'opacity', '' ); + } + } + ); + } + + // By default a notification is paused. + // If this notification is within the first {autoHideLimit} notifications then + // start the auto-hide timer as soon as it's created. + var autohideCount = $area.find( '.mw-notification-autohide' ).length; + if ( autohideCount <= notification.autoHideLimit ) { + this.resume(); + } + }; + + /** + * Pause any running auto-hide timer for this notification + */ + Notification.prototype.pause = function () { + if ( this.isPaused ) { + return; + } + this.isPaused = true; + + if ( this.timeout ) { + clearTimeout( this.timeout ); + delete this.timeout; + } + }; + + /** + * Start autoHide timer if not already started. + * Does nothing if autoHide is disabled. + * Either to resume from pause or to make the first start. + */ + Notification.prototype.resume = function () { + var notif = this; + if ( !notif.isPaused ) { + return; + } + // Start any autoHide timeouts + if ( notif.options.autoHide ) { + notif.isPaused = false; + notif.timeout = setTimeout( function () { + // Already finished, so don't try to re-clear it + delete notif.timeout; + notif.close(); + }, notification.autoHideSeconds * 1000 ); + } + }; + + /** + * Close/hide the notification. + * + * @param {Object} options An object containing options for the closing of the notification. + * These are typically only used internally. + * - speed: Use a close speed different than the default 'slow'. + * - placeholder: Set to false to disable the placeholder transition. + */ + Notification.prototype.close = function ( options ) { + if ( !this.isOpen ) { + return; + } + this.isOpen = false; + // Clear any remaining timeout on close + this.pause(); + + options = $.extend( { + speed: 'slow', + placeholder: true + }, options ); + + // Remove the mw-notification-autohide class from the notification to avoid + // having a half-closed notification counted as a notification to resume + // when handling {autoHideLimit}. + this.$notification.removeClass( 'mw-notification-autohide' ); + + // Now that a notification is being closed. Start auto-hide timers for any + // notification that has now become one of the first {autoHideLimit} notifications. + notification.resume(); + + this.$notification + .css( { + // Don't trigger any mouse events while fading out, just in case the cursor + // happens to be right above us when we transition upwards. + pointerEvents: 'none', + // Set an absolute position so we can move upwards in the animation. + // Notification replacement doesn't look right unless we use an animation like this. + position: 'absolute', + // We must fix the width to avoid it shrinking horizontally. + width: this.$notification.width() + } ) + // Fix the top/left position to the current computed position from which we + // can animate upwards. + .css( this.$notification.position() ); + + // This needs to be done *after* notification's position has been made absolute. + if ( options.placeholder ) { + // Insert a placeholder with a height equal to the height of the + // notification plus it's vertical margins in place of the notification + var $placeholder = $( '<div>' ) + .css( 'height', this.$notification.outerHeight( true ) ) + .insertBefore( this.$notification ); + } + + // Animate opacity and top to create fade upwards animation for notification closing + this.$notification + .animate( { + opacity: 0, + top: '-=35' + }, { + duration: options.speed, + complete: function () { + // Remove the notification + $( this ).remove(); + if ( options.placeholder ) { + // Use a fast slide up animation after closing to make it look like the notifications + // below slide up into place when the notification disappears + $placeholder.slideUp( 'fast', function () { + // Remove the placeholder + $( this ).remove(); + } ); + } + } + } ); + }; + + /** + * Helper function, take a list of notification divs and call + * a function on the Notification instance attached to them + * + * @param {jQuery} $notifications A jQuery object containing notification divs + * @param {string} fn The name of the function to call on the Notification instance + */ + function callEachNotification( $notifications, fn ) { + $notifications.each( function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + notif[fn](); + } + } ); + } + + /** + * Initialisation + * (don't call before document ready) + */ + function init() { + if ( !isInitialized ) { + isInitialized = true; + $area = $( '<div id="mw-notification-area"></div>' ) + // Pause auto-hide timers when the mouse is in the notification area. + .on( { + mouseenter: notification.pause, + mouseleave: notification.resume + } ) + // When clicking on a notification close it. + .on( 'click', '.mw-notification', function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + notif.close(); + } + } ) + // Stop click events from <a> tags from propogating to prevent clicking. + // on links from hiding a notification. + .on( 'click', 'a', function ( e ) { + e.stopPropagation(); + } ); + + // Prepend the notification area to the content area and save it's object. + mw.util.$content.prepend( $area ); + } + } + + var notification = { + /** + * Pause auto-hide timers for all notifications. + * Notifications will not auto-hide until resume is called. + */ + pause: function () { + callEachNotification( + $area.children( '.mw-notification' ), + 'pause' + ); + }, + + /** + * Resume any paused auto-hide timers from the beginning. + * Only the first {autoHideLimit} timers will be resumed. + */ + resume: function () { + callEachNotification( + // Only call resume on the first {autoHideLimit} notifications. + // Exclude noautohide notifications to avoid bugs where {autoHideLimit} + // { autoHide: false } notifications are at the start preventing any + // auto-hide notifications from being autohidden. + $area.children( '.mw-notification-autohide' ).slice( 0, notification.autoHideLimit ), + 'resume' + ); + }, + + /** + * Display a notification message to the user. + * + * @param {mixed} message The DOM-element, jQuery object, mw.Message instance, + * or plaintext string to be used as the message. + * @param {Object} options The options to use for the notification. + * See mw.notification.defaults for details. + */ + notify: function ( message, options ) { + var notif; + options = $.extend( {}, notification.defaults, options ); + + notif = new Notification( message, options ); + + if ( isPageReady ) { + notif.start(); + } else { + preReadyNotifQueue.push( notif ); + } + }, + + /** + * @var {Object} + * The defaults for mw.notification.notify's options parameter + * autoHide: + * A boolean indicating whether the notifification should automatically + * be hidden after shown. Or if it should persist. + * + * tag: + * An optional string. When a notification is tagged only one message + * with that tag will be displayed. Trying to display a new notification + * with the same tag as one already being displayed will cause the other + * notification to be closed and this new notification to open up inside + * the same place as the previous notification. + * + * title: + * An optional title for the notification. Will be displayed above the + * content. Usually in bold. + */ + defaults: { + autoHide: true, + tag: false, + title: undefined + }, + + /** + * @var {number} + * Number of seconds to wait before auto-hiding notifications. + */ + autoHideSeconds: 5, + + /** + * @var {number} + * Maximum number of notifications to count down auto-hide timers for. + * Only the first {autoHideLimit} notifications being displayed will + * auto-hide. Any notifications further down in the list will only start + * counting down to auto-hide after the first few messages have closed. + * + * This basically represents the number of notifications the user should + * be able to process in {autoHideSeconds} time. + */ + autoHideLimit: 3 + }; + + $( function () { + var notif; + + init(); + + // Handle pre-ready queue. + isPageReady = true; + while ( preReadyNotifQueue.length ) { + notif = preReadyNotifQueue.shift(); + notif.start(); + } + } ); + + mw.notification = notification; + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.notify.js b/resources/mediawiki/mediawiki.notify.js new file mode 100644 index 00000000..3bf2a896 --- /dev/null +++ b/resources/mediawiki/mediawiki.notify.js @@ -0,0 +1,20 @@ +/** + * Implements mediaWiki.notify function + */ +( function ( mw ) { + 'use strict'; + + /** + * @see mw.notification.notify + */ + mw.notify = function ( message, options ) { + // Don't bother loading the whole notification system if we never use it. + mw.loader.using( 'mediawiki.notification', function () { + // Don't bother calling mw.loader.using a second time after we've already loaded mw.notification. + mw.notify = mw.notification.notify; + // Call notify with the notification the user requested of us. + mw.notify( message, options ); + } ); + }; + +}( mediaWiki ) );
\ No newline at end of file diff --git a/resources/mediawiki/mediawiki.searchSuggest.js b/resources/mediawiki/mediawiki.searchSuggest.js new file mode 100644 index 00000000..99a55576 --- /dev/null +++ b/resources/mediawiki/mediawiki.searchSuggest.js @@ -0,0 +1,166 @@ +/** + * Add search suggestions to the search form. + */ +( function ( mw, $ ) { + $( document ).ready( function ( $ ) { + var map, searchboxesSelectors, + // Region where the suggestions box will appear directly below + // (using the same width). Can be a container element or the input + // itself, depending on what suits best in the environment. + // For Vector the suggestion box should align with the simpleSearch + // container's borders, in other skins it should align with the input + // element (not the search form, as that would leave the buttons + // vertically between the input and the suggestions). + $searchRegion = $( '#simpleSearch, #searchInput' ).first(), + $searchInput = $( '#searchInput' ); + + // Compatibility map + map = { + browsers: { + // Left-to-right languages + ltr: { + // SimpleSearch is broken in Opera < 9.6 + opera: [['>=', 9.6]], + docomo: false, + blackberry: false, + ipod: false, + iphone: false + }, + // Right-to-left languages + rtl: { + opera: [['>=', 9.6]], + docomo: false, + blackberry: false, + ipod: false, + iphone: false + } + } + }; + + if ( !$.client.test( map ) ) { + return; + } + + // General suggestions functionality for all search boxes + searchboxesSelectors = [ + // Primary searchbox on every page in standard skins + '#searchInput', + // Secondary searchbox in legacy skins (LegacyTemplate::searchForm uses id "searchInput + unique id") + '#searchInput2', + // Special:Search + '#powerSearchText', + '#searchText', + // Generic selector for skins with multiple searchboxes (used by CologneBlue) + '.mw-searchInput' + ]; + $( searchboxesSelectors.join(', ') ) + .suggestions( { + fetch: function ( query ) { + var $el, jqXhr; + + if ( query.length !== 0 ) { + $el = $(this); + jqXhr = $.ajax( { + url: mw.util.wikiScript( 'api' ), + data: { + format: 'json', + action: 'opensearch', + search: query, + namespace: 0, + suggest: '' + }, + dataType: 'json', + success: function ( data ) { + if ( $.isArray( data ) && data.length ) { + $el.suggestions( 'suggestions', data[1] ); + } + } + }); + $el.data( 'request', jqXhr ); + } + }, + cancel: function () { + var jqXhr = $(this).data( 'request' ); + // If the delay setting has caused the fetch to have not even happened + // yet, the jqXHR object will have never been set. + if ( jqXhr && $.isFunction( jqXhr.abort ) ) { + jqXhr.abort(); + $(this).removeData( 'request' ); + } + }, + result: { + select: function ( $input ) { + $input.closest( 'form' ).submit(); + } + }, + delay: 120, + highlightInput: true + } ) + .bind( 'paste cut drop', function () { + // make sure paste and cut events from the mouse and drag&drop events + // trigger the keypress handler and cause the suggestions to update + $( this ).trigger( 'keypress' ); + } ); + + // Ensure that the thing is actually present! + if ( $searchRegion.length === 0 ) { + // Don't try to set anything up if simpleSearch is disabled sitewide. + // The loader code loads us if the option is present, even if we're + // not actually enabled (anymore). + return; + } + + // Placeholder text for search box + $searchInput + .attr( 'placeholder', mw.msg( 'searchsuggest-search' ) ) + .placeholder(); + + // Special suggestions functionality for skin-provided search box + $searchInput.suggestions( { + result: { + select: function ( $input ) { + $input.closest( 'form' ).submit(); + } + }, + special: { + render: function ( query ) { + var $el = this; + if ( $el.children().length === 0 ) { + $el + .append( + $( '<div>' ) + .addClass( 'special-label' ) + .text( mw.msg( 'searchsuggest-containing' ) ), + $( '<div>' ) + .addClass( 'special-query' ) + .text( query ) + .autoEllipsis() + ) + .show(); + } else { + $el.find( '.special-query' ) + .text( query ) + .autoEllipsis(); + } + }, + select: function ( $input ) { + $input.closest( 'form' ).append( + $( '<input type="hidden" name="fulltext" value="1"/>' ) + ); + $input.closest( 'form' ).submit(); + } + }, + $region: $searchRegion + } ); + + // In most skins (at least Monobook and Vector), the font-size is messed up in <body>. + // (they use 2 elements to get a sane font-height). So, instead of making exceptions for + // each skin or adding more stylesheets, just copy it from the active element so auto-fit. + $searchInput + .data( 'suggestions-context' ) + .data.$container + .css( 'fontSize', $searchInput.css( 'fontSize' ) ); + + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.user.js b/resources/mediawiki/mediawiki.user.js index 7f881b0e..5c5c87e2 100644 --- a/resources/mediawiki/mediawiki.user.js +++ b/resources/mediawiki/mediawiki.user.js @@ -2,16 +2,48 @@ * Implementation for mediaWiki.user */ -(function( $ ) { +( function ( mw, $ ) { /** * User object */ function User( options, tokens ) { + var user, callbacks; /* Private Members */ - var that = this; + user = this; + callbacks = {}; + + /** + * Gets the current user's groups or rights. + * @param {String} info: One of 'groups' or 'rights'. + * @param {Function} callback + */ + function getUserInfo( info, callback ) { + var api; + if ( callbacks[info] ) { + callbacks[info].add( callback ); + return; + } + callbacks.rights = $.Callbacks('once memory'); + callbacks.groups = $.Callbacks('once memory'); + callbacks[info].add( callback ); + api = new mw.Api(); + api.get( { + action: 'query', + meta: 'userinfo', + uiprop: 'rights|groups' + } ).always( function ( data ) { + var rights, groups; + if ( data.query && data.query.userinfo ) { + rights = data.query.userinfo.rights; + groups = data.query.userinfo.groups; + } + callbacks.rights.fire( rights || [] ); + callbacks.groups.fire( groups || [] ); + } ); + } /* Public Members */ @@ -25,14 +57,15 @@ * Generates a random user session ID (32 alpha-numeric characters). * * This information would potentially be stored in a cookie to identify a user during a - * session or series of sessions. It's uniqueness should not be depended on. + * session or series of sessions. Its uniqueness should not be depended on. * * @return String: Random set of 32 alpha-numeric characters */ function generateId() { - var id = ''; - var seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - for ( var i = 0, r; i < 32; i++ ) { + var i, r, + id = '', + seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + for ( i = 0; i < 32; i++ ) { r = Math.floor( Math.random() * seed.length ); id += seed.substring( r, r + 1 ); } @@ -44,17 +77,31 @@ * * @return Mixed: User name string or null if users is anonymous */ - this.name = function() { + this.getName = function () { return mw.config.get( 'wgUserName' ); }; /** + * @deprecated since 1.20 use mw.user.getName() instead + */ + this.name = function () { + return this.getName(); + }; + + /** * Checks if the current user is anonymous. * * @return Boolean */ - this.anonymous = function() { - return that.name() ? false : true; + this.isAnon = function () { + return user.getName() === null; + }; + + /** + * @deprecated since 1.20 use mw.user.isAnon() instead + */ + this.anonymous = function () { + return user.isAnon(); }; /** @@ -67,7 +114,7 @@ */ this.sessionId = function () { var sessionId = $.cookie( 'mediaWiki.user.sessionId' ); - if ( typeof sessionId == 'undefined' || sessionId === null ) { + if ( typeof sessionId === 'undefined' || sessionId === null ) { sessionId = generateId(); $.cookie( 'mediaWiki.user.sessionId', sessionId, { 'expires': null, 'path': '/' } ); } @@ -84,16 +131,20 @@ * @return String: User name or random session ID */ this.id = function() { - var name = that.name(); + var id, + name = user.getName(); if ( name ) { return name; } - var id = $.cookie( 'mediaWiki.user.id' ); - if ( typeof id == 'undefined' || id === null ) { + id = $.cookie( 'mediaWiki.user.id' ); + if ( typeof id === 'undefined' || id === null ) { id = generateId(); } // Set cookie if not set, or renew it if already set - $.cookie( 'mediaWiki.user.id', id, { 'expires': 365, 'path': '/' } ); + $.cookie( 'mediaWiki.user.id', id, { + expires: 365, + path: '/' + } ); return id; }; @@ -120,38 +171,41 @@ * 'expires': 7 * } ); */ - this.bucket = function( key, options ) { + this.bucket = function ( key, options ) { + var cookie, parts, version, bucket, + range, k, rand, total; + options = $.extend( { - 'buckets': {}, - 'version': 0, - 'tracked': false, - 'expires': 30 + buckets: {}, + version: 0, + tracked: false, + expires: 30 }, options || {} ); - var cookie = $.cookie( 'mediaWiki.user.bucket:' + key ); - var bucket = null; - var version = 0; + + cookie = $.cookie( 'mediaWiki.user.bucket:' + key ); + // Bucket information is stored as 2 integers, together as version:bucket like: "1:2" if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) > 0 ) { - var parts = cookie.split( ':' ); - if ( parts.length > 1 && parts[0] == options.version ) { + parts = cookie.split( ':' ); + if ( parts.length > 1 && Number( parts[0] ) === options.version ) { version = Number( parts[0] ); bucket = String( parts[1] ); } } - if ( bucket === null ) { + if ( bucket === undefined ) { if ( !$.isPlainObject( options.buckets ) ) { throw 'Invalid buckets error. Object expected for options.buckets.'; } version = Number( options.version ); // Find range - var range = 0, k; + range = 0; for ( k in options.buckets ) { range += options.buckets[k]; } // Select random value within range - var rand = Math.random() * range; + rand = Math.random() * range; // Determine which bucket the value landed in - var total = 0; + total = 0; for ( k in options.buckets ) { bucket = k; total += options.buckets[k]; @@ -160,7 +214,7 @@ } } if ( options.tracked ) { - mw.loader.using( 'jquery.clickTracking', function() { + mw.loader.using( 'jquery.clickTracking', function () { $.trackAction( 'mediaWiki.user.bucket:' + key + '@' + version + ':' + bucket ); @@ -174,10 +228,24 @@ } return bucket; }; + + /** + * Gets the current user's groups. + */ + this.getGroups = function ( callback ) { + getUserInfo( 'groups', callback ); + }; + + /** + * Gets the current user's rights. + */ + this.getRights = function ( callback ) { + getUserInfo( 'rights', callback ); + }; } // Extend the skeleton mw.user from mediawiki.js // This is kind of ugly but we're stuck with this for b/c reasons mw.user = new User( mw.user.options, mw.user.tokens ); -})(jQuery);
\ No newline at end of file +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.util.js b/resources/mediawiki/mediawiki.util.js index 0a95d102..29284384 100644 --- a/resources/mediawiki/mediawiki.util.js +++ b/resources/mediawiki/mediawiki.util.js @@ -1,8 +1,8 @@ /** * Implements mediaWiki.util library */ -( function ( $, mw ) { - "use strict"; +( function ( mw, $ ) { + 'use strict'; // Local cache and alias var util = { @@ -14,12 +14,6 @@ init: function () { var profile, $tocTitle, $tocToggleLink, hideTocCookie; - /* Set up $.messageBox */ - $.messageBoxNew( { - id: 'mw-js-message', - parent: '#content' - } ); - /* Set tooltipAccessKeyPrefix */ profile = $.client.profile(); @@ -48,7 +42,11 @@ && profile.name === 'safari' && profile.layoutVersion > 526 ) { util.tooltipAccessKeyPrefix = 'ctrl-alt-'; - + // Firefox 14+ on Mac + } else if ( profile.platform === 'mac' + && profile.name === 'firefox' + && profile.versionNumber >= 14 ) { + util.tooltipAccessKeyPrefix = 'ctrl-option-'; // Safari/Konqueror on any platform, or any browser on Mac // (but not Safari on Windows) } else if ( !( profile.platform === 'win' && profile.name === 'safari' ) @@ -63,25 +61,49 @@ } /* Fill $content var */ - if ( $( '#bodyContent' ).length ) { - // Vector, Monobook, Chick etc. - util.$content = $( '#bodyContent' ); - - } else if ( $( '#mw_contentholder' ).length ) { - // Modern - util.$content = $( '#mw_contentholder' ); - - } else if ( $( '#article' ).length ) { - // Standard, CologneBlue - util.$content = $( '#article' ); + util.$content = ( function () { + var $content, selectors = [ + // The preferred standard for setting $content (class="mw-body") + // You may also use (class="mw-body mw-body-primary") if you use + // mw-body in multiple locations. + // Or class="mw-body-primary" if you want $content to be deeper + // in the dom than mw-body + '.mw-body-primary', + '.mw-body', + + /* Legacy fallbacks for setting the content */ + // Vector, Monobook, Chick, etc... based skins + '#bodyContent', + + // Modern based skins + '#mw_contentholder', + + // Standard, CologneBlue + '#article', + + // #content is present on almost all if not all skins. Most skins (the above cases) + // have #content too, but as an outer wrapper instead of the article text container. + // The skins that don't have an outer wrapper do have #content for everything + // so it's a good fallback + '#content', + + // If nothing better is found fall back to our bodytext div that is guaranteed to be here + '#mw-content-text', + + // Should never happen... well, it could if someone is not finished writing a skin and has + // not inserted bodytext yet. But in any case <body> should always exist + 'body' + ]; + for ( var i = 0, l = selectors.length; i < l; i++ ) { + $content = $( selectors[i] ).first(); + if ( $content.length ) { + return $content; + } + } - } else { - // #content is present on almost all if not all skins. Most skins (the above cases) - // have #content too, but as an outer wrapper instead of the article text container. - // The skins that don't have an outer wrapper do have #content for everything - // so it's a good fallback - util.$content = $( '#content' ); - } + // Make sure we don't unset util.$content if it was preset and we don't find anything + return util.$content; + } )(); // Table of contents toggle $tocTitle = $( '#toctitle' ); @@ -150,32 +172,41 @@ * Get address to a script in the wiki root. * For index.php use mw.config.get( 'wgScript' ) * + * @since 1.18 * @param str string Name of script (eg. 'api'), defaults to 'index' * @return string Address to script (eg. '/w/api.php' ) */ wikiScript: function ( str ) { - return mw.config.get( 'wgScriptPath' ) + '/' + ( str || 'index' ) + - mw.config.get( 'wgScriptExtension' ); + str = str || 'index'; + if ( str === 'index' ) { + return mw.config.get( 'wgScript' ); + } else if ( str === 'load' ) { + return mw.config.get( 'wgLoadScript' ); + } else { + return mw.config.get( 'wgScriptPath' ) + '/' + str + + mw.config.get( 'wgScriptExtension' ); + } }, /** - * Append a new style block to the head + * Append a new style block to the head and return the CSSStyleSheet object. + * Use .ownerNode to access the <style> element, or use mw.loader.addStyleTag. + * This function returns the styleSheet object for convience (due to cross-browsers + * difference as to where it is located). + * @example + * <code> + * var sheet = mw.util.addCSS('.foobar { display: none; }'); + * $(foo).click(function () { + * // Toggle the sheet on and off + * sheet.disabled = !sheet.disabled; + * }); + * </code> * * @param text string CSS to be appended - * @return CSSStyleSheet + * @return CSSStyleSheet (use .ownerNode to get to the <style> element) */ addCSS: function ( text ) { - var s = document.createElement( 'style' ); - s.type = 'text/css'; - s.rel = 'stylesheet'; - // Insert into document before setting cssText (bug 33305) - document.getElementsByTagName('head')[0].appendChild( s ); - if ( s.styleSheet ) { - s.styleSheet.cssText = text; // IE - } else { - // Safari sometimes borks on null - s.appendChild( document.createTextNode( String( text ) ) ); - } + var s = mw.loader.addStyleTag( text ); return s.sheet || s; }, @@ -231,7 +262,7 @@ // Get last match, stop at hash var re = new RegExp( '^[^#]*[&?]' + $.escapeRE( param ) + '=([^&#]*)' ), m = re.exec( url ); - if ( m && m.length > 1 ) { + if ( m ) { // Beware that decodeURIComponent is not required to understand '+' // by spec, as encodeURIComponent does not produce it. return decodeURIComponent( m[1].replace( /\+/g, '%20' ) ); @@ -282,7 +313,7 @@ /* * @var jQuery - * A jQuery object that refers to the page-content element + * A jQuery object that refers to the content area element * Populated by init(). */ $content: null, @@ -353,20 +384,21 @@ return null; } // Select the first (most likely only) unordered list inside the portlet - $ul = $portlet.find( 'ul' ); + $ul = $portlet.find( 'ul' ).eq( 0 ); // If it didn't have an unordered list yet, create it if ( $ul.length === 0 ) { + + $ul = $( '<ul>' ); + // If there's no <div> inside, append it to the portlet directly if ( $portlet.find( 'div:first' ).length === 0 ) { - $portlet.append( '<ul></ul>' ); + $portlet.append( $ul ); } else { // otherwise if there's a div (such as div.body or div.pBody) // append the <ul> to last (most likely only) div - $portlet.find( 'div' ).eq( -1 ).append( '<ul></ul>' ); + $portlet.find( 'div' ).eq( -1 ).append( $ul ); } - // Select the created element - $ul = $portlet.find( 'ul' ).eq( 0 ); } // Just in case.. if ( $ul.length === 0 ) { @@ -424,43 +456,18 @@ * Calling with no arguments, with an empty string or null will hide the message * * @param message {mixed} The DOM-element, jQuery object or HTML-string to be put inside the message box. - * @param className {String} Used in adding a class; should be different for each call * to allow CSS/JS to hide different boxes. null = no class used. - * @return {Boolean} True on success, false on failure. + * @depreceated Use mw.notify */ - jsMessage: function ( message, className ) { + jsMessage: function ( message ) { if ( !arguments.length || message === '' || message === null ) { - $( '#mw-js-message' ).empty().hide(); - return true; // Emptying and hiding message is intended behaviour, return true - - } else { - // We special-case skin structures provided by the software. Skins that - // choose to abandon or significantly modify our formatting can just define - // an mw-js-message div to start with. - var $messageDiv = $( '#mw-js-message' ); - if ( !$messageDiv.length ) { - $messageDiv = $( '<div id="mw-js-message"></div>' ); - if ( util.$content.parent().length ) { - util.$content.parent().prepend( $messageDiv ); - } else { - return false; - } - } - - if ( className ) { - $messageDiv.prop( 'class', 'mw-js-message-' + className ); - } - - if ( typeof message === 'object' ) { - $messageDiv.empty(); - $messageDiv.append( message ); - } else { - $messageDiv.html( message ); - } - - $messageDiv.slideDown(); return true; } + if ( typeof message !== 'object' ) { + message = $.parseHTML( message ); + } + mw.notify( message, { autoHide: true, tag: 'legacy' } ); + return true; }, /** @@ -598,4 +605,4 @@ mw.util = util; -} )( jQuery, mediaWiki ); +}( mediaWiki, jQuery ) ); diff --git a/resources/startup.js b/resources/startup.js index 52c320c6..7951af06 100644 --- a/resources/startup.js +++ b/resources/startup.js @@ -10,25 +10,24 @@ * generally useful beyond startup * * jQuery has minimum requirements of: - * * Firefox 2.0+ - * * Internet Explorer 6+ - * * Safari 3+ - * * Opera 9+ - * * Chrome 1+ + * * Internet Explorer 6.0+ + * * Firefox 3.6+ + * * Safari 5.0+ + * * Opera 11+ + * * Chrome */ -var isCompatible = function() { - // IE < 6 +function isCompatible() { + // IE < 6.0 if ( navigator.appVersion.indexOf( 'MSIE' ) !== -1 && parseFloat( navigator.appVersion.split( 'MSIE' )[1] ) < 6 ) { return false; } - // TODO: Firefox < 2 - // TODO: Safari < 3 - // TODO: Opera < 9 - // TODO: Chrome < 1 + // @todo FIXME: Firefox < 3.6 + // @todo FIXME: Safari < 5.0 + // @todo FIXME: Opera < 11 return true; -}; +} /** * The startUp() function will be generated and added here (at the bottom) */ |