From 08aa4418c30cfc18ccc69a0f0f9cb9e17be6c196 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Mon, 12 Aug 2013 09:28:15 +0200 Subject: Update to MediaWiki 1.21.1 --- resources/Resources.php | 118 ++- resources/Resources.php.orig | 968 +++++++++++++++++++++ resources/jquery.effects/jquery.effects.blind.js | 2 +- resources/jquery.effects/jquery.effects.bounce.js | 2 +- resources/jquery.effects/jquery.effects.clip.js | 2 +- resources/jquery.effects/jquery.effects.core.js | 4 +- resources/jquery.effects/jquery.effects.drop.js | 2 +- resources/jquery.effects/jquery.effects.explode.js | 2 +- resources/jquery.effects/jquery.effects.fade.js | 2 +- resources/jquery.effects/jquery.effects.fold.js | 2 +- .../jquery.effects/jquery.effects.highlight.js | 2 +- resources/jquery.effects/jquery.effects.pulsate.js | 2 +- resources/jquery.effects/jquery.effects.scale.js | 2 +- resources/jquery.effects/jquery.effects.shake.js | 2 +- resources/jquery.effects/jquery.effects.slide.js | 2 +- .../jquery.effects/jquery.effects.transfer.js | 2 +- .../jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js | 4 +- .../jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js | 4 +- .../jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js | 4 +- resources/jquery.ui/jquery.ui.accordion.js | 4 +- resources/jquery.ui/jquery.ui.autocomplete.js | 2 +- resources/jquery.ui/jquery.ui.button.js | 2 +- resources/jquery.ui/jquery.ui.core.js | 4 +- resources/jquery.ui/jquery.ui.datepicker.js | 6 +- resources/jquery.ui/jquery.ui.dialog.js | 4 +- resources/jquery.ui/jquery.ui.draggable.js | 13 +- resources/jquery.ui/jquery.ui.droppable.js | 11 +- resources/jquery.ui/jquery.ui.mouse.js | 2 +- resources/jquery.ui/jquery.ui.position.js | 2 +- resources/jquery.ui/jquery.ui.progressbar.js | 4 +- resources/jquery.ui/jquery.ui.resizable.js | 4 +- resources/jquery.ui/jquery.ui.selectable.js | 4 +- resources/jquery.ui/jquery.ui.slider.js | 4 +- resources/jquery.ui/jquery.ui.sortable.js | 34 +- resources/jquery.ui/jquery.ui.tabs.js | 4 +- resources/jquery.ui/jquery.ui.widget.js | 2 +- .../themes/default/jquery.ui.accordion.css | 2 +- .../themes/default/jquery.ui.autocomplete.css | 4 +- .../jquery.ui/themes/default/jquery.ui.button.css | 114 ++- .../jquery.ui/themes/default/jquery.ui.core.css | 2 +- .../themes/default/jquery.ui.datepicker.css | 2 +- .../jquery.ui/themes/default/jquery.ui.dialog.css | 2 +- .../themes/default/jquery.ui.progressbar.css | 2 +- .../themes/default/jquery.ui.resizable.css | 2 +- .../themes/default/jquery.ui.selectable.css | 2 +- .../jquery.ui/themes/default/jquery.ui.slider.css | 2 +- .../jquery.ui/themes/default/jquery.ui.tabs.css | 2 +- .../jquery.ui/themes/default/jquery.ui.theme.css | 2 +- .../jquery.ui/themes/vector/jquery.ui.button.css | 148 +++- resources/jquery/jquery.arrowSteps.js | 11 +- resources/jquery/jquery.badge.css | 37 +- resources/jquery/jquery.badge.js | 121 +-- resources/jquery/jquery.byteLimit.js | 5 +- resources/jquery/jquery.checkboxShiftClick.js | 10 +- resources/jquery/jquery.client.js | 152 ++-- resources/jquery/jquery.collapsibleTabs.js | 126 --- resources/jquery/jquery.colorUtil.js | 21 +- resources/jquery/jquery.delayedBind.js | 6 +- resources/jquery/jquery.expandableField.js | 8 +- resources/jquery/jquery.hidpi.js | 117 +++ resources/jquery/jquery.highlightText.js | 2 +- resources/jquery/jquery.jStorage.js | 853 +++++++++++++++--- resources/jquery/jquery.js | 206 +++-- resources/jquery/jquery.json.js | 250 +++--- resources/jquery/jquery.localize.js | 59 +- resources/jquery/jquery.makeCollapsible.js | 621 +++++++------ resources/jquery/jquery.mw-jump.js | 10 +- resources/jquery/jquery.mwExtension.js | 5 +- resources/jquery/jquery.qunit.completenessTest.js | 4 +- resources/jquery/jquery.qunit.css | 15 +- resources/jquery/jquery.qunit.js | 493 +++++++---- resources/jquery/jquery.spinner.js | 2 +- resources/jquery/jquery.suggestions.js | 207 +++-- resources/jquery/jquery.tablesorter.js | 371 +++++--- resources/jquery/jquery.textSelection.js | 111 +-- .../mediawiki.action/mediawiki.action.edit.js | 2 +- .../mediawiki.action.edit.preview.js | 6 + .../mediawiki.action.history.diff.css | 80 +- .../mediawiki.action/mediawiki.action.history.js | 2 +- .../mediawiki.action.view.dblClickEdit.js | 14 +- .../mediawiki.action.view.postEdit.js | 15 + .../mediawiki.action.view.rightClickEdit.js | 14 +- resources/mediawiki.api/mediawiki.api.category.js | 174 ++-- resources/mediawiki.api/mediawiki.api.edit.js | 85 +- resources/mediawiki.api/mediawiki.api.js | 73 +- resources/mediawiki.api/mediawiki.api.parse.js | 39 +- .../mediawiki.api/mediawiki.api.titleblacklist.js | 52 -- resources/mediawiki.api/mediawiki.api.watch.js | 68 +- resources/mediawiki.language/languages/bs.js | 6 +- resources/mediawiki.language/languages/dsb.js | 6 +- resources/mediawiki.language/languages/fi.js | 15 +- resources/mediawiki.language/languages/ga.js | 7 +- resources/mediawiki.language/languages/he.js | 14 +- resources/mediawiki.language/languages/hsb.js | 6 +- resources/mediawiki.language/languages/hu.js | 6 +- resources/mediawiki.language/languages/hy.js | 16 +- resources/mediawiki.language/languages/la.js | 6 +- resources/mediawiki.language/languages/os.js | 29 +- resources/mediawiki.language/languages/ru.js | 46 +- resources/mediawiki.language/languages/sl.js | 6 +- resources/mediawiki.language/languages/uk.js | 28 +- resources/mediawiki.language/mediawiki.cldr.js | 23 +- .../mediawiki.language/mediawiki.language.init.js | 4 +- resources/mediawiki.language/mediawiki.language.js | 51 +- .../mediawiki.language.numbers.js | 243 ++++++ resources/mediawiki.libs/CLDRPluralRuleParser.js | 1 + .../mediawiki.page/mediawiki.page.patrol.ajax.js | 63 ++ resources/mediawiki.page/mediawiki.page.ready.js | 40 +- .../mediawiki.page/mediawiki.page.watch.ajax.js | 17 +- .../mediawiki.special/mediawiki.special.block.js | 78 +- .../mediawiki.special.changeemail.js | 62 +- .../mediawiki.special.changeslist.css | 11 +- .../mediawiki.special.javaScriptTest.js | 2 +- resources/mediawiki.special/mediawiki.special.js | 6 +- .../mediawiki.special.movePage.js | 9 +- .../mediawiki.special.preferences.js | 336 +++---- .../mediawiki.special.recentchanges.js | 31 +- .../mediawiki.special/mediawiki.special.search.js | 90 +- .../mediawiki.special.undelete.js | 13 +- .../mediawiki.special/mediawiki.special.upload.js | 91 +- .../mediawiki.special.userLogin.signup.js | 10 + resources/mediawiki/mediawiki.Title.js | 103 ++- resources/mediawiki/mediawiki.Uri.js | 64 +- resources/mediawiki/mediawiki.debug.css | 1 - resources/mediawiki/mediawiki.debug.js | 6 +- resources/mediawiki/mediawiki.feedback.js | 45 +- resources/mediawiki/mediawiki.hidpi.js | 5 + resources/mediawiki/mediawiki.htmlform.js | 112 ++- resources/mediawiki/mediawiki.jqueryMsg.js | 423 ++++++--- resources/mediawiki/mediawiki.jqueryMsg.peg | 1 + resources/mediawiki/mediawiki.js | 699 ++++++++------- resources/mediawiki/mediawiki.log.js | 2 +- resources/mediawiki/mediawiki.notification.js | 145 +-- resources/mediawiki/mediawiki.notify.js | 13 +- resources/mediawiki/mediawiki.searchSuggest.css | 16 + resources/mediawiki/mediawiki.searchSuggest.js | 110 ++- resources/mediawiki/mediawiki.user.js | 25 +- resources/mediawiki/mediawiki.util.js | 236 +++-- resources/startup.js | 23 +- 139 files changed, 6236 insertions(+), 3132 deletions(-) create mode 100644 resources/Resources.php.orig delete mode 100644 resources/jquery/jquery.collapsibleTabs.js create mode 100644 resources/jquery/jquery.hidpi.js create mode 100644 resources/mediawiki.action/mediawiki.action.view.postEdit.js delete mode 100644 resources/mediawiki.api/mediawiki.api.titleblacklist.js create mode 100644 resources/mediawiki.language/mediawiki.language.numbers.js create mode 100644 resources/mediawiki.page/mediawiki.page.patrol.ajax.js create mode 100644 resources/mediawiki.special/mediawiki.special.userLogin.signup.js create mode 100644 resources/mediawiki/mediawiki.hidpi.js create mode 100644 resources/mediawiki/mediawiki.searchSuggest.css (limited to 'resources') diff --git a/resources/Resources.php b/resources/Resources.php index 0a70c5a2..6205bb91 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1,8 +1,34 @@ array( 'class' => 'ResourceLoaderSiteModule' ), @@ -24,7 +50,16 @@ return array( // Scripts for the dynamic language specific data, like grammar forms. 'mediawiki.language.data' => array( 'class' => 'ResourceLoaderLanguageDataModule' ), - /* Skins */ + /** + * Skins + * Be careful not to add 'scripts' to these modules, + * since they are loaded with OutputPage::addModuleStyles so that the skin styles + * apply without javascript. + * If a skin needs custom js in the interface, register a separate module + * and add it to the load queue with OutputPage::addModules. + * + * See Vector for an example. + */ 'skins.chick' => array( 'styles' => array( 'chick/main.css' => array( 'media' => 'screen, handheld' ) ), @@ -91,6 +126,10 @@ return array( 'vector/screen.css' => array( 'media' => 'screen' ), 'vector/screen-hd.css' => array( 'media' => 'screen and (min-width: 982px)' ), ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.vector.js' => array( 'scripts' => 'vector/vector.js', 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], @@ -101,6 +140,7 @@ return array( 'jquery' => array( 'scripts' => 'resources/jquery/jquery.js', 'debugRaw' => false, + 'targets' => array( 'desktop', 'mobile' ), ), /* jQuery Plugins */ @@ -122,6 +162,7 @@ return array( 'jquery.badge' => array( 'scripts' => 'resources/jquery/jquery.badge.js', 'styles' => 'resources/jquery/jquery.badge.css', + 'dependencies' => 'mediawiki.language', ), 'jquery.byteLength' => array( 'scripts' => 'resources/jquery/jquery.byteLength.js', @@ -135,9 +176,7 @@ return array( ), 'jquery.client' => array( 'scripts' => 'resources/jquery/jquery.client.js', - ), - 'jquery.collapsibleTabs' => array( - 'scripts' => 'resources/jquery/jquery.collapsibleTabs.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.color' => array( 'scripts' => 'resources/jquery/jquery.color.js', @@ -148,6 +187,7 @@ return array( ), 'jquery.cookie' => array( 'scripts' => 'resources/jquery/jquery.cookie.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.delayedBind' => array( 'scripts' => 'resources/jquery/jquery.delayedBind.js', @@ -171,6 +211,10 @@ return array( 'jquery.getAttrs' => array( 'scripts' => 'resources/jquery/jquery.getAttrs.js', ), + 'jquery.hidpi' => array( + 'scripts' => 'resources/jquery/jquery.hidpi.js', + 'targets' => array( 'desktop', 'mobile' ), + ), 'jquery.highlightText' => array( 'scripts' => 'resources/jquery/jquery.highlightText.js', 'dependencies' => 'jquery.mwExtension', @@ -180,6 +224,7 @@ return array( ), 'jquery.json' => array( 'scripts' => 'resources/jquery/jquery.json.js', + 'targets' => array( 'mobile', 'desktop' ), ), 'jquery.localize' => array( 'scripts' => 'resources/jquery/jquery.localize.js', @@ -197,6 +242,7 @@ return array( ), 'jquery.mwExtension' => array( 'scripts' => 'resources/jquery/jquery.mwExtension.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.placeholder' => array( 'scripts' => 'resources/jquery/jquery.placeholder.js', @@ -539,6 +585,7 @@ return array( 'scripts' => 'resources/mediawiki/mediawiki.js', 'debugScripts' => 'resources/mediawiki/mediawiki.log.js', 'debugRaw' => false, + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.api' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.js', @@ -562,13 +609,6 @@ return array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.parse.js', 'dependencies' => 'mediawiki.api', ), - 'mediawiki.api.titleblacklist' => array( - 'scripts' => 'resources/mediawiki.api/mediawiki.api.titleblacklist.js', - 'dependencies' => array( - 'mediawiki.api', - 'mediawiki.Title', - ), - ), 'mediawiki.api.watch' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.watch.js', 'dependencies' => array( @@ -614,6 +654,13 @@ return array( 'feedback-bugnew', ), ), + 'mediawiki.hidpi' => array( + 'scripts' => 'resources/mediawiki/mediawiki.hidpi.js', + 'dependencies' => array( + 'jquery.hidpi', + ), + 'targets' => array( 'desktop', 'mobile' ), + ), 'mediawiki.htmlform' => array( 'scripts' => 'resources/mediawiki/mediawiki.htmlform.js', ), @@ -626,9 +673,11 @@ return array( ), 'mediawiki.notify' => array( 'scripts' => 'resources/mediawiki/mediawiki.notify.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.searchSuggest' => array( 'scripts' => 'resources/mediawiki/mediawiki.searchSuggest.js', + 'styles' => 'resources/mediawiki/mediawiki.searchSuggest.css', 'messages' => array( 'searchsuggest-search', 'searchsuggest-containing', @@ -652,6 +701,8 @@ return array( 'dependencies' => array( 'jquery.cookie', 'mediawiki.api', + 'user.options', + 'user.tokens', ), ), 'mediawiki.util' => array( @@ -664,6 +715,7 @@ return array( ), 'messages' => array( 'showtoc', 'hidetoc' ), 'position' => 'top', // For $wgPreloadJavaScriptMwUtil + 'targets' => array( 'desktop', 'mobile' ), ), /* MediaWiki Action */ @@ -693,7 +745,10 @@ return array( ), 'mediawiki.action.view.dblClickEdit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js', - 'dependencies' => 'mediawiki.util', + 'dependencies' => array( + 'mediawiki.util', + 'mediawiki.page.startup', + ), ), 'mediawiki.action.view.metadata' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.metadata.js', @@ -702,6 +757,10 @@ return array( 'metadata-collapse', ), ), + 'mediawiki.action.view.postEdit' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.postEdit.js', + 'dependencies' => 'jquery.cookie' + ), 'mediawiki.action.view.rightClickEdit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js', ), @@ -713,7 +772,10 @@ return array( /* MediaWiki Language */ 'mediawiki.language' => array( - 'scripts' => 'resources/mediawiki.language/mediawiki.language.js', + 'scripts' => array( + 'resources/mediawiki.language/mediawiki.language.js', + 'resources/mediawiki.language/mediawiki.language.numbers.js' + ), 'languageScripts' => array( 'bs' => 'resources/mediawiki.language/languages/bs.js', 'dsb' => 'resources/mediawiki.language/languages/dsb.js', @@ -731,8 +793,9 @@ return array( ), 'dependencies' => array( 'mediawiki.language.data', - 'mediawiki.cldr' + 'mediawiki.cldr', ), + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.cldr' => array( @@ -740,14 +803,17 @@ return array( 'dependencies' => array( 'mediawiki.libs.pluralruleparser', ), + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.libs.pluralruleparser' => array( 'scripts' => 'resources/mediawiki.libs/CLDRPluralRuleParser.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.language.init' => array( 'scripts' => 'resources/mediawiki.language/mediawiki.language.init.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.jqueryMsg' => array( @@ -756,6 +822,7 @@ return array( 'mediawiki.util', 'mediawiki.language', ), + 'targets' => array( 'desktop', 'mobile' ), ), /* MediaWiki Libs */ @@ -784,6 +851,23 @@ return array( ), 'position' => 'top', ), + 'mediawiki.page.patrol.ajax' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.patrol.ajax.js', + 'dependencies' => array( + 'mediawiki.page.startup', + 'mediawiki.api', + 'mediawiki.util', + 'mediawiki.Title', + 'mediawiki.notify', + 'jquery.spinner', + 'user.tokens' + ), + 'messages' => array( + 'markedaspatrollednotify', + 'markedaspatrollederrornotify', + 'markedaspatrollederror-noautopatrol' + ), + ), 'mediawiki.page.watch.ajax' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.watch.ajax.js', 'dependencies' => array( @@ -869,6 +953,9 @@ return array( ), 'dependencies' => array( 'mediawiki.libs.jpegmeta', 'mediawiki.util' ), ), + 'mediawiki.special.userlogin.signup' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.userLogin.signup.js', + ), 'mediawiki.special.javaScriptTest' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js', 'messages' => array_merge( Skin::getSkinNameMessages(), array( @@ -884,6 +971,7 @@ return array( 'mediawiki.tests.qunit.testrunner' => array( 'scripts' => 'tests/qunit/data/testrunner.js', 'dependencies' => array( + 'jquery.getAttrs', 'jquery.qunit', 'jquery.qunit.completenessTest', 'mediawiki.page.startup', diff --git a/resources/Resources.php.orig b/resources/Resources.php.orig new file mode 100644 index 00000000..0a70c5a2 --- /dev/null +++ b/resources/Resources.php.orig @@ -0,0 +1,968 @@ + array( 'class' => 'ResourceLoaderSiteModule' ), + 'noscript' => array( 'class' => 'ResourceLoaderNoscriptModule' ), + 'startup' => array( 'class' => 'ResourceLoaderStartUpModule' ), + 'filepage' => array( 'class' => 'ResourceLoaderFilePageModule' ), + 'user.groups' => array( 'class' => 'ResourceLoaderUserGroupsModule' ), + + // 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' ), + + // Scripts for the dynamic language specific data, like grammar forms. + 'mediawiki.language.data' => array( 'class' => 'ResourceLoaderLanguageDataModule' ), + + /* Skins */ + + 'skins.chick' => array( + 'styles' => array( 'chick/main.css' => array( 'media' => 'screen, handheld' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.cologneblue' => array( + 'styles' => array( 'cologneblue/screen.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.modern' => array( + 'styles' => array( + 'modern/main.css' => array( 'media' => 'screen' ), + 'modern/print.css' => array( 'media' => 'print' ), + ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.monobook' => array( + 'styles' => array( + 'common/commonElements.css' => array( 'media' => 'screen' ), + 'common/commonContent.css' => array( 'media' => 'screen' ), + 'common/commonInterface.css' => array( 'media' => 'screen' ), + 'monobook/main.css' => array( 'media' => 'screen' ), + ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.archlinux' => array( + 'styles' => array( + 'common/commonElements.css' => array( 'media' => 'screen' ), + 'common/commonContent.css' => array( 'media' => 'screen' ), + 'common/commonInterface.css' => array( 'media' => 'screen' ), + 'archlinux/main.css' => array( 'media' => 'screen' ), + 'archlinux/archnavbar.css' => array( 'media' => 'screen' ), + 'archlinux/arch.css' => array( 'media' => 'screen' ), + 'archlinux/print.css' => array( 'media' => 'print' ), + ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.nostalgia' => array( + 'styles' => array( 'nostalgia/screen.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.simple' => array( + 'styles' => array( 'simple/main.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.standard' => array( + 'styles' => array( 'standard/main.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.vector' => array( + // Keep in sync with WebInstallerOutput::getCSS() + 'styles' => array( + 'common/commonElements.css' => array( 'media' => 'screen' ), + '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'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + + /* jQuery */ + + 'jquery' => array( + 'scripts' => 'resources/jquery/jquery.js', + 'debugRaw' => false, + ), + + /* jQuery Plugins */ + + 'jquery.appear' => array( + 'scripts' => 'resources/jquery/jquery.appear.js', + ), + 'jquery.arrowSteps' => array( + 'scripts' => 'resources/jquery/jquery.arrowSteps.js', + 'styles' => 'resources/jquery/jquery.arrowSteps.css', + ), + 'jquery.async' => array( + 'scripts' => 'resources/jquery/jquery.async.js', + ), + 'jquery.autoEllipsis' => 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', + ), + 'jquery.byteLimit' => array( + 'scripts' => 'resources/jquery/jquery.byteLimit.js', + 'dependencies' => 'jquery.byteLength', + ), + 'jquery.checkboxShiftClick' => array( + 'scripts' => 'resources/jquery/jquery.checkboxShiftClick.js', + ), + 'jquery.client' => array( + 'scripts' => 'resources/jquery/jquery.client.js', + ), + 'jquery.collapsibleTabs' => array( + 'scripts' => 'resources/jquery/jquery.collapsibleTabs.js', + ), + 'jquery.color' => array( + 'scripts' => 'resources/jquery/jquery.color.js', + 'dependencies' => 'jquery.colorUtil', + ), + 'jquery.colorUtil' => array( + 'scripts' => 'resources/jquery/jquery.colorUtil.js', + ), + 'jquery.cookie' => array( + 'scripts' => 'resources/jquery/jquery.cookie.js', + ), + 'jquery.delayedBind' => array( + 'scripts' => 'resources/jquery/jquery.delayedBind.js', + ), + 'jquery.expandableField' => array( + 'scripts' => 'resources/jquery/jquery.expandableField.js', + 'dependencies' => 'jquery.delayedBind', + ), + 'jquery.farbtastic' => array( + 'scripts' => 'resources/jquery/jquery.farbtastic.js', + 'styles' => 'resources/jquery/jquery.farbtastic.css', + 'dependencies' => 'jquery.colorUtil', + ), + 'jquery.footHovzer' => array( + 'scripts' => 'resources/jquery/jquery.footHovzer.js', + 'styles' => 'resources/jquery/jquery.footHovzer.css', + ), + 'jquery.form' => array( + 'scripts' => 'resources/jquery/jquery.form.js', + ), + 'jquery.getAttrs' => array( + 'scripts' => 'resources/jquery/jquery.getAttrs.js', + ), + 'jquery.highlightText' => array( + 'scripts' => 'resources/jquery/jquery.highlightText.js', + 'dependencies' => 'jquery.mwExtension', + ), + 'jquery.hoverIntent' => array( + 'scripts' => 'resources/jquery/jquery.hoverIntent.js', + ), + 'jquery.json' => array( + 'scripts' => 'resources/jquery/jquery.json.js', + ), + 'jquery.localize' => array( + 'scripts' => 'resources/jquery/jquery.localize.js', + ), + 'jquery.makeCollapsible' => array( + 'scripts' => 'resources/jquery/jquery.makeCollapsible.js', + 'styles' => 'resources/jquery/jquery.makeCollapsible.css', + 'messages' => array( 'collapsible-expand', 'collapsible-collapse' ), + ), + 'jquery.mockjax' => array( + 'scripts' => 'resources/jquery/jquery.mockjax.js', + ), + 'jquery.mw-jump' => array( + 'scripts' => 'resources/jquery/jquery.mw-jump.js', + ), + 'jquery.mwExtension' => array( + 'scripts' => 'resources/jquery/jquery.mwExtension.js', + ), + 'jquery.placeholder' => array( + 'scripts' => 'resources/jquery/jquery.placeholder.js', + ), + 'jquery.qunit' => array( + 'scripts' => 'resources/jquery/jquery.qunit.js', + 'styles' => 'resources/jquery/jquery.qunit.css', + 'position' => 'top', + ), + 'jquery.qunit.completenessTest' => array( + 'scripts' => 'resources/jquery/jquery.qunit.completenessTest.js', + 'dependencies' => 'jquery.qunit', + ), + 'jquery.spinner' => 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', + 'dependencies' => 'jquery.autoEllipsis', + ), + 'jquery.tabIndex' => array( + 'scripts' => 'resources/jquery/jquery.tabIndex.js', + ), + 'jquery.tablesorter' => 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', + ), + 'jquery.xmldom' => array( + 'scripts' => 'resources/jquery/jquery.xmldom.js', + ), + + /* jQuery Tipsy */ + + 'jquery.tipsy' => array( + 'scripts' => 'resources/jquery.tipsy/jquery.tipsy.js', + 'styles' => 'resources/jquery.tipsy/jquery.tipsy.css', + ), + + /* jQuery UI */ + + // Core + 'jquery.ui.core' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.core.js', + 'skinStyles' => array( + 'default' => array( + 'resources/jquery.ui/themes/default/jquery.ui.core.css', + 'resources/jquery.ui/themes/default/jquery.ui.theme.css', + ), + 'vector' => array( + 'resources/jquery.ui/themes/vector/jquery.ui.core.css', + 'resources/jquery.ui/themes/vector/jquery.ui.theme.css', + ), + ), + 'dependencies' => 'jquery', + 'group' => 'jquery.ui', + ), + 'jquery.ui.widget' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.widget.js', + 'group' => 'jquery.ui', + ), + 'jquery.ui.mouse' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.mouse.js', + 'dependencies' => 'jquery.ui.widget', + 'group' => 'jquery.ui', + ), + 'jquery.ui.position' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.position.js', + 'group' => 'jquery.ui', + ), + // Interactions + 'jquery.ui.draggable' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.draggable.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.mouse', 'jquery.ui.widget' ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.droppable' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.droppable.js', + 'dependencies' => array( + 'jquery.ui.core', 'jquery.ui.mouse', 'jquery.ui.widget', 'jquery.ui.draggable', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.resizable' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.resizable.js', + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.resizable.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.resizable.css', + ), + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.mouse' ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.selectable' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.selectable.js', + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.selectable.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.selectable.css', + ), + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.mouse' ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.sortable' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.sortable.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.mouse' ), + 'group' => 'jquery.ui', + ), + // Widgets + 'jquery.ui.accordion' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.accordion.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.accordion.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.accordion.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.autocomplete' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.autocomplete.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.position' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.autocomplete.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.autocomplete.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.button' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.button.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.button.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.button.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.datepicker' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.datepicker.js', + 'dependencies' => 'jquery.ui.core', + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.datepicker.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.datepicker.css', + ), + 'languageScripts' => array( + 'af' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-af.js', + 'ar' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ar.js', + 'az' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-az.js', + 'bg' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-bg.js', + 'bs' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-bs.js', + 'ca' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ca.js', + 'cs' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-cs.js', + 'da' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-da.js', + 'de' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-de.js', + 'el' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-el.js', + 'en-gb' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-en-GB.js', + 'eo' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-eo.js', + 'es' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-es.js', + 'et' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-et.js', + 'eu' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-eu.js', + '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' => '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', + 'id' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-id.js', + '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', + 'lv' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-lv.js', + 'mk' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-mk.js', + 'ml' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ml.js', + 'ms' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ms.js', + 'nl' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-nl.js', + 'no' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-no.js', + 'pl' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-pl.js', + 'pt' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-pt.js', + 'pt-br' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-pt-BR.js', + 'rm' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-rm.js', + 'ro' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ro.js', + 'ru' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ru.js', + 'sk' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sk.js', + 'sl' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sl.js', + 'sq' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sq.js', + 'sr-sr' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sr-SR.js', + 'sr' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sr.js', + 'sv' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-sv.js', + 'ta' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-ta.js', + 'th' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-th.js', + 'tr' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-tr.js', + 'uk' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-uk.js', + 'vi' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-vi.js', + 'zh-cn' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js', + 'zh-hk' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js', + 'zh-tw' => 'resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.dialog' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.dialog.js', + 'dependencies' => array( + 'jquery.ui.core', + 'jquery.ui.widget', + 'jquery.ui.button', + 'jquery.ui.draggable', + 'jquery.ui.mouse', + 'jquery.ui.position', + 'jquery.ui.resizable', + ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.dialog.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.dialog.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.progressbar' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.progressbar.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.progressbar.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.progressbar.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.slider' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.slider.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.mouse' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.slider.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.slider.css', + ), + 'group' => 'jquery.ui', + ), + 'jquery.ui.tabs' => array( + 'scripts' => 'resources/jquery.ui/jquery.ui.tabs.js', + 'dependencies' => array( 'jquery.ui.core', 'jquery.ui.widget' ), + 'skinStyles' => array( + 'default' => 'resources/jquery.ui/themes/default/jquery.ui.tabs.css', + 'vector' => 'resources/jquery.ui/themes/vector/jquery.ui.tabs.css', + ), + 'group' => 'jquery.ui', + ), + // Effects + 'jquery.effects.core' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.core.js', + 'dependencies' => 'jquery', + 'group' => 'jquery.ui', + ), + 'jquery.effects.blind' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.blind.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.bounce' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.bounce.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.clip' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.clip.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.drop' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.drop.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.explode' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.explode.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.fade' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.fade.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.fold' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.fold.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.highlight' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.highlight.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.pulsate' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.pulsate.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.scale' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.scale.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.shake' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.shake.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.slide' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.slide.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + 'jquery.effects.transfer' => array( + 'scripts' => 'resources/jquery.effects/jquery.effects.transfer.js', + 'dependencies' => 'jquery.effects.core', + 'group' => 'jquery.ui', + ), + + /* MediaWiki */ + + 'mediawiki' => array( + 'scripts' => 'resources/mediawiki/mediawiki.js', + 'debugScripts' => 'resources/mediawiki/mediawiki.log.js', + 'debugRaw' => false, + ), + 'mediawiki.api' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.js', + 'dependencies' => 'mediawiki.util', + ), + 'mediawiki.api.category' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.category.js', + 'dependencies' => array( + 'mediawiki.api', + 'mediawiki.Title', + ), + ), + 'mediawiki.api.edit' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.edit.js', + 'dependencies' => array( + 'mediawiki.api', + 'mediawiki.Title', + ), + ), + 'mediawiki.api.parse' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.parse.js', + 'dependencies' => 'mediawiki.api', + ), + 'mediawiki.api.titleblacklist' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.titleblacklist.js', + 'dependencies' => array( + 'mediawiki.api', + 'mediawiki.Title', + ), + ), + 'mediawiki.api.watch' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.watch.js', + 'dependencies' => array( + 'mediawiki.api', + 'user.tokens', + ), + ), + 'mediawiki.debug' => array( + 'scripts' => 'resources/mediawiki/mediawiki.debug.js', + 'styles' => 'resources/mediawiki/mediawiki.debug.css', + 'dependencies' => 'jquery.footHovzer', + 'position' => 'bottom', + ), + 'mediawiki.debug.init' => array( + 'scripts' => 'resources/mediawiki/mediawiki.debug.init.js', + 'dependencies' => 'mediawiki.debug', + // Uses a custom mw.config variable that is set in debughtml, + // must be loaded on the bottom + 'position' => 'bottom', + ), + 'mediawiki.feedback' => array( + 'scripts' => 'resources/mediawiki/mediawiki.feedback.js', + 'styles' => 'resources/mediawiki/mediawiki.feedback.css', + 'dependencies' => array( + 'mediawiki.api.edit', + 'mediawiki.Title', + 'mediawiki.jqueryMsg', + 'jquery.ui.dialog', + ), + 'messages' => array( + 'feedback-bugornote', + 'feedback-subject', + 'feedback-message', + 'feedback-cancel', + 'feedback-submit', + 'feedback-adding', + 'feedback-error1', + 'feedback-error2', + 'feedback-error3', + 'feedback-thanks', + 'feedback-close', + 'feedback-bugcheck', + 'feedback-bugnew', + ), + ), + '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', + ), + 'mediawiki.Uri' => array( + 'scripts' => 'resources/mediawiki/mediawiki.Uri.js', + ), + 'mediawiki.user' => array( + 'scripts' => 'resources/mediawiki/mediawiki.user.js', + 'dependencies' => array( + 'jquery.cookie', + 'mediawiki.api', + ), + ), + 'mediawiki.util' => array( + 'scripts' => 'resources/mediawiki/mediawiki.util.js', + 'dependencies' => array( + 'jquery.client', + 'jquery.cookie', + 'jquery.mwExtension', + 'mediawiki.notify', + ), + 'messages' => array( 'showtoc', 'hidetoc' ), + 'position' => 'top', // For $wgPreloadJavaScriptMwUtil + ), + + /* MediaWiki Action */ + + 'mediawiki.action.edit' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.js', + 'dependencies' => 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', + 'group' => 'mediawiki.action.history', + ), + 'mediawiki.action.history.diff' => array( + 'styles' => 'resources/mediawiki.action/mediawiki.action.history.diff.css', + 'group' => 'mediawiki.action.history', + ), + 'mediawiki.action.view.dblClickEdit' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js', + 'dependencies' => 'mediawiki.util', + ), + 'mediawiki.action.view.metadata' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.metadata.js', + 'messages' => array( + 'metadata-expand', + 'metadata-collapse', + ), + ), + 'mediawiki.action.view.rightClickEdit' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js', + ), + // Alias for backwards compatibility + 'mediawiki.action.watch.ajax' => array( + 'dependencies' => 'mediawiki.page.watch.ajax' + ), + + /* MediaWiki Language */ + + 'mediawiki.language' => array( + 'scripts' => 'resources/mediawiki.language/mediawiki.language.js', + 'languageScripts' => array( + 'bs' => 'resources/mediawiki.language/languages/bs.js', + 'dsb' => 'resources/mediawiki.language/languages/dsb.js', + 'fi' => 'resources/mediawiki.language/languages/fi.js', + 'ga' => 'resources/mediawiki.language/languages/ga.js', + 'he' => 'resources/mediawiki.language/languages/he.js', + 'hsb' => 'resources/mediawiki.language/languages/hsb.js', + 'hu' => 'resources/mediawiki.language/languages/hu.js', + 'hy' => 'resources/mediawiki.language/languages/hy.js', + 'la' => 'resources/mediawiki.language/languages/la.js', + 'os' => 'resources/mediawiki.language/languages/os.js', + 'ru' => 'resources/mediawiki.language/languages/ru.js', + 'sl' => 'resources/mediawiki.language/languages/sl.js', + 'uk' => 'resources/mediawiki.language/languages/uk.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( + 'scripts' => 'resources/mediawiki/mediawiki.jqueryMsg.js', + 'dependencies' => array( + 'mediawiki.util', + 'mediawiki.language', + ), + ), + + /* MediaWiki Libs */ + + 'mediawiki.libs.jpegmeta' => array( + 'scripts' => 'resources/mediawiki.libs/mediawiki.libs.jpegmeta.js', + ), + + /* MediaWiki Page */ + + 'mediawiki.page.ready' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.ready.js', + 'dependencies' => array( + 'jquery.checkboxShiftClick', + 'jquery.makeCollapsible', + 'jquery.placeholder', + 'jquery.mw-jump', + 'mediawiki.util', + ), + ), + 'mediawiki.page.startup' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.startup.js', + 'dependencies' => array( + 'jquery.client', + 'mediawiki.util', + ), + '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 */ + + 'mediawiki.special' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.js', + 'styles' => 'resources/mediawiki.special/mediawiki.special.css', + ), + 'mediawiki.special.block' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.block.js', + 'dependencies' => array( + 'mediawiki.util', + ), + ), + 'mediawiki.special.changeemail' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.changeemail.js', + 'styles' => 'resources/mediawiki.special/mediawiki.special.changeemail.css', + 'dependencies' => array( + 'mediawiki.util', + ), + 'messages' => array( + 'email-address-validity-valid', + 'email-address-validity-invalid', + ), + ), + 'mediawiki.special.changeslist' => array( + 'styles' => 'resources/mediawiki.special/mediawiki.special.changeslist.css', + 'dependencies' => array( 'jquery.makeCollapsible' ), + ), + 'mediawiki.special.movePage' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.movePage.js', + 'dependencies' => 'jquery.byteLimit', + ), + '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', + 'dependencies' => array( 'mediawiki.special' ), + 'position' => 'top', + ), + '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', + ), + 'mediawiki.special.upload' => array( + // @TODO: merge in remainder of mediawiki.legacy.upload + 'scripts' => 'resources/mediawiki.special/mediawiki.special.upload.js', + 'messages' => array( + 'widthheight', + 'size-bytes', + 'size-kilobytes', + 'size-megabytes', + 'size-gigabytes', + 'largefileserver', + ), + 'dependencies' => array( 'mediawiki.libs.jpegmeta', 'mediawiki.util' ), + ), + 'mediawiki.special.javaScriptTest' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js', + 'messages' => array_merge( Skin::getSkinNameMessages(), array( + 'colon-separator', + 'javascripttest-pagetext-skins', + ) ), + 'dependencies' => array( 'jquery.qunit' ), + 'position' => 'top', + ), + + /* MediaWiki Tests */ + + 'mediawiki.tests.qunit.testrunner' => array( + 'scripts' => 'tests/qunit/data/testrunner.js', + 'dependencies' => array( + 'jquery.qunit', + 'jquery.qunit.completenessTest', + 'mediawiki.page.startup', + 'mediawiki.page.ready', + ), + 'position' => 'top', + ), + + /* MediaWiki Legacy */ + + 'mediawiki.legacy.ajax' => array( + 'scripts' => 'common/ajax.js', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => array( + 'mediawiki.util', + 'mediawiki.legacy.wikibits', + ), + 'position' => 'top', // Temporary hack for legacy support + ), + 'mediawiki.legacy.commonPrint' => array( + 'styles' => array( 'common/commonPrint.css' => array( 'media' => 'print' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'mediawiki.legacy.config' => array( + 'scripts' => 'common/config.js', + 'styles' => array( 'common/config.css', 'common/config-cc.css' ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => 'mediawiki.legacy.wikibits', + ), + 'mediawiki.legacy.IEFixes' => array( + 'scripts' => 'common/IEFixes.js', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => 'mediawiki.legacy.wikibits', + ), + 'mediawiki.legacy.protect' => array( + 'scripts' => 'common/protect.js', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => array( + 'mediawiki.legacy.wikibits', + 'jquery.byteLimit', + ), + 'position' => 'top', + ), + 'mediawiki.legacy.shared' => array( + 'styles' => array( 'common/shared.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'mediawiki.legacy.oldshared' => array( + 'styles' => array( 'common/oldshared.css' => array( 'media' => 'screen' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'mediawiki.legacy.upload' => array( + 'scripts' => 'common/upload.js', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => array( + 'mediawiki.legacy.wikibits', + 'mediawiki.util', + ), + ), + 'mediawiki.legacy.wikibits' => array( + 'scripts' => 'common/wikibits.js', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'dependencies' => array( + 'mediawiki.util', + ), + 'position' => 'top', + ), + 'mediawiki.legacy.wikiprintable' => array( + 'styles' => array( 'common/wikiprintable.css' => array( 'media' => 'print' ) ), + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), +); diff --git a/resources/jquery.effects/jquery.effects.blind.js b/resources/jquery.effects/jquery.effects.blind.js index 1e997690..ac25bbd8 100644 --- a/resources/jquery.effects/jquery.effects.blind.js +++ b/resources/jquery.effects/jquery.effects.blind.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Blind 1.8.23 + * jQuery UI Effects Blind 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.bounce.js b/resources/jquery.effects/jquery.effects.bounce.js index 7927a4a9..1169d770 100644 --- a/resources/jquery.effects/jquery.effects.bounce.js +++ b/resources/jquery.effects/jquery.effects.bounce.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Bounce 1.8.23 + * jQuery UI Effects Bounce 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.clip.js b/resources/jquery.effects/jquery.effects.clip.js index d8b8218f..edd51a6a 100644 --- a/resources/jquery.effects/jquery.effects.clip.js +++ b/resources/jquery.effects/jquery.effects.clip.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Clip 1.8.23 + * jQuery UI Effects Clip 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.core.js b/resources/jquery.effects/jquery.effects.core.js index 91ac5755..7fd946fd 100644 --- a/resources/jquery.effects/jquery.effects.core.js +++ b/resources/jquery.effects/jquery.effects.core.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects 1.8.23 + * jQuery UI Effects 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -307,7 +307,7 @@ $.fn.extend({ /******************************************************************************/ $.extend($.effects, { - version: "1.8.23", + version: "1.8.24", // Saves a set of properties in a data storage save: function(element, set) { diff --git a/resources/jquery.effects/jquery.effects.drop.js b/resources/jquery.effects/jquery.effects.drop.js index 6d25bd30..97e5abd4 100644 --- a/resources/jquery.effects/jquery.effects.drop.js +++ b/resources/jquery.effects/jquery.effects.drop.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Drop 1.8.23 + * jQuery UI Effects Drop 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.explode.js b/resources/jquery.effects/jquery.effects.explode.js index 1caeca86..f63e47a2 100644 --- a/resources/jquery.effects/jquery.effects.explode.js +++ b/resources/jquery.effects/jquery.effects.explode.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Explode 1.8.23 + * jQuery UI Effects Explode 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.fade.js b/resources/jquery.effects/jquery.effects.fade.js index 61249798..7aa37b1a 100644 --- a/resources/jquery.effects/jquery.effects.fade.js +++ b/resources/jquery.effects/jquery.effects.fade.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Fade 1.8.23 + * jQuery UI Effects Fade 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.fold.js b/resources/jquery.effects/jquery.effects.fold.js index 81b15b83..06cc5533 100644 --- a/resources/jquery.effects/jquery.effects.fold.js +++ b/resources/jquery.effects/jquery.effects.fold.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Fold 1.8.23 + * jQuery UI Effects Fold 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.highlight.js b/resources/jquery.effects/jquery.effects.highlight.js index dee0639f..ad9e7bd4 100644 --- a/resources/jquery.effects/jquery.effects.highlight.js +++ b/resources/jquery.effects/jquery.effects.highlight.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Highlight 1.8.23 + * jQuery UI Effects Highlight 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.pulsate.js b/resources/jquery.effects/jquery.effects.pulsate.js index 45cdc884..d730beed 100644 --- a/resources/jquery.effects/jquery.effects.pulsate.js +++ b/resources/jquery.effects/jquery.effects.pulsate.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Pulsate 1.8.23 + * jQuery UI Effects Pulsate 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.scale.js b/resources/jquery.effects/jquery.effects.scale.js index 44ecee18..52d18710 100644 --- a/resources/jquery.effects/jquery.effects.scale.js +++ b/resources/jquery.effects/jquery.effects.scale.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Scale 1.8.23 + * jQuery UI Effects Scale 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.shake.js b/resources/jquery.effects/jquery.effects.shake.js index bc1fd191..44b8ea44 100644 --- a/resources/jquery.effects/jquery.effects.shake.js +++ b/resources/jquery.effects/jquery.effects.shake.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Shake 1.8.23 + * jQuery UI Effects Shake 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.slide.js b/resources/jquery.effects/jquery.effects.slide.js index 0a430278..502e6c9d 100644 --- a/resources/jquery.effects/jquery.effects.slide.js +++ b/resources/jquery.effects/jquery.effects.slide.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Slide 1.8.23 + * jQuery UI Effects Slide 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.effects/jquery.effects.transfer.js b/resources/jquery.effects/jquery.effects.transfer.js index 64f2a17b..4ee4ae88 100644 --- a/resources/jquery.effects/jquery.effects.transfer.js +++ b/resources/jquery.effects/jquery.effects.transfer.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Transfer 1.8.23 + * jQuery UI Effects Transfer 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js index 6c4883f5..83f2825c 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js @@ -8,8 +8,8 @@ jQuery(function($){ currentText: '今天', monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'], - monthNamesShort: ['一','二','三','四','五','六', - '七','八','九','十','十一','十二'], + monthNamesShort: ['一月','二月','三月','四月','五月','六月', + '七月','八月','九月','十月','十一月','十二月'], dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'], dayNamesMin: ['日','一','二','三','四','五','六'], diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js index 06c4c628..11189d3a 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js @@ -8,8 +8,8 @@ jQuery(function($){ currentText: '今天', monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'], - monthNamesShort: ['一','二','三','四','五','六', - '七','八','九','十','十一','十二'], + monthNamesShort: ['一月','二月','三月','四月','五月','六月', + '七月','八月','九月','十月','十一月','十二月'], dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'], dayNamesMin: ['日','一','二','三','四','五','六'], diff --git a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js index dd51e359..089498b4 100644 --- a/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js +++ b/resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js @@ -8,8 +8,8 @@ jQuery(function($){ currentText: '今天', monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'], - monthNamesShort: ['一','二','三','四','五','六', - '七','八','九','十','十一','十二'], + monthNamesShort: ['一月','二月','三月','四月','五月','六月', + '七月','八月','九月','十月','十一月','十二月'], dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'], dayNamesMin: ['日','一','二','三','四','五','六'], diff --git a/resources/jquery.ui/jquery.ui.accordion.js b/resources/jquery.ui/jquery.ui.accordion.js index b3340e09..dc1ba60a 100644 --- a/resources/jquery.ui/jquery.ui.accordion.js +++ b/resources/jquery.ui/jquery.ui.accordion.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Accordion 1.8.23 + * jQuery UI Accordion 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -518,7 +518,7 @@ $.widget( "ui.accordion", { }); $.extend( $.ui.accordion, { - version: "1.8.23", + version: "1.8.24", 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 b634cce5..8d69be28 100644 --- a/resources/jquery.ui/jquery.ui.autocomplete.js +++ b/resources/jquery.ui/jquery.ui.autocomplete.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Autocomplete 1.8.23 + * jQuery UI Autocomplete 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/jquery.ui.button.js b/resources/jquery.ui/jquery.ui.button.js index db2637e8..8326262c 100644 --- a/resources/jquery.ui/jquery.ui.button.js +++ b/resources/jquery.ui/jquery.ui.button.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Button 1.8.23 + * jQuery UI Button 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/jquery.ui.core.js b/resources/jquery.ui/jquery.ui.core.js index 1285a6dd..b36c1ac4 100644 --- a/resources/jquery.ui/jquery.ui.core.js +++ b/resources/jquery.ui/jquery.ui.core.js @@ -1,5 +1,5 @@ /*! - * jQuery UI 1.8.23 + * jQuery UI 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -18,7 +18,7 @@ if ( $.ui.version ) { } $.extend( $.ui, { - version: "1.8.23", + version: "1.8.24", keyCode: { ALT: 18, diff --git a/resources/jquery.ui/jquery.ui.datepicker.js b/resources/jquery.ui/jquery.ui.datepicker.js index 7ea5b079..1fcea12a 100644 --- a/resources/jquery.ui/jquery.ui.datepicker.js +++ b/resources/jquery.ui/jquery.ui.datepicker.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Datepicker 1.8.23 + * jQuery UI Datepicker 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -12,7 +12,7 @@ */ (function( $, undefined ) { -$.extend($.ui, { datepicker: { version: "1.8.23" } }); +$.extend($.ui, { datepicker: { version: "1.8.24" } }); var PROP_NAME = 'datepicker'; var dpuuid = new Date().getTime(); @@ -1845,7 +1845,7 @@ $.fn.datepicker = function(options){ $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); -$.datepicker.version = "1.8.23"; +$.datepicker.version = "1.8.24"; // 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 082bf2c0..06b85f23 100644 --- a/resources/jquery.ui/jquery.ui.dialog.js +++ b/resources/jquery.ui/jquery.ui.dialog.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Dialog 1.8.23 + * jQuery UI Dialog 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -690,7 +690,7 @@ $.widget("ui.dialog", { }); $.extend($.ui.dialog, { - version: "1.8.23", + version: "1.8.24", uuid: 0, maxZ: 0, diff --git a/resources/jquery.ui/jquery.ui.draggable.js b/resources/jquery.ui/jquery.ui.draggable.js index 6da1aafe..149035c2 100644 --- a/resources/jquery.ui/jquery.ui.draggable.js +++ b/resources/jquery.ui/jquery.ui.draggable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Draggable 1.8.23 + * jQuery UI Draggable 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -237,11 +237,10 @@ $.widget("ui.draggable", $.ui.mouse, { }, _mouseUp: function(event) { - if (this.options.iframeFix === true) { - $("div.ui-draggable-iframeFix").each(function() { - this.parentNode.removeChild(this); - }); //Remove frame helpers - } + //Remove frame helpers + $("div.ui-draggable-iframeFix").each(function() { + this.parentNode.removeChild(this); + }); //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event); @@ -513,7 +512,7 @@ $.widget("ui.draggable", $.ui.mouse, { }); $.extend($.ui.draggable, { - version: "1.8.23" + version: "1.8.24" }); $.ui.plugin.add("draggable", "connectToSortable", { diff --git a/resources/jquery.ui/jquery.ui.droppable.js b/resources/jquery.ui/jquery.ui.droppable.js index 4b98b3ad..f17c2223 100644 --- a/resources/jquery.ui/jquery.ui.droppable.js +++ b/resources/jquery.ui/jquery.ui.droppable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Droppable 1.8.23 + * jQuery UI Droppable 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -147,7 +147,7 @@ $.widget("ui.droppable", { }); $.extend($.ui.droppable, { - version: "1.8.23" + version: "1.8.24" }); $.ui.intersect = function(draggable, droppable, toleranceMode) { @@ -260,7 +260,12 @@ $.ui.ddmanager = { var parentInstance; if (this.options.greedy) { - var parent = this.element.parents(':data(droppable):eq(0)'); + // find droppable parents with same scope + var scope = this.options.scope; + var parent = this.element.parents(':data(droppable)').filter(function () { + return $.data(this, 'droppable').options.scope === scope; + }); + if (parent.length) { parentInstance = $.data(parent[0], 'droppable'); parentInstance.greedyChild = (c == 'isover' ? 1 : 0); diff --git a/resources/jquery.ui/jquery.ui.mouse.js b/resources/jquery.ui/jquery.ui.mouse.js index e051055d..52a1786c 100644 --- a/resources/jquery.ui/jquery.ui.mouse.js +++ b/resources/jquery.ui/jquery.ui.mouse.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Mouse 1.8.23 + * jQuery UI Mouse 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/jquery.ui.position.js b/resources/jquery.ui/jquery.ui.position.js index 03f2606c..8b20179b 100644 --- a/resources/jquery.ui/jquery.ui.position.js +++ b/resources/jquery.ui/jquery.ui.position.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Position 1.8.23 + * jQuery UI Position 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/jquery.ui.progressbar.js b/resources/jquery.ui/jquery.ui.progressbar.js index c1d9f3c2..7cea1baa 100644 --- a/resources/jquery.ui/jquery.ui.progressbar.js +++ b/resources/jquery.ui/jquery.ui.progressbar.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Progressbar 1.8.23 + * jQuery UI Progressbar 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -103,7 +103,7 @@ $.widget( "ui.progressbar", { }); $.extend( $.ui.progressbar, { - version: "1.8.23" + version: "1.8.24" }); })( jQuery ); diff --git a/resources/jquery.ui/jquery.ui.resizable.js b/resources/jquery.ui/jquery.ui.resizable.js index f6ce6949..6cc6f41f 100644 --- a/resources/jquery.ui/jquery.ui.resizable.js +++ b/resources/jquery.ui/jquery.ui.resizable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Resizable 1.8.23 + * jQuery UI Resizable 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -540,7 +540,7 @@ $.widget("ui.resizable", $.ui.mouse, { }); $.extend($.ui.resizable, { - version: "1.8.23" + version: "1.8.24" }); /* diff --git a/resources/jquery.ui/jquery.ui.selectable.js b/resources/jquery.ui/jquery.ui.selectable.js index ac5bf046..44c6e8c5 100644 --- a/resources/jquery.ui/jquery.ui.selectable.js +++ b/resources/jquery.ui/jquery.ui.selectable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Selectable 1.8.23 + * jQuery UI Selectable 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -261,7 +261,7 @@ $.widget("ui.selectable", $.ui.mouse, { }); $.extend($.ui.selectable, { - version: "1.8.23" + version: "1.8.24" }); })(jQuery); diff --git a/resources/jquery.ui/jquery.ui.slider.js b/resources/jquery.ui/jquery.ui.slider.js index 5ea589e6..c554e783 100644 --- a/resources/jquery.ui/jquery.ui.slider.js +++ b/resources/jquery.ui/jquery.ui.slider.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Slider 1.8.23 + * jQuery UI Slider 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -656,7 +656,7 @@ $.widget( "ui.slider", $.ui.mouse, { }); $.extend( $.ui.slider, { - version: "1.8.23" + version: "1.8.24" }); }(jQuery)); diff --git a/resources/jquery.ui/jquery.ui.sortable.js b/resources/jquery.ui/jquery.ui.sortable.js index 1d87f653..9e0cac68 100644 --- a/resources/jquery.ui/jquery.ui.sortable.js +++ b/resources/jquery.ui/jquery.ui.sortable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Sortable 1.8.23 + * jQuery UI Sortable 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -296,7 +296,16 @@ $.widget("ui.sortable", $.ui.mouse, { var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); if (!intersection) continue; - if(itemElement != this.currentItem[0] //cannot intersect with itself + // Only put the placeholder inside the current Container, skip all + // items form other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this moving items in "sub-sortables" can cause the placeholder to jitter + // beetween the outer and inner container. + if (item.instance !== this.currentContainer) continue; + + if (itemElement != this.currentItem[0] //cannot intersect with itself && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true) @@ -1003,15 +1012,16 @@ $.widget("ui.sortable", $.ui.mouse, { if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed - if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element - if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); - for (var i = this.containers.length - 1; i >= 0; i--){ - if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) { - delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - } - }; - }; + + // Check if the items Container has Changed and trigger appropriate + // events. + if (this !== this.currentContainer) { + if(!noPropagation) { + delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); + delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + } + } //Post events to containers for (var i = this.containers.length - 1; i >= 0; i--){ @@ -1078,7 +1088,7 @@ $.widget("ui.sortable", $.ui.mouse, { }); $.extend($.ui.sortable, { - version: "1.8.23" + version: "1.8.24" }); })(jQuery); diff --git a/resources/jquery.ui/jquery.ui.tabs.js b/resources/jquery.ui/jquery.ui.tabs.js index de453cc3..0c47f0e1 100644 --- a/resources/jquery.ui/jquery.ui.tabs.js +++ b/resources/jquery.ui/jquery.ui.tabs.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Tabs 1.8.23 + * jQuery UI Tabs 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -698,7 +698,7 @@ $.widget( "ui.tabs", { }); $.extend( $.ui.tabs, { - version: "1.8.23" + version: "1.8.24" }); /* diff --git a/resources/jquery.ui/jquery.ui.widget.js b/resources/jquery.ui/jquery.ui.widget.js index befdcc25..66ef0133 100644 --- a/resources/jquery.ui/jquery.ui.widget.js +++ b/resources/jquery.ui/jquery.ui.widget.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Widget 1.8.23 + * jQuery UI Widget 1.8.24 * * Copyright 2012, 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.accordion.css b/resources/jquery.ui/themes/default/jquery.ui.accordion.css index 1ce7d5ea..cd8f971c 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.accordion.css +++ b/resources/jquery.ui/themes/default/jquery.ui.accordion.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Accordion 1.8.23 + * jQuery UI Accordion 1.8.24 * * Copyright 2012, 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.autocomplete.css b/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css index a9817ceb..c7eaff22 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css +++ b/resources/jquery.ui/themes/default/jquery.ui.autocomplete.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Autocomplete 1.8.23 + * jQuery UI Autocomplete 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -13,7 +13,7 @@ * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ /* - * jQuery UI Menu 1.8.23 + * jQuery UI Menu 1.8.24 * * 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 c1f26009..cd2dbb6e 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.button.css +++ b/resources/jquery.ui/themes/default/jquery.ui.button.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Button 1.8.23 + * jQuery UI Button 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. @@ -7,32 +7,104 @@ * * http://docs.jquery.com/UI/Button#theming */ -.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 { + 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 */ +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} + +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.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.4; } -.ui-button-text-only .ui-button-text { padding: .4em 1em; } -.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } -.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } -.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } -.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +.ui-button .ui-button-text { + display: block; + line-height: 1.4; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} /* no icon support for input elements, provide padding by default */ -input.ui-button { padding: .4em 1em; } + input.ui-button { + padding: .4em 1em; +} /*button icon element(s) */ -.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } -.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } -.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } -.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } -.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} /*button sets*/ -.ui-buttonset { margin-right: 7px; } -.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} /* workarounds */ -button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; /* reset extra padding in Firefox */ +} diff --git a/resources/jquery.ui/themes/default/jquery.ui.core.css b/resources/jquery.ui/themes/default/jquery.ui.core.css index c24627e7..8b953a2b 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.core.css +++ b/resources/jquery.ui/themes/default/jquery.ui.core.css @@ -1,5 +1,5 @@ /*! - * jQuery UI CSS Framework 1.8.23 + * jQuery UI CSS Framework 1.8.24 * * Copyright 2012, 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.datepicker.css b/resources/jquery.ui/themes/default/jquery.ui.datepicker.css index 0282eeee..37d3a98e 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.datepicker.css +++ b/resources/jquery.ui/themes/default/jquery.ui.datepicker.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Datepicker 1.8.23 + * jQuery UI Datepicker 1.8.24 * * Copyright 2012, 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.dialog.css b/resources/jquery.ui/themes/default/jquery.ui.dialog.css index ba50ba5a..04515f48 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.dialog.css +++ b/resources/jquery.ui/themes/default/jquery.ui.dialog.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Dialog 1.8.23 + * jQuery UI Dialog 1.8.24 * * Copyright 2012, 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.progressbar.css b/resources/jquery.ui/themes/default/jquery.ui.progressbar.css index c775a33a..90bf3081 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.progressbar.css +++ b/resources/jquery.ui/themes/default/jquery.ui.progressbar.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Progressbar 1.8.23 + * jQuery UI Progressbar 1.8.24 * * Copyright 2012, 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.resizable.css b/resources/jquery.ui/themes/default/jquery.ui.resizable.css index 420c4afa..d17873e7 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.resizable.css +++ b/resources/jquery.ui/themes/default/jquery.ui.resizable.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Resizable 1.8.23 + * jQuery UI Resizable 1.8.24 * * Copyright 2012, 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.selectable.css b/resources/jquery.ui/themes/default/jquery.ui.selectable.css index 33202748..9850ee7e 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.selectable.css +++ b/resources/jquery.ui/themes/default/jquery.ui.selectable.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Selectable 1.8.23 + * jQuery UI Selectable 1.8.24 * * Copyright 2012, 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.slider.css b/resources/jquery.ui/themes/default/jquery.ui.slider.css index 650ad7e2..fbfe6658 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.slider.css +++ b/resources/jquery.ui/themes/default/jquery.ui.slider.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Slider 1.8.23 + * jQuery UI Slider 1.8.24 * * Copyright 2012, 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.tabs.css b/resources/jquery.ui/themes/default/jquery.ui.tabs.css index 64ac9bf5..f0bee7a2 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.tabs.css +++ b/resources/jquery.ui/themes/default/jquery.ui.tabs.css @@ -1,5 +1,5 @@ /*! - * jQuery UI Tabs 1.8.23 + * jQuery UI Tabs 1.8.24 * * Copyright 2012, 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.theme.css b/resources/jquery.ui/themes/default/jquery.ui.theme.css index 536c8e0e..b7d2f617 100644 --- a/resources/jquery.ui/themes/default/jquery.ui.theme.css +++ b/resources/jquery.ui/themes/default/jquery.ui.theme.css @@ -1,5 +1,5 @@ /*! - * jQuery UI CSS Framework 1.8.23 + * jQuery UI CSS Framework 1.8.24 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. diff --git a/resources/jquery.ui/themes/vector/jquery.ui.button.css b/resources/jquery.ui/themes/vector/jquery.ui.button.css index 006bbeac..a6a1b544 100644 --- a/resources/jquery.ui/themes/vector/jquery.ui.button.css +++ b/resources/jquery.ui/themes/vector/jquery.ui.button.css @@ -1,35 +1,103 @@ /* Button ----------------------------------*/ -.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 { + 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; +} /*button text element */ -.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; } +.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; +} /* no icon support for input elements, provide padding by default */ -input.ui-button { padding: 0.3em 1em; } +input.ui-button { + padding: 0.3em 1em; +} /*button icon element(s) */ -.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-text-icon .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -9px; } -.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } -.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icon .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: 0.5em; } -.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icon .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: 0.5em; } +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-text-icon .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -9px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icon .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: 0.5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icon .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: 0.5em; +} /*button sets*/ -.ui-buttonset { margin-right: 7px; } -.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} /* workarounds */ -button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; /* reset extra padding in Firefox */ +} body .ui-button { margin: 0.5em 0 0.5em 0.4em; @@ -46,10 +114,38 @@ body .ui-button { /* 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; } +.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; @@ -66,11 +162,11 @@ body .ui-button.disabled { color: #7f7f7f; border-color: #cccccc; /* @embed */ - background: #f2f2f2 url(images/button-disabled.png) repeat-x scroll 50% 100% !important; + 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 { - border: 0; +body button.ui-button::-moz-focus-inner { + border: 0; } /* Give large buttons some extra padding */ body .ui-button-large { diff --git a/resources/jquery/jquery.arrowSteps.js b/resources/jquery/jquery.arrowSteps.js index 488d1065..a1fd679d 100644 --- a/resources/jquery/jquery.arrowSteps.js +++ b/resources/jquery/jquery.arrowSteps.js @@ -42,18 +42,21 @@ */ ( function ( $ ) { $.fn.arrowSteps = function () { - var $steps, width, arrowWidth; + var $steps, width, arrowWidth, + paddingSide = $( 'body' ).hasClass( 'rtl' ) ? 'padding-left' : 'padding-right'; + this.addClass( 'arrowSteps' ); $steps = this.find( 'li' ); 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 - // for the calculated arrow width. + // Every step except the last one has an arrow pointing forward: + // at the right hand side in LTR languages, and at the left hand side in RTL. + // Also add in the padding for the calculated arrow width. arrowWidth = parseInt( this.outerHeight(), 10 ); $steps.filter( ':not(:last-child)' ).addClass( 'arrow' ) - .find( 'div' ).css( 'padding-right', arrowWidth.toString() + 'px' ); + .find( 'div' ).css( paddingSide, arrowWidth.toString() + 'px' ); this.data( 'arrowSteps', $steps ); return this; diff --git a/resources/jquery/jquery.badge.css b/resources/jquery/jquery.badge.css index 92e72555..d961bf3d 100644 --- a/resources/jquery/jquery.badge.css +++ b/resources/jquery/jquery.badge.css @@ -1,39 +1,34 @@ .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; + min-width: 7px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + padding: 1px 4px; text-align: center; + font-size: 12px; + line-height: 12px; + background-color: #d2d2d2; } .mw-badge-content { - font-size: 12px; - line-height: 14px; + font-weight: bold; color: white; - vertical-align: top; + vertical-align: baseline; + text-shadow: 0 1px rgba(0, 0, 0, 0.4); } .mw-badge-inline { display: inline-block; margin-left: 3px; } - .mw-badge-overlay { position: absolute; bottom: -1px; right: -3px; z-index: 50; } + +.mw-badge-important { + background-color: #cc0000; +} + diff --git a/resources/jquery/jquery.badge.js b/resources/jquery/jquery.badge.js index 04495b71..9404e818 100644 --- a/resources/jquery/jquery.badge.js +++ b/resources/jquery/jquery.badge.js @@ -1,8 +1,6 @@ /** * jQuery Badge plugin * - * Based on Badger plugin by Daniel Raftery (http://thrivingkings.com/badger). - * * @license MIT */ @@ -23,95 +21,56 @@ * * This program is distributed WITHOUT ANY WARRANTY. */ -( function ( $ ) { - +( function ( $, mw ) { /** - * Allows you to put a numeric "badge" on an item on the page. + * Allows you to put a "badge" on an item on the page. The badge container + * will be appended to the selected element(s). * 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 + * @param {number|string} text The value to display in the badge. If the value is falsey (0, + * null, false, '', etc.), any existing badge will be removed. + * @param {boolean} inline True if the badge should be displayed inline, false + * if the badge should overlay the parent element (default is inline) + * @param {boolean} displayZero True if the number zero should be displayed, + * false if the number zero should result in the badge being hidden + * (default is zero will result in the badge being hidden) */ - $.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; - } + $.fn.badge = function ( text, inline, displayZero ) { + var $badge = this.find( '.mw-badge' ), + badgeStyleClass = 'mw-badge-' + ( inline ? 'inline' : 'overlay' ), + isImportant = true, displayBadge = true; - // 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; + // If we're displaying zero, ensure style to be non-important + if ( mw.language.convertNumber( text, true ) === 0 ) { + isImportant = false; + if ( !displayZero ) { + displayBadge = false; } - // Other types are not supported, fall back to 0. - } else { - newBadgeCount = 0; + // If text is falsey (besides 0), hide the badge + } else if ( !text ) { + displayBadge = false; } - // 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 = $( '
' ) - .addClass( 'mw-badge' ) - .append( - $( '' ) - .addClass( 'mw-badge-content' ) - .text( newBadgeCount ) - ); - this.append( $badge ); - } - - if ( options.type === 'inline' ) { + if ( displayBadge ) { + // If a badge already exists, reuse it + if ( $badge.length ) { $badge - .removeClass( 'mw-badge-overlay' ) - .addClass( 'mw-badge-inline' ); - // Default: overlay + .toggleClass( 'mw-badge-important', isImportant ) + .find( '.mw-badge-content' ) + .text( text ); } 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 ); + // Otherwise, create a new badge with the specified text and style + $badge = $( '
' ) + .addClass( badgeStyleClass ) + .toggleClass( 'mw-badge-important', isImportant ) + .append( + $( '' ).text( text ) + ) + .appendTo( this ); } + } else { + $badge.remove(); } + return this; }; -}( jQuery ) ); +}( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.byteLimit.js b/resources/jquery/jquery.byteLimit.js index 75dc2b90..f2b98f09 100644 --- a/resources/jquery/jquery.byteLimit.js +++ b/resources/jquery/jquery.byteLimit.js @@ -221,8 +221,11 @@ // This is a side-effect of limiting after the fact. if ( res.trimmed === true ) { this.value = res.newVal; - prevSafeVal = res.newVal; } + // Always adjust prevSafeVal to reflect the input value. Not doing this could cause + // trimValForByteLength to compare the new value to an empty string instead of the + // old value, resulting in trimming always from the end (bug 40850). + prevSafeVal = res.newVal; } ); } ); }; diff --git a/resources/jquery/jquery.checkboxShiftClick.js b/resources/jquery/jquery.checkboxShiftClick.js index 1990dc0d..aced0633 100644 --- a/resources/jquery/jquery.checkboxShiftClick.js +++ b/resources/jquery/jquery.checkboxShiftClick.js @@ -1,14 +1,16 @@ /** * jQuery checkboxShiftClick * - * This will enable checkboxes to be checked or unchecked in a row by clicking one, holding shift and clicking another one + * This will enable checkboxes to be checked or unchecked in a row by clicking one, + * holding shift and clicking another one. * - * @author Krinkle + * @author Timo Tijhof, 2011 - 2012 * @license GPL v2 */ ( function ( $ ) { - $.fn.checkboxShiftClick = function ( text ) { - var prevCheckbox = null, $box = this; + $.fn.checkboxShiftClick = function () { + var prevCheckbox = null, + $box = this; // When our boxes are clicked.. $box.click( function ( e ) { // And one has been clicked before... diff --git a/resources/jquery/jquery.client.js b/resources/jquery/jquery.client.js index 24f8959e..b0bd6850 100644 --- a/resources/jquery/jquery.client.js +++ b/resources/jquery/jquery.client.js @@ -40,71 +40,74 @@ // Use the cached version if possible if ( profileCache[nav.userAgent] === undefined ) { - /* Configuration */ - - // Name of browsers or layout engines we don't recognize - var uk = 'unknown'; - // Generic version digit - var x = 'x'; - // Strings found in user agent strings that need to be conformed - var wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3']; - // Translations for conforming user agent strings - var userAgentTranslations = [ - // Tons of browsers lie about being something they are not - [/(Firefox|MSIE|KHTML,\slike\sGecko|Konqueror)/, ''], - // Chrome lives in the shadow of Safari still - ['Chrome Safari', 'Chrome'], - // KHTML is the layout engine not the browser - LIES! - ['KHTML', 'Konqueror'], - // Firefox nightly builds - ['Minefield', 'Firefox'], - // This helps keep differnt versions consistent - ['Navigator', 'Netscape'], - // This prevents version extraction issues, otherwise translation would happen later - ['PLAYSTATION 3', 'PS3'] - ]; - // Strings which precede a version number in a user agent string - combined and used as match 1 in - // version detectection - var versionPrefixes = [ - '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', 'rekonq' - ]; - // Tanslations for conforming browser names - var nameTranslations = []; - // Names of known layout engines - var layouts = ['gecko', 'konqueror', 'msie', 'opera', 'webkit']; - // Translations for conforming layout names - var layoutTranslations = [['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto']]; - // Names of supported layout engines for version number - var layoutVersions = ['applewebkit', 'gecko']; - // Names of known operating systems - var platforms = ['win', 'mac', 'linux', 'sunos', 'solaris', 'iphone']; - // Translations for conforming operating system names - var platformTranslations = [['sunos', 'solaris']]; - - /* Methods */ - - /** - * 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; - }; - - /* Pre-processing */ - - var ua = nav.userAgent, + var + versionNumber, + + /* Configuration */ + + // Name of browsers or layout engines we don't recognize + uk = 'unknown', + // Generic version digit + x = 'x', + // Strings found in user agent strings that need to be conformed + wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3'], + // Translations for conforming user agent strings + userAgentTranslations = [ + // Tons of browsers lie about being something they are not + [/(Firefox|MSIE|KHTML,\slike\sGecko|Konqueror)/, ''], + // Chrome lives in the shadow of Safari still + ['Chrome Safari', 'Chrome'], + // KHTML is the layout engine not the browser - LIES! + ['KHTML', 'Konqueror'], + // Firefox nightly builds + ['Minefield', 'Firefox'], + // This helps keep differnt versions consistent + ['Navigator', 'Netscape'], + // This prevents version extraction issues, otherwise translation would happen later + ['PLAYSTATION 3', 'PS3'] + ], + // Strings which precede a version number in a user agent string - combined and used as match 1 in + // version detectection + versionPrefixes = [ + 'camino', 'chrome', 'firefox', 'iceweasel', '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 + versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)', + // Names of known browsers + names = [ + 'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', + 'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq' + ], + // Tanslations for conforming browser names + nameTranslations = [], + // Names of known layout engines + layouts = ['gecko', 'konqueror', 'msie', 'opera', 'webkit'], + // Translations for conforming layout names + layoutTranslations = [ ['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto'] ], + // Names of supported layout engines for version number + layoutVersions = ['applewebkit', 'gecko'], + // Names of known operating systems + platforms = ['win', 'mac', 'linux', 'sunos', 'solaris', 'iphone'], + // Translations for conforming operating system names + platformTranslations = [ ['sunos', 'solaris'] ], + + /* Methods */ + + /** + * Performs multiple replacements on a string + */ + translate = function ( source, translations ) { + var i; + for ( i = 0; i < translations.length; i++ ) { + source = source.replace( translations[i][0], translations[i][1] ); + } + return source; + }, + + /* Pre-processing */ + + ua = nav.userAgent, match, name = uk, layout = uk, @@ -145,9 +148,14 @@ } // Expose Opera 10's lies about being Opera 9.8 if ( name === 'opera' && version >= 9.8) { - version = ua.match( /version\/([0-9\.]*)/i )[1] || 10; + match = ua.match( /version\/([0-9\.]*)/i ); + if ( match && match[1] ) { + version = match[1]; + } else { + version = '10'; + } } - var versionNumber = parseFloat( version, 10 ) || 0.0; + versionNumber = parseFloat( version, 10 ) || 0.0; /* Caching */ @@ -191,11 +199,10 @@ * @return Boolean true if browser known or assumed to be supported, false if blacklisted */ test: function ( map, profile ) { - /*jshint evil:true */ + /*jshint evil: true */ var conditions, dir, i, op, val; profile = $.isPlainObject( profile ) ? profile : $.client.profile(); - 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' || map[dir][profile.name] === undefined ) { @@ -203,12 +210,12 @@ return true; } conditions = map[dir][profile.name]; + if ( conditions === false ) { + return false; + } for ( i = 0; i < conditions.length; i++ ) { op = conditions[i][0]; val = conditions[i][1]; - if ( val === false ) { - return false; - } if ( typeof val === 'string' ) { if ( !( eval( 'profile.version' + op + '"' + val + '"' ) ) ) { return false; @@ -219,6 +226,7 @@ } } } + return true; } }; diff --git a/resources/jquery/jquery.collapsibleTabs.js b/resources/jquery/jquery.collapsibleTabs.js deleted file mode 100644 index cb25796f..00000000 --- a/resources/jquery/jquery.collapsibleTabs.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Collapsible tabs jQuery Plugin - */ -( 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 - var $settings = $.extend( {}, $.collapsibleTabs.defaults, options ); - - this.each( function () { - var $el = $( this ); - // add the element to our array of collapsible managers - $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length === 0 ? - $el : $.collapsibleTabs.instances.add( $el ) ); - // attach the settings to the elements - $el.data( 'collapsibleTabsSettings', $settings ); - // attach data to our collapsible elements - $el.children( $settings.collapsible ).each( function () { - $.collapsibleTabs.addData( $( this ) ); - } ); - } ); - - // if we haven't already bound our resize hanlder, bind it now - if ( !$.collapsibleTabs.boundEvent ) { - $( window ) - .delayedBind( '500', 'resize', function ( ) { - $.collapsibleTabs.handleResize(); - } ); - } - // call our resize handler to setup the page - $.collapsibleTabs.handleResize(); - return this; - }; - $.collapsibleTabs = { - instances: [], - boundEvent: null, - defaults: { - expandedContainer: '#p-views ul', - collapsedContainer: '#p-cactions ul', - collapsible: 'li.collapsible', - shifting: false, - expandCondition: function ( eleWidth ) { - return ( $( '#left-navigation' ).position().left + $( '#left-navigation' ).width() ) - < ( $( '#right-navigation' ).position().left - eleWidth ); - }, - collapseCondition: function () { - return ( $( '#left-navigation' ).position().left + $( '#left-navigation' ).width() ) - > $( '#right-navigation' ).position().left; - } - }, - addData: function ( $collapsible ) { - var $settings = $collapsible.parent().data( 'collapsibleTabsSettings' ); - if ( $settings !== null ) { - $collapsible.data( 'collapsibleTabsSettings', { - expandedContainer: $settings.expandedContainer, - collapsedContainer: $settings.collapsedContainer, - expandedWidth: $collapsible.width(), - prevElement: $collapsible.prev() - } ); - } - }, - getSettings: function ( $collapsible ) { - var $settings = $collapsible.data( 'collapsibleTabsSettings' ); - if ( $settings === undefined ) { - $.collapsibleTabs.addData( $collapsible ); - $settings = $collapsible.data( 'collapsibleTabsSettings' ); - } - return $settings; - }, - 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 ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) { - - $el.trigger( 'beforeTabCollapse' ); - // move the element to the dropdown menu - $.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 - && data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children( - data.collapsible + ':first' ) ).expandedWidth ) ) { - //move the element from the dropdown to the tab - $el.trigger( 'beforeTabExpand' ); - $.collapsibleTabs - .moveToExpanded( data.collapsedContainer + ' ' + data.collapsible + ':first' ); - } - }); - }, - moveToCollapsed: function ( ele ) { - var $moving = $( ele ), - data = $.collapsibleTabs.getSettings( $moving ), - dataExp = $.collapsibleTabs.getSettings( data.expandedContainer ); - dataExp.shifting = true; - $moving - .detach() - .prependTo( data.collapsedContainer ) - .data( 'collapsibleTabsSettings', data ); - dataExp.shifting = false; - $.collapsibleTabs.handleResize(); - }, - 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 ); - dataExp.shifting = false; - $.collapsibleTabs.handleResize(); - } - }; - -}( jQuery ) ); diff --git a/resources/jquery/jquery.colorUtil.js b/resources/jquery/jquery.colorUtil.js index c1fe7fe3..9c6f9ecb 100644 --- a/resources/jquery/jquery.colorUtil.js +++ b/resources/jquery/jquery.colorUtil.js @@ -113,17 +113,20 @@ * @return Array The HSL representation */ rgbToHsl: function ( R, G, B ) { - var r = R / 255, + var d, + 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; + b = B / 255, + max = Math.max( r, g, b ), min = Math.min( r, g, b ), + h, + s, + l = (max + min) / 2; if ( max === min ) { // achromatic h = s = 0; } else { - var d = max - min; + d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch ( max ) { case r: @@ -155,12 +158,12 @@ * @return Array The RGB representation */ hslToRgb: function ( h, s, l ) { - var r, g, b; + var r, g, b, hue2rgb, q, p; if ( s === 0 ) { r = g = b = l; // achromatic } else { - var hue2rgb = function ( p, q, t ) { + hue2rgb = function ( p, q, t ) { if ( t < 0 ) { t += 1; } @@ -179,8 +182,8 @@ return p; }; - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; + q = l < 0.5 ? l * (1 + s) : l + s - l * s; + p = 2 * l - q; r = hue2rgb( p, q, h + 1/3 ); g = hue2rgb( p, q, h ); b = hue2rgb( p, q, h - 1/3 ); diff --git a/resources/jquery/jquery.delayedBind.js b/resources/jquery/jquery.delayedBind.js index 5d32b6b0..40f3d44e 100644 --- a/resources/jquery/jquery.delayedBind.js +++ b/resources/jquery/jquery.delayedBind.js @@ -43,12 +43,12 @@ $.fn.extend( { $(this).data( '_delayedBindTimerID-' + encEvent + '-' + timeout, timerID ); } ); } - + // Bottom half $(this).bind( '_delayedBind-' + encEvent + '-' + timeout, data, callback ); } ); }, - + /** * Cancel the timers for delayed events on the selected elements. */ @@ -61,7 +61,7 @@ $.fn.extend( { } } ); }, - + /** * Unbind an event bound with delayedBind() */ diff --git a/resources/jquery/jquery.expandableField.js b/resources/jquery/jquery.expandableField.js index 063f2609..9e532e52 100644 --- a/resources/jquery/jquery.expandableField.js +++ b/resources/jquery/jquery.expandableField.js @@ -67,16 +67,16 @@ context = { config: { // callback function for before collapse - beforeCondense: function ( context ) {}, + beforeCondense: function () {}, // callback function for before expand - beforeExpand: function ( context ) {}, + beforeExpand: function () {}, // callback function for after collapse - afterCondense: function ( context ) {}, + afterCondense: function () {}, // callback function for after expand - afterExpand: function ( context ) {}, + afterExpand: function () {}, // Whether the field should expand to the left or the right -- defaults to left expandToLeft: true diff --git a/resources/jquery/jquery.hidpi.js b/resources/jquery/jquery.hidpi.js new file mode 100644 index 00000000..70bfc4ea --- /dev/null +++ b/resources/jquery/jquery.hidpi.js @@ -0,0 +1,117 @@ +/** + * Responsive images based on 'srcset' and 'window.devicePixelRatio' emulation where needed. + * + * Call $().hidpi() on a document or part of a document to replace image srcs in that section. + * + * $.devicePixelRatio() can be used to supplement window.devicePixelRatio with support on + * some additional browsers. + */ +( function ( $ ) { + +/** + * Detect reported or approximate device pixel ratio. + * 1.0 means 1 CSS pixel is 1 hardware pixel + * 2.0 means 1 CSS pixel is 2 hardware pixels + * etc + * + * Uses window.devicePixelRatio if available, or CSS media queries on IE. + * + * @method + * @returns {number} Device pixel ratio + */ +$.devicePixelRatio = function () { + if ( window.devicePixelRatio !== undefined ) { + // Most web browsers: + // * WebKit (Safari, Chrome, Android browser, etc) + // * Opera + // * Firefox 18+ + return window.devicePixelRatio; + } else if ( window.msMatchMedia !== undefined ) { + // Windows 8 desktops / tablets, probably Windows Phone 8 + // + // IE 10 doesn't report pixel ratio directly, but we can get the + // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for + // simplicity, but you may get different values depending on zoom + // factor, size of screen and orientation in Metro IE. + if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) { + return 2; + } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) { + return 1.5; + } else { + return 1; + } + } else { + // Legacy browsers... + // Assume 1 if unknown. + return 1; + } +}; + +/** + * Implement responsive images based on srcset attributes, if browser has no + * native srcset support. + * + * @method + * @returns {jQuery} This selection + */ +$.fn.hidpi = function () { + var $target = this, + // @todo add support for dpi media query checks on Firefox, IE + devicePixelRatio = $.devicePixelRatio(), + testImage = new Image(); + + if ( devicePixelRatio > 1 && testImage.srcset === undefined ) { + // No native srcset support. + $target.find( 'img' ).each( function () { + var $img = $( this ), + srcset = $img.attr( 'srcset' ), + match; + if ( typeof srcset === 'string' && srcset !== '' ) { + match = $.matchSrcSet( devicePixelRatio, srcset ); + if (match !== null ) { + $img.attr( 'src', match ); + } + } + }); + } + + return $target; +}; + +/** + * Match a srcset entry for the given device pixel ratio + * + * @param {number} devicePixelRatio + * @param {string} srcset + * @return {mixed} null or the matching src string + * + * Exposed for testing. + */ +$.matchSrcSet = function ( devicePixelRatio, srcset ) { + var candidates, + candidate, + bits, + src, + i, + ratioStr, + ratio, + selectedRatio = 1, + selectedSrc = null; + candidates = srcset.split( / *, */ ); + for ( i = 0; i < candidates.length; i++ ) { + candidate = candidates[i]; + bits = candidate.split( / +/ ); + src = bits[0]; + if ( bits.length > 1 && bits[1].charAt( bits[1].length - 1 ) === 'x' ) { + ratioStr = bits[1].substr( 0, bits[1].length - 1 ); + ratio = parseFloat( ratioStr ); + if ( ratio <= devicePixelRatio && ratio > selectedRatio ) { + selectedRatio = ratio; + selectedSrc = src; + } + } + } + return selectedSrc; +}; + +}( jQuery ) ); diff --git a/resources/jquery/jquery.highlightText.js b/resources/jquery/jquery.highlightText.js index f720e07f..cda2765b 100644 --- a/resources/jquery/jquery.highlightText.js +++ b/resources/jquery/jquery.highlightText.js @@ -29,7 +29,7 @@ // non latin characters can make regex think a new word has begun: do not use \b // http://stackoverflow.com/questions/3787072/regex-wordwrap-with-utf8-characters-in-js // look for an occurrence of our pattern and store the starting position - match = node.data.match( new RegExp( "(^|\\s)" + $.escapeRE( pat ), "i" ) ); + match = node.data.match( new RegExp( '(^|\\s)' + $.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 diff --git a/resources/jquery/jquery.jStorage.js b/resources/jquery/jquery.jStorage.js index 95959cf7..6ca21b5c 100644 --- a/resources/jquery/jquery.jStorage.js +++ b/resources/jquery/jquery.jStorage.js @@ -3,12 +3,9 @@ * 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 + * Copyright (c) 2010 - 2012 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 @@ -27,52 +24,30 @@ * 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 - * - * can be any JSON-able value, including objects and arrays. - * - **/ + (function(){ + var + /* jStorage version */ + JSTORAGE_VERSION = "0.3.0", + + /* detect a dollar object or create one if not found */ + $ = window.jQuery || window.$ || (window.$ = {}), -(function($){ - if(!$ || !($.toJSON || Object.toJSON || window.JSON)){ - throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!"); + /* check for a JSON handling support */ + JSON = { + parse: + window.JSON && (window.JSON.parse || window.JSON.decode) || + String.prototype.evalJSON && function(str){return String(str).evalJSON();} || + $.parseJSON || + $.evalJSON, + stringify: + Object.toJSON || + window.JSON && (window.JSON.stringify || window.JSON.encode) || + $.toJSON + }; + + // Break if no JSON support was found + if(!JSON.parse || !JSON.stringify){ + throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page"); } var @@ -88,20 +63,58 @@ /* 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, + /* onchange observers */ + _observers = {}, + + /* timeout to wait after onchange event */ + _observer_timeout = false, + + /* last update time */ + _observer_update = 0, + + /* pubsub observers */ + _pubsub_observers = {}, + + /* skip published items older than current timestamp */ + _pubsub_last = +new Date(), + /* Next check for TTL */ _ttl_timeout, + /* crc32 table */ + _crc32Table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 "+ + "0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 "+ + "6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 "+ + "FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 "+ + "A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 "+ + "32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 "+ + "56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 "+ + "C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 "+ + "E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 "+ + "6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 "+ + "12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE "+ + "A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 "+ + "DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 "+ + "5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 "+ + "2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF "+ + "04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 "+ + "7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 "+ + "FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 "+ + "A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C "+ + "36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 "+ + "5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 "+ + "C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 "+ + "EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D "+ + "7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 "+ + "18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 "+ + "A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A "+ + "D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A "+ + "53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 "+ + "2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D", + /** * 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 @@ -158,14 +171,16 @@ resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml'); return this.isXML(resultXML)?resultXML:false; } - }; + }, + + _localStoragePolyfillSetKey = function(){}; + ////////////////////////// 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 */ @@ -180,11 +195,13 @@ // QUOTA_EXCEEDED_ERRROR DOM Exception 22. } } + if(localStorageReallyWorks){ try { if(window.localStorage) { _storage_service = window.localStorage; _backend = "localStorage"; + _observer_update = _storage_service.jStorage_update; } } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */} } @@ -194,6 +211,7 @@ if(window.globalStorage) { _storage_service = window.globalStorage[window.location.hostname]; _backend = "globalStorage"; + _observer_update = _storage_service.jStorage_update; } } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */} } @@ -208,11 +226,24 @@ /* userData element needs to be inserted into the DOM! */ document.getElementsByTagName('head')[0].appendChild(_storage_elm); - _storage_elm.load("jStorage"); + try{ + _storage_elm.load("jStorage"); + }catch(E){ + // try to reset cache + _storage_elm.setAttribute("jStorage", "{}"); + _storage_elm.save("jStorage"); + _storage_elm.load("jStorage"); + } + var data = "{}"; try{ data = _storage_elm.getAttribute("jStorage"); }catch(E5){} + + try{ + _observer_update = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + _storage_service.jStorage = data; _backend = "userDataBehavior"; }else{ @@ -221,35 +252,427 @@ } } + // Load data from storage + _load_storage(); + + // remove dead keys + _handleTTL(); + + // create localStorage and sessionStorage polyfills if needed + _createPolyfillStorage("local"); + _createPolyfillStorage("session"); + + // start listening for changes + _setupObserver(); + + // initialize publish-subscribe service + _handlePubSub(); + + // handle cached navigation + if("addEventListener" in window){ + window.addEventListener("pageshow", function(event){ + if(event.persisted){ + _storageObserver(); + } + }, false); + } + } + + /** + * Create a polyfill for localStorage (type="local") or sessionStorage (type="session") + * + * @param {String} type Either "local" or "session" + * @param {Boolean} forceCreate If set to true, recreate the polyfill (needed with flush) + */ + function _createPolyfillStorage(type, forceCreate){ + var _skipSave = false, + _length = 0, + i, + storage, + storage_source = {}; + + var rand = Math.random(); + + if(!forceCreate && typeof window[type+"Storage"] != "undefined"){ + return; + } + + // Use globalStorage for localStorage if available + if(type == "local" && window.globalStorage){ + localStorage = window.globalStorage[window.location.hostname]; + return; + } + + // only IE6/7 from this point on + if(_backend != "userDataBehavior"){ + return; + } + + // Remove existing storage element if available + if(forceCreate && window[type+"Storage"] && window[type+"Storage"].parentNode){ + window[type+"Storage"].parentNode.removeChild(window[type+"Storage"]); + } + + storage = document.createElement("button"); + document.getElementsByTagName('head')[0].appendChild(storage); + + if(type == "local"){ + storage_source = _storage; + }else if(type == "session"){ + _sessionStoragePolyfillUpdate(); + } + + for(i in storage_source){ + + if(storage_source.hasOwnProperty(i) && i != "__jstorage_meta" && i != "length" && typeof storage_source[i] != "undefined"){ + if(!(i in storage)){ + _length++; + } + storage[i] = storage_source[i]; + } + } + + // Polyfill API + + /** + * Indicates how many keys are stored in the storage + */ + storage.length = _length; + + /** + * Returns the key of the nth stored value + * + * @param {Number} n Index position + * @return {String} Key name of the nth stored value + */ + storage.key = function(n){ + var count = 0, i; + _sessionStoragePolyfillUpdate(); + for(i in storage_source){ + if(storage_source.hasOwnProperty(i) && i != "__jstorage_meta" && i!="length" && typeof storage_source[i] != "undefined"){ + if(count == n){ + return i; + } + count++; + } + } + } + + /** + * Returns the current value associated with the given key + * + * @param {String} key key name + * @return {Mixed} Stored value + */ + storage.getItem = function(key){ + _sessionStoragePolyfillUpdate(); + if(type == "session"){ + return storage_source[key]; + } + return $.jStorage.get(key); + } + + /** + * Sets or updates value for a give key + * + * @param {String} key Key name to be updated + * @param {String} value String value to be stored + */ + storage.setItem = function(key, value){ + if(typeof value == "undefined"){ + return; + } + storage[key] = (value || "").toString(); + } + + /** + * Removes key from the storage + * + * @param {String} key Key name to be removed + */ + storage.removeItem = function(key){ + if(type == "local"){ + return $.jStorage.deleteKey(key); + } + + storage[key] = undefined; + + _skipSave = true; + if(key in storage){ + storage.removeAttribute(key); + } + _skipSave = false; + } + + /** + * Clear storage + */ + storage.clear = function(){ + if(type == "session"){ + window.name = ""; + _createPolyfillStorage("session", true); + return; + } + $.jStorage.flush(); + } + + if(type == "local"){ + + _localStoragePolyfillSetKey = function(key, value){ + if(key == "length"){ + return; + } + _skipSave = true; + if(typeof value == "undefined"){ + if(key in storage){ + _length--; + storage.removeAttribute(key); + } + }else{ + if(!(key in storage)){ + _length++; + } + storage[key] = (value || "").toString(); + } + storage.length = _length; + _skipSave = false; + } + } + + function _sessionStoragePolyfillUpdate(){ + if(type != "session"){ + return; + } + try{ + storage_source = JSON.parse(window.name || "{}"); + }catch(E){ + storage_source = {}; + } + } + + function _sessionStoragePolyfillSave(){ + if(type != "session"){ + return; + } + window.name = JSON.stringify(storage_source); + }; + + storage.attachEvent("onpropertychange", function(e){ + if(e.propertyName == "length"){ + return; + } + + if(_skipSave || e.propertyName == "length"){ + return; + } + + if(type == "local"){ + if(!(e.propertyName in storage_source) && typeof storage[e.propertyName] != "undefined"){ + _length ++; + } + }else if(type == "session"){ + _sessionStoragePolyfillUpdate(); + if(typeof storage[e.propertyName] != "undefined" && !(e.propertyName in storage_source)){ + storage_source[e.propertyName] = storage[e.propertyName]; + _length++; + }else if(typeof storage[e.propertyName] == "undefined" && e.propertyName in storage_source){ + delete storage_source[e.propertyName]; + _length--; + }else{ + storage_source[e.propertyName] = storage[e.propertyName]; + } + + _sessionStoragePolyfillSave(); + storage.length = _length; + return; + } + + $.jStorage.set(e.propertyName, storage[e.propertyName]); + storage.length = _length; + }); + + window[type+"Storage"] = storage; + } + + /** + * Reload data from storage when needed + */ + function _reloadData(){ + var data = "{}"; + + if(_backend == "userDataBehavior"){ + _storage_elm.load("jStorage"); + + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + + try{ + _observer_update = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + + _storage_service.jStorage = data; + } + _load_storage(); // remove dead keys _handleTTL(); + + _handlePubSub(); + } + + /** + * Sets up a storage change observer + */ + function _setupObserver(){ + if(_backend == "localStorage" || _backend == "globalStorage"){ + if("addEventListener" in window){ + window.addEventListener("storage", _storageObserver, false); + }else{ + document.attachEvent("onstorage", _storageObserver); + } + }else if(_backend == "userDataBehavior"){ + setInterval(_storageObserver, 1000); + } + } + + /** + * Fired on any kind of data change, needs to check if anything has + * really been changed + */ + function _storageObserver(){ + var updateTime; + // cumulate change notifications with timeout + clearTimeout(_observer_timeout); + _observer_timeout = setTimeout(function(){ + + if(_backend == "localStorage" || _backend == "globalStorage"){ + updateTime = _storage_service.jStorage_update; + }else if(_backend == "userDataBehavior"){ + _storage_elm.load("jStorage"); + try{ + updateTime = _storage_elm.getAttribute("jStorage_update"); + }catch(E5){} + } + + if(updateTime && updateTime != _observer_update){ + _observer_update = updateTime; + _checkUpdatedKeys(); + } + + }, 25); + } + + /** + * Reloads the data and checks if any keys are changed + */ + function _checkUpdatedKeys(){ + var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)), + newCrc32List; + + _reloadData(); + newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)); + + var key, + updated = [], + removed = []; + + for(key in oldCrc32List){ + if(oldCrc32List.hasOwnProperty(key)){ + if(!newCrc32List[key]){ + removed.push(key); + continue; + } + if(oldCrc32List[key] != newCrc32List[key]){ + updated.push(key); + } + } + } + + for(key in newCrc32List){ + if(newCrc32List.hasOwnProperty(key)){ + if(!oldCrc32List[key]){ + updated.push(key); + } + } + } + + _fireObservers(updated, "updated"); + _fireObservers(removed, "deleted"); + } + + /** + * Fires observers for updated keys + * + * @param {Array|String} keys Array of key names or a key + * @param {String} action What happened with the value (updated, deleted, flushed) + */ + function _fireObservers(keys, action){ + keys = [].concat(keys || []); + if(action == "flushed"){ + keys = []; + for(var key in _observers){ + if(_observers.hasOwnProperty(key)){ + keys.push(key); + } + } + action = "deleted"; + } + for(var i=0, len = keys.length; i=0; i--){ + pubelm = _storage.__jstorage_meta.PubSub[i]; + if(pubelm[0] > _pubsub_last){ + _pubsubCurrent = pubelm[0]; + _fireSubscribers(pubelm[1], pubelm[2]); + } + } + + _pubsub_last = _pubsubCurrent; + } + + /** + * Fires all subscriber listeners for a pubsub channel + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload data to deliver + */ + function _fireSubscribers(channel, payload){ + if(_pubsub_observers[channel]){ + for(var i=0, len = _pubsub_observers[channel].length; i>> 8)^x; + } + return crc^(-1); + } + ////////////////////////// PUBLIC INTERFACE ///////////////////////// $.jStorage = { /* Version number */ - version: "0.1.7.0", + version: JSTORAGE_VERSION, /** * Sets a key's value. * - * @param {String} key - Key to set. If this value is not set or not + * @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 + * @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 + * @return {Mixed} the used value */ set: function(key, value, options){ _checkKey(key); options = options || {}; + // undefined values are deleted automatically + if(typeof value == "undefined"){ + this.deleteKey(key); + return value; + } + 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"){ + }else if(typeof value == "function"){ + return undefined; // 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)); + value = JSON.parse(JSON.stringify(value)); } + _storage[key] = value; - if(!isNaN(options.TTL)){ - this.setTTL(key, options.TTL); - // also handles saving - }else{ - _save(); - } + _storage.__jstorage_meta.CRC32[key] = _crc32(JSON.stringify(value)); + + this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + + _localStoragePolyfillSetKey(key, value); + + _fireObservers(key, "updated"); return value; }, @@ -356,12 +896,12 @@ * * @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 + * @return {Mixed} the key value, default value or null */ get: function(key, def){ _checkKey(key); if(key in _storage){ - if(_storage[key] && typeof _storage[key] === "object" && + if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml && _storage[key]._is_xml){ return _XMLService.decode(_storage[key].xml); @@ -369,26 +909,31 @@ return _storage[key]; } } - return typeof(def) === 'undefined' ? null : def; + 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 + * @return {Boolean} 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" && + if(typeof _storage.__jstorage_meta.TTL == "object" && key in _storage.__jstorage_meta.TTL){ delete _storage.__jstorage_meta.TTL[key]; } + + delete _storage.__jstorage_meta.CRC32[key]; + _localStoragePolyfillSetKey(key, undefined); + _save(); + _publishChange(); + _fireObservers(key, "deleted"); return true; } return false; @@ -399,7 +944,7 @@ * * @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 + * @return {Boolean} true if key existed or false if it didn't */ setTTL: function(key, ttl){ var curtime = +new Date(); @@ -407,9 +952,6 @@ ttl = Number(ttl) || 0; if(key in _storage){ - if(!_storage.__jstorage_meta){ - _storage.__jstorage_meta = {}; - } if(!_storage.__jstorage_meta.TTL){ _storage.__jstorage_meta.TTL = {}; } @@ -424,26 +966,47 @@ _save(); _handleTTL(); + + _publishChange(); return true; } return false; }, + /** + * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set + * + * @param {String} key Key to check + * @return {Number} Remaining TTL in milliseconds + */ + getTTL: function(key){ + var curtime = +new Date(), ttl; + _checkKey(key); + if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ + ttl = _storage.__jstorage_meta.TTL[key] - curtime; + return ttl || 0; + } + return 0; + }, + /** * Deletes everything in cache. * - * @return true + * @return {Boolean} Always true */ flush: function(){ - _storage = {}; + _storage = {__jstorage_meta:{CRC32:{}}}; + _createPolyfillStorage("local", true); _save(); + _publishChange(); + _fireObservers(null, "flushed"); return true; }, /** * Returns a read-only copy of _storage * - * @returns Object + * @return {Object} Read-only copy of _storage */ storageObj: function(){ function F() {} @@ -455,12 +1018,12 @@ * Returns an index of all used keys as an array * ['key1', 'key2',..'keyN'] * - * @returns Array + * @return {Array} Used keys */ index: function(){ var index = [], i; for(i in _storage){ - if(_storage.hasOwnProperty(i) && i !== "__jstorage_meta"){ + if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ index.push(i); } } @@ -470,7 +1033,8 @@ /** * How much space in bytes does the storage take? * - * @returns Number + * @return {Number} Storage size in chars (not the same as in bytes, + * since some chars may take several bytes) */ storageSize: function(){ return _storage_size; @@ -479,7 +1043,7 @@ /** * Which backend is currently in use? * - * @returns String + * @return {String} Backend name */ currentBackend: function(){ return _backend; @@ -488,45 +1052,92 @@ /** * Test if storage is available * - * @returns Boolean + * @return {Boolean} True if storage can be used */ storageAvailable: function(){ return !!_backend; }, /** - * Reloads the data from browser storage + * Register change listeners * - * @returns undefined + * @param {String} key Key name + * @param {Function} callback Function to run when the key changes */ - reInit: function(){ - var new_storage_elm, data; - if(_storage_elm && _storage_elm.addBehavior){ - new_storage_elm = document.createElement('link'); + listenKeyChange: function(key, callback){ + _checkKey(key); + if(!_observers[key]){ + _observers[key] = []; + } + _observers[key].push(callback); + }, - _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm); - _storage_elm = new_storage_elm; + /** + * Remove change listeners + * + * @param {String} key Key name to unregister listeners against + * @param {Function} [callback] If set, unregister the callback, if not - unregister all + */ + stopListening: function(key, callback){ + _checkKey(key); - /* Use a DOM element to act as userData storage */ - _storage_elm.style.behavior = 'url(#default#userData)'; + if(!_observers[key]){ + return; + } - /* userData element needs to be inserted into the DOM! */ - document.getElementsByTagName('head')[0].appendChild(_storage_elm); + if(!callback){ + delete _observers[key]; + return; + } - _storage_elm.load("jStorage"); - data = "{}"; - try{ - data = _storage_elm.getAttribute("jStorage"); - }catch(E5){} - _storage_service.jStorage = data; - _backend = "userDataBehavior"; + for(var i = _observers[key].length - 1; i>=0; i--){ + if(_observers[key][i] == callback){ + _observers[key].splice(i,1); + } } + }, + + /** + * Subscribe to a Publish/Subscribe event stream + * + * @param {String} channel Channel name + * @param {Function} callback Function to run when the something is published to the channel + */ + subscribe: function(channel, callback){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + if(!_pubsub_observers[channel]){ + _pubsub_observers[channel] = []; + } + _pubsub_observers[channel].push(callback); + }, - _load_storage(); + /** + * Publish data to an event stream + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to deliver + */ + publish: function(channel, payload){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + + _publish(channel, payload); + }, + + /** + * Reloads the data from browser storage + */ + reInit: function(){ + _reloadData(); } }; // Initialize jStorage _init(); -})(window.$ || window.jQuery); +})(); diff --git a/resources/jquery/jquery.js b/resources/jquery/jquery.js index d4f3bb38..a86bf797 100644 --- a/resources/jquery/jquery.js +++ b/resources/jquery/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.8.2 + * jQuery JavaScript Library v1.8.3 * http://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * http://jquery.org/license * - * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time) + * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time) */ (function( window, undefined ) { var @@ -186,7 +186,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.8.2", + jquery: "1.8.3", // The default length of a jQuery object is 0 length: 0, @@ -999,8 +999,10 @@ jQuery.Callbacks = function( options ) { (function add( args ) { jQuery.each( args, function( _, arg ) { var type = jQuery.type( arg ); - if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) { - list.push( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } } else if ( arg && arg.length && type !== "string" ) { // Inspect recursively add( arg ); @@ -1253,24 +1255,23 @@ jQuery.support = (function() { clickFn, div = document.createElement("div"); - // Preliminary tests + // Setup div.setAttribute( "className", "t" ); div.innerHTML = "
a"; + // Support tests won't run in some limited or non-browser environments 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 ) { + if ( !all || !a || !all.length ) { return {}; } - // First batch of supports tests + // First batch of tests select = document.createElement("select"); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName("input")[ 0 ]; + a.style.cssText = "top:1px;float:left;opacity:.5"; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), @@ -1312,7 +1313,7 @@ jQuery.support = (function() { // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", - // Tests for enctype support on a form(#6743) + // Tests for enctype support on a form (#6743) enctype: !!document.createElement("form").enctype, // Makes sure cloning an html5 element does not cause problems @@ -2217,26 +2218,25 @@ jQuery.extend({ }, select: { get: function( elem ) { - var value, i, max, option, - index = elem.selectedIndex, - values = [], + var value, option, options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; // Loop through all the selected options - i = one ? index : 0; - max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { option = options[ i ]; - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); @@ -2251,11 +2251,6 @@ jQuery.extend({ } } - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - return values; }, @@ -3233,7 +3228,7 @@ jQuery.removeEvent = document.removeEventListener ? if ( elem.detachEvent ) { - // #8545, #7054, preventing memory leaks for custom events in IE6-8 – + // #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; @@ -3725,7 +3720,8 @@ var cachedruns, delete cache[ keys.shift() ]; } - return (cache[ key ] = value); + // Retrieve with (key + " ") to avoid collision with native Object.prototype properties (see Issue #157) + return (cache[ key + " " ] = value); }, cache ); }, @@ -4259,13 +4255,13 @@ Expr = Sizzle.selectors = { }, "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")) || "" ); - }; + var pattern = classCache[ expando ][ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); }, "ATTR": function( name, operator, check ) { @@ -4511,7 +4507,7 @@ Expr = Sizzle.selectors = { "focus": function( elem ) { var doc = elem.ownerDocument; - return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href); + return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, "active": function( elem ) { @@ -4519,11 +4515,11 @@ Expr = Sizzle.selectors = { }, // Positional types - "first": createPositionalPseudo(function( matchIndexes, length, argument ) { + "first": createPositionalPseudo(function() { return [ 0 ]; }), - "last": createPositionalPseudo(function( matchIndexes, length, argument ) { + "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), @@ -4531,14 +4527,14 @@ Expr = Sizzle.selectors = { return [ argument < 0 ? argument + length : argument ]; }), - "even": createPositionalPseudo(function( matchIndexes, length, argument ) { + "even": createPositionalPseudo(function( matchIndexes, length ) { for ( var i = 0; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), - "odd": createPositionalPseudo(function( matchIndexes, length, argument ) { + "odd": createPositionalPseudo(function( matchIndexes, length ) { for ( var i = 1; i < length; i += 2 ) { matchIndexes.push( i ); } @@ -4659,7 +4655,9 @@ baseHasDuplicate = !hasDuplicate; // Document sorting and removing duplicates Sizzle.uniqueSort = function( results ) { var elem, - i = 1; + duplicates = [], + i = 1, + j = 0; hasDuplicate = baseHasDuplicate; results.sort( sortOrder ); @@ -4667,9 +4665,12 @@ Sizzle.uniqueSort = function( results ) { if ( hasDuplicate ) { for ( ; (elem = results[i]); i++ ) { if ( elem === results[ i - 1 ] ) { - results.splice( i--, 1 ); + j = duplicates.push( i ); } } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } } return results; @@ -4680,8 +4681,9 @@ Sizzle.error = function( msg ) { }; function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, soFar, groups, preFilters, - cached = tokenCache[ expando ][ selector ]; + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ expando ][ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); @@ -4696,7 +4698,8 @@ function tokenize( selector, parseOnly ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { - soFar = soFar.slice( match[0].length ); + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; } groups.push( tokens = [] ); } @@ -4715,8 +4718,7 @@ function tokenize( selector, parseOnly ) { // 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 ))) ) { + (match = preFilters[ type ]( match ))) ) { tokens.push( matched = new Token( match.shift() ) ); soFar = soFar.slice( matched.length ); @@ -4836,18 +4838,13 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS 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, + var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ), + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? @@ -4872,27 +4869,45 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS // Apply postFilter if ( postFilter ) { - postFilterIn = condense( matcherOut, postMap ); - postFilter( postFilterIn, [], context, xml ); + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn - i = postFilterIn.length; + i = temp.length; while ( i-- ) { - if ( (elem = postFilterIn[i]) ) { + if ( (elem = temp[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); + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } } } + + // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? @@ -4933,7 +4948,6 @@ function matcherFromTokens( tokens ) { 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 @@ -5062,7 +5076,7 @@ compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], - cached = compilerCache[ expando ][ selector ]; + cached = compilerCache[ expando ][ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element @@ -5085,11 +5099,11 @@ compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { return cached; }; -function multipleContexts( selector, contexts, results, seed ) { +function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results, seed ); + Sizzle( selector, contexts[i], results ); } return results; } @@ -5167,15 +5181,14 @@ if ( document.querySelectorAll ) { rescape = /'|\\/g, rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, - // qSa(:focus) reports false when true (Chrome 21), + // qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA // A support test would require too much code (would include document ready) - rbuggyQSA = [":focus"], + 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" ], + rbuggyMatches = [ ":active" ], matches = docElem.matchesSelector || docElem.mozMatchesSelector || docElem.webkitMatchesSelector || @@ -5229,7 +5242,7 @@ if ( document.querySelectorAll ) { // Only use querySelectorAll when not filtering, // when this is not xml, // and when no QSA bugs apply - if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + if ( !seed && !xml && !rbuggyQSA.test( selector ) ) { var groups, i, old = true, nid = expando, @@ -5298,7 +5311,7 @@ if ( document.querySelectorAll ) { 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 )) ) { + if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) { try { var ret = matches.call( elem, expr ); @@ -6533,7 +6546,7 @@ var curCSS, iframe, iframeDoc, rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ), - elemdisplay = {}, + elemdisplay = { BODY: "block" }, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { @@ -6814,7 +6827,9 @@ if ( window.getComputedStyle ) { if ( computed ) { - ret = computed[ name ]; + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed.getPropertyValue( name ) || computed[ name ]; + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { ret = jQuery.style( elem, name ); } @@ -7843,9 +7858,12 @@ jQuery.extend({ // 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() ) || false; - s.crossDomain = parts && ( parts.join(":") + ( parts[ 3 ] ? "" : parts[ 1 ] === "http:" ? 80 : 443 ) ) !== - ( ajaxLocParts.join(":") + ( ajaxLocParts[ 3 ] ? "" : ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ); + 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 ) ) ) + ); } // Convert data if not already a string @@ -8464,7 +8482,7 @@ if ( jQuery.support.ajax ) { // on any attempt to access responseText (#11426) try { responses.text = xhr.responseText; - } catch( _ ) { + } catch( e ) { } // Firefox throws an exception when accessing @@ -8617,7 +8635,9 @@ function Animation( elem, properties, options ) { tick = function() { var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - percent = 1 - ( remaining / animation.duration || 0 ), + // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, index = 0, length = animation.tweens.length; @@ -8769,7 +8789,7 @@ jQuery.Animation = jQuery.extend( Animation, { }); function defaultPrefilter( elem, props, opts ) { - var index, prop, value, length, dataShow, tween, hooks, oldfire, + var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire, anim = this, style = elem.style, orig = {}, @@ -8843,6 +8863,7 @@ function defaultPrefilter( elem, props, opts ) { value = props[ index ]; if ( rfxtypes.exec( value ) ) { delete props[ index ]; + toggle = toggle || value === "toggle"; if ( value === ( hidden ? "hide" : "show" ) ) { continue; } @@ -8853,6 +8874,14 @@ function defaultPrefilter( elem, props, opts ) { length = handled.length; if ( length ) { dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + + // store state if its toggle - enables .stop().toggle() to "reverse" + if ( toggle ) { + dataShow.hidden = !hidden; + } if ( hidden ) { jQuery( elem ).show(); } else { @@ -9149,6 +9178,8 @@ jQuery.fx.tick = function() { timers = jQuery.timers, i = 0; + fxNow = jQuery.now(); + for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Checks the timer has not already been removed @@ -9160,6 +9191,7 @@ jQuery.fx.tick = function() { if ( !timers.length ) { jQuery.fx.stop(); } + fxNow = undefined; }; jQuery.fx.timer = function( timer ) { diff --git a/resources/jquery/jquery.json.js b/resources/jquery/jquery.json.js index aac3428b..75953f4d 100644 --- a/resources/jquery/jquery.json.js +++ b/resources/jquery/jquery.json.js @@ -1,168 +1,174 @@ /** - * jQuery JSON Plugin - * version: 2.3 (2011-09-17) + * jQuery JSON plugin 2.4.0 * - * 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 - * 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 - * copyrighted 2005 by Bob Ippolito. + * @author Brantley Harris, 2009-2011 + * @author Timo Tijhof, 2011-2012 + * @source This plugin is heavily influenced by MochiKit's serializeJSON, which is + * copyrighted 2005 by Bob Ippolito. + * @source 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. + * @license MIT License */ +(function ($) { + 'use strict'; -(function( $ ) { - - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, - meta = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; + var escape = /["\\\x00-\x1f\x7f-\x9f]/g, + meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + hasOwn = Object.prototype.hasOwnProperty; /** * jQuery.toJSON - * Converts the given argument into a JSON respresentation. + * Converts the given argument into a JSON representation. * - * @param o {Mixed} The json-serializble *thing* to be converted + * @param o {Mixed} The json-serializable *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 ) { + $.toJSON = typeof JSON === 'object' && JSON.stringify ? JSON.stringify : function (o) { + if (o === null) { return 'null'; } - var type = typeof o; + var pairs, k, name, val, + type = $.type(o); - if ( type === 'undefined' ) { + if (type === 'undefined') { return undefined; } - if ( type === 'number' || type === 'boolean' ) { - return '' + o; + + // Also covers instantiated Number and Boolean objects, + // which are typeof 'object' but thanks to $.type, we + // catch them here. I don't know whether it is right + // or wrong that instantiated primitives are not + // exported to JSON as an {"object":..}. + // We choose this path because that's what the browsers did. + if (type === 'number' || type === 'boolean') { + return String(o); } - if ( type === 'string') { - return $.quoteString( 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 (typeof o.toJSON === 'function') { + return $.toJSON(o.toJSON()); + } + if (type === '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 (month < 10) { + month = '0' + month; } - if ( o.constructor === Array ) { - var ret = []; - for ( var i = 0; i < o.length; i++ ) { - ret.push( $.toJSON( o[i] ) || 'null' ); - } - return '[' + ret.join(',') + ']'; + 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"'; + } + + pairs = []; + + if ($.isArray(o)) { + for (k = 0; k < o.length; k++) { + pairs.push($.toJSON(o[k]) || 'null'); } - 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 { + return '[' + pairs.join(',') + ']'; + } + + // Any other object (plain object, RegExp, ..) + // Need to do typeof instead of $.type, because we also + // want to catch non-plain objects. + if (typeof o === 'object') { + for (k in o) { + // Only include own properties, + // Filter out inherited prototypes + if (hasOwn.call(o, k)) { // Keys must be numerical or string. Skip others - continue; - } - type = typeof o[k]; + type = typeof k; + if (type === 'number') { + name = '"' + k + '"'; + } else if (type === 'string') { + name = $.quoteString(k); + } else { + 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; + if (type !== 'function' && type !== 'undefined') { + val = $.toJSON(o[k]); + pairs.push(name + ':' + val); + } } - val = $.toJSON( o[k] ); - pairs.push( name + ':' + val ); } - return '{' + pairs.join( ',' ) + '}'; + return '{' + pairs.join(',') + '}'; } }; /** * jQuery.evalJSON - * Evaluates a given piece of json source. + * Evaluates a given json string. * - * @param src {String} + * @param str {String} */ - $.evalJSON = typeof JSON === 'object' && JSON.parse - ? JSON.parse - : function( src ) { - return eval('(' + src + ')'); + $.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) { + /*jshint evil: true */ + return eval('(' + str + ')'); }; /** * jQuery.secureEvalJSON * Evals JSON in a way that is *more* secure. * - * @param src {String} + * @param str {String} */ - $.secureEvalJSON = typeof JSON === 'object' && JSON.parse - ? JSON.parse - : function( src ) { - + $.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) { var filtered = - src - .replace( /\\["\\\/bfnrtu]/g, '@' ) - .replace( /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace( /(?:^|:|,)(?:\s*\[)+/g, ''); + str + .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.' ); + if (/^[\],:{}\s]*$/.test(filtered)) { + /*jshint evil: true */ + return eval('(' + str + ')'); } + throw new SyntaxError('Error parsing JSON, source is not valid.'); }; /** @@ -176,18 +182,18 @@ * >>> 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 ) { + $.quoteString = function (str) { + if (str.match(escape)) { + return '"' + str.replace(escape, function (a) { var c = meta[a]; - if ( typeof c === 'string' ) { + if (typeof c === 'string') { return c; } c = a.charCodeAt(); return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); }) + '"'; } - return '"' + string + '"'; + return '"' + str + '"'; }; -})( jQuery ); +}(jQuery)); diff --git a/resources/jquery/jquery.localize.js b/resources/jquery/jquery.localize.js index 3e786ec2..d9a2b199 100644 --- a/resources/jquery/jquery.localize.js +++ b/resources/jquery/jquery.localize.js @@ -1,9 +1,31 @@ /** - * Simple Placeholder-based Localization + * @class jQuery.plugin.localize + */ +( function ( $, mw ) { + +/** + * Gets a localized message, using parameters from options if present. + * @ignore + * + * @param {Object} options + * @param {string} key + * @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 elements with localized text and adding + * localized title and alt attributes to elements with title-msg and alt-msg attributes + * respectively. * - * Call on a selection of HTML which contains elements or elements + * Call on a selection of HTML which contains `` elements or elements * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes. - * elements will be replaced with localized text, *-msg attributes will be replaced + * `` 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: @@ -77,34 +99,12 @@ * Appends something like this to the body... *

You may not get there all in one piece.

* - */ -( 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 elements with localized text and adding - * localized title and alt attributes to elements with title-msg and alt-msg attributes - * respectively. - * * @method * @param {Object} options Map of options to be used while localizing - * @param {String} options.prefix String to prepend to all message keys + * @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 + * @return {jQuery} */ $.fn.localize = function ( options ) { var $target = this, @@ -162,4 +162,9 @@ $.fn.localize = function ( options ) { // Let IE know about the msg tag before it's used... document.createElement( 'msg' ); +/** + * @class jQuery + * @mixins jQuery.plugin.localize + */ + }( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.makeCollapsible.js b/resources/jquery/jquery.makeCollapsible.js index 0a4d3645..1407f53b 100644 --- a/resources/jquery/jquery.makeCollapsible.js +++ b/resources/jquery/jquery.makeCollapsible.js @@ -2,340 +2,391 @@ * jQuery makeCollapsible * * This will enable collapsible-functionality on all passed elements. - * Will prevent binding twice to the same element. - * Initial state is expanded by default, this can be overriden by adding class - * "mw-collapsed" to the "mw-collapsible" element. - * Elements made collapsible have class "mw-made-collapsible". - * Except for tables and lists, the inner content is wrapped in "mw-collapsible-content". + * - Will prevent binding twice to the same element. + * - Initial state is expanded by default, this can be overriden by adding class + * "mw-collapsed" to the "mw-collapsible" element. + * - Elements made collapsible have jQuery data "mw-made-collapsible" set to true. + * - The inner content is wrapped in a "div.mw-collapsible-content" (except for tables and lists). * - * @author Krinkle + * @author Krinkle, 2011-2012 * * Dual license: * @license CC-BY 3.0 * @license GPL2 */ ( function ( $, mw ) { + var lpx = 'jquery.makeCollapsible> '; + + /** + * @param {jQuery} $collapsible + * @param {string} action The action this function will take ('expand' or 'collapse'). + * @param {jQuery|null} [optional] $defaultToggle + * @param {Object|undefined} options + */ + function toggleElement( $collapsible, action, $defaultToggle, options ) { + var $collapsibleContent, $containers; + options = options || {}; + + // Validate parameters + + // $collapsible must be an instance of jQuery + if ( !$collapsible.jquery ) { + return; + } + if ( action !== 'expand' && action !== 'collapse' ) { + // action must be string with 'expand' or 'collapse' + return; + } + if ( $defaultToggle === undefined ) { + $defaultToggle = null; + } + if ( $defaultToggle !== null && !$defaultToggle.jquery ) { + // is optional (may be undefined), but if defined it must be an instance of jQuery. + // If it's not, abort right away. + // After this $defaultToggle is either null or a valid jQuery instance. + return; + } -$.fn.makeCollapsible = function () { - - return this.each(function () { + // Handle different kinds of elements - // Define reused variables and functions - 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 ) { - var $collapsibleContent, $containers; + if ( !options.plainMode && $collapsible.is( 'table' ) ) { + // Tables + $containers = $collapsible.find( '> tbody > tr' ); + if ( $defaultToggle ) { + // Exclude table row containing togglelink + $containers = $containers.not( $defaultToggle.closest( 'tr' ) ); + } - // Validate parameters - if ( !$collapsible.jquery ) { // $collapsible must be an instance of jQuery - return; - } - if ( action !== 'expand' && action !== 'collapse' ) { - // action must be string with 'expand' or 'collapse' - return; - } - if ( $defaultToggle === undefined ) { - $defaultToggle = null; - } - if ( $defaultToggle !== null && !($defaultToggle instanceof $) ) { - // is optional (may be undefined), but if defined it must be an instance of jQuery. - // If it's not, abort right away. - // After this $defaultToggle is either null or a valid jQuery instance. - return; + if ( action === 'collapse' ) { + // Hide all table rows of this table + // Slide doesn't work with tables, but fade does as of jQuery 1.1.3 + // http://stackoverflow.com/questions/467336#920480 + if ( options.instantHide ) { + $containers.hide(); + } else { + $containers.stop( true, true ).fadeOut(); } + } else { + $containers.stop( true, true ).fadeIn(); + } - if ( action === 'collapse' ) { - - // Collapse the element - if ( $collapsible.is( 'table' ) ) { - // Hide all table rows of this table - // Slide doens't work with tables, but fade does as of jQuery 1.1.3 - // http://stackoverflow.com/questions/467336#920480 - $containers = $collapsible.find( '>tbody>tr' ); - if ( $defaultToggle ) { - // Exclude tablerow containing togglelink - $containers.not( $defaultToggle.closest( 'tr' ) ).stop(true, true).fadeOut(); - } else { - if ( instantHide ) { - $containers.hide(); - } else { - $containers.stop( true, true ).fadeOut(); - } - } - - } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) { - $containers = $collapsible.find( '> li' ); - if ( $defaultToggle ) { - // Exclude list-item containing togglelink - $containers.not( $defaultToggle.parent() ).stop( true, true ).slideUp(); - } else { - if ( instantHide ) { - $containers.hide(); - } else { - $containers.stop( true, true ).slideUp(); - } - } + } else if ( !options.plainMode && ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) ) { + // Lists + $containers = $collapsible.find( '> li' ); + if ( $defaultToggle ) { + // Exclude list-item containing togglelink + $containers = $containers.not( $defaultToggle.parent() ); + } - } else { //
,

etc. - $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); + if ( action === 'collapse' ) { + if ( options.instantHide ) { + $containers.hide(); + } else { + $containers.stop( true, true ).slideUp(); + } + } else { + $containers.stop( true, true ).slideDown(); + } - // If a collapsible-content is defined, collapse it - if ( $collapsibleContent.length ) { - if ( instantHide ) { - $collapsibleContent.hide(); - } else { - $collapsibleContent.slideUp(); - } + } else { + // Everything else:

,

etc. + $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); - // Otherwise assume this is a customcollapse with a remote toggle - // .. and there is no collapsible-content because the entire element should be toggled - } else { - if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { - $collapsible.fadeOut(); - } else { - $collapsible.slideUp(); - } - } + // If a collapsible-content is defined, act on it + if ( !options.plainMode && $collapsibleContent.length ) { + if ( action === 'collapse' ) { + if ( options.instantHide ) { + $collapsibleContent.hide(); + } else { + $collapsibleContent.slideUp(); } - } else { + $collapsibleContent.slideDown(); + } - // Expand the element - if ( $collapsible.is( 'table' ) ) { - $containers = $collapsible.find( '>tbody>tr' ); - if ( $defaultToggle ) { - // Exclude tablerow containing togglelink - $containers.not( $defaultToggle.parent().parent() ).stop(true, true).fadeIn(); - } else { - $containers.stop(true, true).fadeIn(); - } - - } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) { - $containers = $collapsible.find( '> li' ); - if ( $defaultToggle ) { - // Exclude list-item containing togglelink - $containers.not( $defaultToggle.parent() ).stop( true, true ).slideDown(); - } else { - $containers.stop( true, true ).slideDown(); - } - - } else { //

,

etc. - $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' ); - - // If a collapsible-content is defined, collapse it - if ( $collapsibleContent.length ) { - $collapsibleContent.slideDown(); - - // Otherwise assume this is a customcollapse with a remote toggle - // .. and there is no collapsible-content because the entire element should be toggled + // Otherwise assume this is a customcollapse with a remote toggle + // .. and there is no collapsible-content because the entire element should be toggled + } else { + if ( action === 'collapse' ) { + if ( options.instantHide ) { + $collapsible.hide(); + } else { + if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { + $collapsible.fadeOut(); } else { - if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { - $collapsible.fadeIn(); - } else { - $collapsible.slideDown(); - } + $collapsible.slideUp(); } } - } - }, - // Toggles collapsible and togglelink class and updates text label - toggleLinkDefault = function ( that, e ) { - var $that = $(that), - $collapsible = $that.closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' ); - e.preventDefault(); - e.stopPropagation(); - - // It's expanded right now - if ( !$that.hasClass( 'mw-collapsible-toggle-collapsed' ) ) { - // Change link to "Show" - $that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' ); - if ( $that.find( '> a' ).length ) { - $that.find( '> a' ).text( expandtext ); - } else { - $that.text( expandtext ); - } - // Collapse element - toggleElement( $collapsible, 'collapse', $that ); - - // It's collapsed right now } else { - // Change link to "Hide" - $that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' ); - if ( $that.find( '> a' ).length ) { - $that.find( '> a' ).text( collapsetext ); + if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { + $collapsible.fadeIn(); } else { - $that.text( collapsetext ); + $collapsible.slideDown(); } - // Expand element - toggleElement( $collapsible, 'expand', $that ); - } - 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' ) ) { - return true; } - e.preventDefault(); - e.stopPropagation(); + } + } + } + + /** + * Handles clicking on the collapsible element toggle and other + * situations where a collapsible element is toggled (e.g. the initial + * toggle for collapsed ones). + * + * @param {jQuery} $toggle the clickable toggle itself + * @param {jQuery} $collapsible the collapsible element + * @param {jQuery.Event|null} e either the event or null if unavailable + * @param {Object|undefined} options + */ + function togglingHandler( $toggle, $collapsible, event, options ) { + var wasCollapsed, $textContainer, collapseText, expandText; + + if ( event ) { + // Don't fire if a link was clicked, if requested (for premade togglers by default) + if ( options.linksPassthru && $.nodeName( event.target, 'a' ) ) { + return true; + } else { + event.preventDefault(); + event.stopPropagation(); + } + } - // It's expanded right now - if ( !$that.hasClass( 'mw-collapsible-toggle-collapsed' ) ) { - // Change toggle to collapsed - $that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' ); - // Collapse element - toggleElement( $collapsible, 'collapse', $that ); + wasCollapsed = $collapsible.hasClass( 'mw-collapsed' ); - // It's collapsed right now - } else { - // Change toggle to expanded - $that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' ); - // Expand element - toggleElement( $collapsible, 'expand', $that ); - } - return; - }, - // Toggles customcollapsible - toggleLinkCustom = function ( $that, e, $collapsible ) { - // For the initial state call of customtogglers there is no event passed - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - // Get current state and toggle to the opposite - var action = $collapsible.hasClass( 'mw-collapsed' ) ? 'expand' : 'collapse'; - $collapsible.toggleClass( 'mw-collapsed' ); - toggleElement( $collapsible, action, $that ); - - }; + // Toggle the state of the collapsible element (that is, expand or collapse) + $collapsible.toggleClass( 'mw-collapsed', !wasCollapsed ); - // Use custom text or default ? - if ( !collapsetext ) { - collapsetext = mw.msg( 'collapsible-collapse' ); - } - if ( !expandtext ) { - expandtext = mw.msg( 'collapsible-expand' ); + // Toggle the mw-collapsible-toggle classes, if requested (for default and premade togglers by default) + if ( options.toggleClasses ) { + $toggle + .toggleClass( 'mw-collapsible-toggle-collapsed', !wasCollapsed ) + .toggleClass( 'mw-collapsible-toggle-expanded', wasCollapsed ); } - // Create toggle link with a space around the brackets ( [text] ) - var $toggleLink = - $( '' ) - .text( collapsetext ) - .wrap( '' ) - .parent() - .prepend( ' [' ) - .append( '] ' ) - .on( 'click.mw-collapse', function ( e ) { - toggleLinkDefault( this, e ); - } ); + // Toggle the text ("Show"/"Hide"), if requested (for default togglers by default) + if ( options.toggleText ) { + collapseText = options.toggleText.collapseText; + expandText = options.toggleText.expandText; - // Return if it has been enabled already. - if ( $that.hasClass( 'mw-made-collapsible' ) ) { - return; - } else { - $that.addClass( 'mw-made-collapsible' ); + $textContainer = $toggle.find( '> a' ); + if ( !$textContainer.length ) { + $textContainer = $toggle; + } + $textContainer.text( wasCollapsed ? collapseText : expandText ); } - // Check if this element has a custom position for the toggle link - // (ie. outside the container or deeper inside the tree) - // Then: Locate the custom toggle link(s) and bind them - if ( ( $that.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) { + // And finally toggle the element state itself + toggleElement( $collapsible, wasCollapsed ? 'expand' : 'collapse', $toggle, options ); + } + + /** + * Toggles collapsible and togglelink class and updates text label. + * + * @param {jQuery} $that + * @param {jQuery.Event} e + * @param {Object|undefined} options + */ + function toggleLinkDefault( $that, e, options ) { + var $collapsible = $that.closest( '.mw-collapsible' ); + options = $.extend( { toggleClasses: true }, options ); + togglingHandler( $that, $collapsible, e, options ); + } + + /** + * Toggles collapsible and togglelink class. + * + * @param {jQuery} $that + * @param {jQuery.Event} e + * @param {Object|undefined} options + */ + function toggleLinkPremade( $that, e, options ) { + var $collapsible = $that.eq( 0 ).closest( '.mw-collapsible' ); + options = $.extend( { toggleClasses: true, linksPassthru: true }, options ); + togglingHandler( $that, $collapsible, e, options ); + } + + /** + * Toggles customcollapsible. + * + * @param {jQuery} $that + * @param {jQuery.Event} e + * @param {Object|undefined} options + * @param {jQuery} $collapsible + */ + function toggleLinkCustom( $that, e, options, $collapsible ) { + options = $.extend( {}, options ); + togglingHandler( $that, $collapsible, e, options ); + } + + /** + * Make any element collapsible. + * + * Supported options: + * - collapseText: text to be used for the toggler when clicking it would + * collapse the element. Default: the 'data-collapsetext' attribute of + * the collapsible element or the content of 'collapsible-collapse' + * message. + * - expandText: text to be used for the toggler when clicking it would + * expand the element. Default: the 'data-expandtext' attribute of + * the collapsible element or the content of 'collapsible-expand' + * message. + * - collapsed: boolean, whether to collapse immediately. By default + * collapse only if the elements has the 'mw-collapsible' class. + * - $customTogglers: jQuerified list of elements to be used as togglers + * for this collapsible element. By default, if the collapsible element + * has an id attribute like 'mw-customcollapsible-XXX', elements with a + * *class* of 'mw-customtoggle-XXX' are made togglers for it. + * - plainMode: boolean, whether to use a "plain mode" when making the + * element collapsible - that is, hide entire tables and lists (instead + * of hiding only all rows but first of tables, and hiding each list + * item separately for lists) and don't wrap other elements in + * div.mw-collapsible-content. May only be used with custom togglers. + */ + $.fn.makeCollapsible = function ( options ) { + return this.each(function () { + var $collapsible, collapsetext, expandtext, $toggle, $toggleLink, $firstItem, collapsibleId, + $customTogglers, firstval; + + if ( options === undefined ) { + options = {}; + } - var thatId = $that.attr( 'id' ), - $customTogglers = $( '.' + thatId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) ); - mw.log( lpx + 'Found custom collapsible: #' + thatId ); + // Ensure class "mw-collapsible" is present in case .makeCollapsible() + // is called on element(s) that don't have it yet. + $collapsible = $(this).addClass( 'mw-collapsible' ); - // Double check that there is actually a customtoggle link - if ( $customTogglers.length ) { - $customTogglers.on( 'click.mw-collapse', function ( e ) { - toggleLinkCustom( $(this), e, $that ); - } ); + // Return if it has been enabled already. + if ( $collapsible.data( 'mw-made-collapsible' ) ) { + return; } else { - mw.log( lpx + '#' + thatId + ': Missing toggler!' ); + $collapsible.data( 'mw-made-collapsible', true ); } - // Initial state - if ( $that.hasClass( 'mw-collapsed' ) ) { - $that.removeClass( 'mw-collapsed' ); - toggleLinkCustom( $customTogglers, null, $that ); + // Use custom text or default? + collapsetext = options.collapseText || $collapsible.attr( 'data-collapsetext' ) || mw.msg( 'collapsible-collapse' ); + expandtext = options.expandText || $collapsible.attr( 'data-expandtext' ) || mw.msg( 'collapsible-expand' ); + + // Create toggle link with a space around the brackets ( [text] ) + $toggleLink = + $( '' ) + .text( collapsetext ) + .wrap( '' ) + .parent() + .prepend( ' [' ) + .append( '] ' ) + .on( 'click.mw-collapse', function ( e, opts ) { + opts = $.extend( { toggleText: { collapseText: collapsetext, expandText: expandtext } }, options, opts ); + toggleLinkDefault( $(this), e, opts ); + } ); + + // Check if this element has a custom position for the toggle link + // (ie. outside the container or deeper inside the tree) + if ( options.$customTogglers ) { + $customTogglers = $( options.$customTogglers ); + } else { + collapsibleId = $collapsible.attr( 'id' ) || ''; + if ( collapsibleId.indexOf( 'mw-customcollapsible-' ) === 0 ) { + mw.log( lpx + 'Found custom collapsible: #' + collapsibleId ); + $customTogglers = $( '.' + collapsibleId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) ); + + // Double check that there is actually a customtoggle link + if ( !$customTogglers.length ) { + mw.log( lpx + '#' + collapsibleId + ': Missing toggler!' ); + } + } } - // If this is not a custom case, do the default: - // Wrap the contents add the toggle link - } else { - - // 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 = $that.find( 'tr:first th, tr:first td' ); - $toggle = $firstRowCells.find( '> .mw-collapsible-toggle' ); + // Bind the custom togglers + if ( $customTogglers && $customTogglers.length ) { + $customTogglers.on( 'click.mw-collapse', function ( e, opts ) { + opts = $.extend( {}, options, opts ); + toggleLinkCustom( $(this), e, opts, $collapsible ); + } ); - // If theres no toggle link, add it to the last cell - if ( !$toggle.length ) { - $firstRowCells.eq(-1).prepend( $toggleLink ); - } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { - toggleLinkPremade( $toggle, e ); - } ); + // Initial state + if ( options.collapsed || $collapsible.hasClass( 'mw-collapsed' ) ) { + // Remove here so that the toggler goes in the right direction, + // It re-adds the class. + $collapsible.removeClass( 'mw-collapsed' ); + toggleLinkCustom( $customTogglers, null, $.extend( { instantHide: true }, options ), $collapsible ); } - } else if ( $that.is( 'ul' ) || $that.is( 'ol' ) ) { - // The toggle-link will be in the first list-item - var $firstItem = $that.find( 'li:first' ); - $toggle = $firstItem.find( '> .mw-collapsible-toggle' ); - - // If theres no toggle link, add it - if ( !$toggle.length ) { - // Make sure the numeral order doesn't get messed up, force the first (soon to be second) item - // 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' || firstval === -1 ) { - $firstItem.attr( 'value', '1' ); + // If this is not a custom case, do the default: + // Wrap the contents and add the toggle link + } else { + // Elements are treated differently + if ( $collapsible.is( 'table' ) ) { + // The toggle-link will be in one the the cells (td or th) of the first row + $firstItem = $collapsible.find( 'tr:first th, tr:first td' ); + $toggle = $firstItem.find( '> .mw-collapsible-toggle' ); + + // If theres no toggle link, add it to the last cell + if ( !$toggle.length ) { + $firstItem.eq(-1).prepend( $toggleLink ); + } else { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { + opts = $.extend( {}, options, opts ); + toggleLinkPremade( $toggle, e, opts ); + } ); } - $that.prepend( $toggleLink.wrap( '

  • ' ).parent() ); - } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { - toggleLinkPremade( $toggle, e ); - } ); - } - } else { //
    ,

    etc. + } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) { + // The toggle-link will be in the first list-item + $firstItem = $collapsible.find( 'li:first' ); + $toggle = $firstItem.find( '> .mw-collapsible-toggle' ); + + // If theres no toggle link, add it + if ( !$toggle.length ) { + // Make sure the numeral order doesn't get messed up, force the first (soon to be second) item + // 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. + firstval = $firstItem.attr( 'value' ); + if ( firstval === undefined || !firstval || firstval === '-1' || firstval === -1 ) { + $firstItem.attr( 'value', '1' ); + } + $collapsible.prepend( $toggleLink.wrap( '

  • ' ).parent() ); + } else { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { + opts = $.extend( {}, options, opts ); + toggleLinkPremade( $toggle, e, opts ); + } ); + } - // The toggle-link will be the first child of the element - $toggle = $that.find( '> .mw-collapsible-toggle' ); + } else { //
    ,

    etc. - // If a direct child .content-wrapper does not exists, create it - if ( !$that.find( '> .mw-collapsible-content' ).length ) { - $that.wrapInner( '

    ' ); - } + // The toggle-link will be the first child of the element + $toggle = $collapsible.find( '> .mw-collapsible-toggle' ); - // If theres no toggle link, add it - if ( !$toggle.length ) { - $that.prepend( $toggleLink ); - } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) { - toggleLinkPremade( $toggle, e ); - } ); + // If a direct child .content-wrapper does not exists, create it + if ( !$collapsible.find( '> .mw-collapsible-content' ).length ) { + $collapsible.wrapInner( '
    ' ); + } + + // If theres no toggle link, add it + if ( !$toggle.length ) { + $collapsible.prepend( $toggleLink ); + } else { + $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { + opts = $.extend( {}, options, opts ); + toggleLinkPremade( $toggle, e, opts ); + } ); + } } } - } - - // Initial state (only for those that are not custom) - if ( $that.hasClass( 'mw-collapsed' ) && ( $that.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) { - $that.removeClass( 'mw-collapsed' ); - // The collapsible element could have multiple togglers - // To toggle the initial state only click one of them (ie. the first one, eq(0) ) - // Else it would go like: hide,show,hide,show for each toggle link. - toggleElement( $that, 'collapse', $toggleLink.eq(0), /* instantHide = */ true ); - $toggleLink.eq(0).click(); - } - } ); -}; + // Initial state (only for those that are not custom, + // because the initial state of those has been taken care of already). + if ( + ( options.collapsed || $collapsible.hasClass( 'mw-collapsed' ) ) && + ( !$customTogglers || !$customTogglers.length ) + ) { + $collapsible.removeClass( 'mw-collapsed' ); + // The collapsible element could have multiple togglers + // To toggle the initial state only click one of them (ie. the first one, eq(0) ) + // Else it would go like: hide,show,hide,show for each toggle link. + // This is just like it would be in reality (only one toggle is clicked at a time). + $toggleLink.eq( 0 ).trigger( 'click', [ { instantHide: true } ] ); + } + } ); + }; }( jQuery, mediaWiki ) ); diff --git a/resources/jquery/jquery.mw-jump.js b/resources/jquery/jquery.mw-jump.js index 36b6690c..e2868341 100644 --- a/resources/jquery/jquery.mw-jump.js +++ b/resources/jquery/jquery.mw-jump.js @@ -1,12 +1,12 @@ /** * JavaScript to show jump links to motor-impaired users when they are focused. */ -jQuery( function( $ ) { +jQuery( function ( $ ) { - $('.mw-jump').delegate( 'a', 'focus blur', function( e ) { - // Confusingly jQuery leaves e.type as "focusout" for delegated blur events - if ( e.type === "blur" || e.type === "focusout" ) { - $( this ).closest( '.mw-jump' ).css({ height: '0' }); + $( '.mw-jump' ).on( 'focus blur', 'a', function ( e ) { + // Confusingly jQuery leaves e.type as focusout for delegated blur events + if ( e.type === 'blur' || e.type === 'focusout' ) { + $( this ).closest( '.mw-jump' ).css({ height: 0 }); } else { $( this ).closest( '.mw-jump' ).css({ height: 'auto' }); } diff --git a/resources/jquery/jquery.mwExtension.js b/resources/jquery/jquery.mwExtension.js index bbffd7b7..de399788 100644 --- a/resources/jquery/jquery.mwExtension.js +++ b/resources/jquery/jquery.mwExtension.js @@ -15,12 +15,13 @@ return str.charAt( 0 ).toUpperCase() + str.substr( 1 ); }, escapeRE: function ( str ) { - return str.replace ( /([\\{}()|.?*+\-\^$\[\]])/g, "\\$1" ); + return str.replace ( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ); }, isDomElement: function ( el ) { return !!el && !!el.nodeType; }, isEmpty: function ( v ) { + var key; if ( v === '' || v === 0 || v === '0' || v === null || v === false || v === undefined ) { @@ -32,7 +33,7 @@ return true; } if ( typeof v === 'object' ) { - for ( var key in v ) { + for ( key in v ) { return false; } return true; diff --git a/resources/jquery/jquery.qunit.completenessTest.js b/resources/jquery/jquery.qunit.completenessTest.js index 1475af2a..20e6678e 100644 --- a/resources/jquery/jquery.qunit.completenessTest.js +++ b/resources/jquery/jquery.qunit.completenessTest.js @@ -12,10 +12,8 @@ * * @author Timo Tijhof, 2011-2012 */ -/*global jQuery, QUnit */ -/*jshint eqeqeq:false, eqnull:false, forin:false */ ( function ( $ ) { - "use strict"; + 'use strict'; var util, hasOwn = Object.prototype.hasOwnProperty, diff --git a/resources/jquery/jquery.qunit.css b/resources/jquery/jquery.qunit.css index 55970e00..d7fc0c8e 100644 --- a/resources/jquery/jquery.qunit.css +++ b/resources/jquery/jquery.qunit.css @@ -1,5 +1,5 @@ /** - * QUnit v1.10.0 - A JavaScript Unit Testing Framework + * QUnit v1.11.0 - A JavaScript Unit Testing Framework * * http://qunitjs.com * @@ -20,7 +20,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -111,7 +111,12 @@ color: #000; } -#qunit-tests ol { +#qunit-tests li .runtime { + float: right; + font-size: smaller; +} + +.qunit-assert-list { margin-top: 0.5em; padding: 0.5em; @@ -122,6 +127,10 @@ -webkit-border-radius: 5px; } +.qunit-collapsed { + display: none; +} + #qunit-tests table { border-collapse: collapse; margin-top: .2em; diff --git a/resources/jquery/jquery.qunit.js b/resources/jquery/jquery.qunit.js index d4f17b5a..302545f4 100644 --- a/resources/jquery/jquery.qunit.js +++ b/resources/jquery/jquery.qunit.js @@ -1,5 +1,5 @@ /** - * QUnit v1.10.0 - A JavaScript Unit Testing Framework + * QUnit v1.11.0 - A JavaScript Unit Testing Framework * * http://qunitjs.com * @@ -11,6 +11,7 @@ (function( window ) { var QUnit, + assert, config, onErrorFnPrev, testId = 0, @@ -20,18 +21,67 @@ var QUnit, // 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 { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { + return false; + } + }()) + }, + /** + * Provides a normalized error string, correcting an issue + * with IE 7 (and prior) where Error.prototype.toString is + * not properly implemented + * + * Based on http://es5.github.com/#x15.11.4.4 + * + * @param {String|Error} error + * @return {String} error message + */ + errorString = function( error ) { + var name, message, + errorString = error.toString(); + if ( errorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return errorString; } - }()) -}; + }, + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + objectValues = function( obj ) { + // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. + /*jshint newcap: false */ + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val) : val; + } + } + return vals; + }; function Test( settings ) { extend( this, settings ); @@ -44,11 +94,11 @@ Test.count = 0; Test.prototype = { init: function() { var a, b, li, - tests = id( "qunit-tests" ); + tests = id( "qunit-tests" ); if ( tests ) { b = document.createElement( "strong" ); - b.innerHTML = this.name; + b.innerHTML = this.nameHtml; // `a` initialized at top of scope a = document.createElement( "a" ); @@ -92,6 +142,7 @@ Test.prototype = { teardown: function() {} }, this.moduleTestEnvironment ); + this.started = +new Date(); runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module @@ -111,7 +162,7 @@ Test.prototype = { try { this.testEnvironment.setup.call( this.testEnvironment ); } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); } }, run: function() { @@ -120,22 +171,28 @@ Test.prototype = { var running = id( "qunit-testresult" ); if ( running ) { - running.innerHTML = "Running:
    " + this.name; + running.innerHTML = "Running:
    " + this.nameHtml; } if ( this.async ) { QUnit.stop(); } + this.callbackStarted = +new Date(); + if ( config.notrycatch ) { this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; return; } try { this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; } catch( e ) { - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); + this.callbackRuntime = +new Date() - this.callbackStarted; + + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -148,38 +205,43 @@ Test.prototype = { teardown: function() { config.current = this; if ( config.notrycatch ) { + if ( typeof this.callbackRuntime === "undefined" ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + } 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 ) ); + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); } } checkPollution(); }, finish: function() { config.current = this; - if ( config.requireExpects && this.expected == null ) { + 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 ) { + } 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 ) { + } 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 assertion, a, b, i, li, ol, + var i, assertion, a, b, time, li, ol, test = this, good = 0, bad = 0, tests = id( "qunit-tests" ); + this.runtime = +new Date() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { ol = document.createElement( "ol" ); + ol.className = "qunit-assert-list"; for ( i = 0; i < this.assertions.length; i++ ) { assertion = this.assertions[i]; @@ -208,22 +270,22 @@ Test.prototype = { } if ( bad === 0 ) { - ol.style.display = "none"; + addClass( ol, "qunit-collapsed" ); } // `b` initialized at top of scope b = document.createElement( "strong" ); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; + var next = b.parentNode.lastChild, + collapsed = hasClass( next, "qunit-collapsed" ); + ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); }); addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { @@ -231,13 +293,19 @@ Test.prototype = { } }); + // `time` initialized at top of scope + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = this.runtime + " ms"; + // `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( time ); li.appendChild( ol ); } else { @@ -255,7 +323,8 @@ Test.prototype = { module: this.module, failed: bad, passed: this.assertions.length - bad, - total: this.assertions.length + total: this.assertions.length, + duration: this.runtime }); QUnit.reset(); @@ -321,7 +390,7 @@ QUnit = { test: function( testName, expected, callback, async ) { var test, - name = "" + escapeInnerText( testName ) + ""; + nameHtml = "" + escapeText( testName ) + ""; if ( arguments.length === 2 ) { callback = expected; @@ -329,11 +398,11 @@ QUnit = { } if ( config.currentModule ) { - name = "" + config.currentModule + ": " + name; + nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; } test = new Test({ - name: name, + nameHtml: nameHtml, testName: testName, expected: expected, async: async, @@ -360,6 +429,18 @@ QUnit = { }, start: function( count ) { + // QUnit hasn't been initialized yet. + // Note: RequireJS (et al) may delay onLoad + if ( config.semaphore === undefined ) { + QUnit.begin(function() { + // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first + setTimeout(function() { + QUnit.start( count ); + }); + }); + return; + } + config.semaphore -= count || 1; // don't start until equal number of stop-calls if ( config.semaphore > 0 ) { @@ -368,6 +449,8 @@ QUnit = { // ignore if start is called more often then stop if ( config.semaphore < 0 ) { config.semaphore = 0; + QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); + return; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { @@ -403,11 +486,14 @@ QUnit = { } }; +// `assert` initialized at top of scope // Asssert helpers -// All of these must call either QUnit.push() or manually do: +// All of these must either call QUnit.push() or manually do: // - runLoggingCallbacks( "log", .. ); // - config.current.assertions.push({ .. }); -QUnit.assert = { +// We attach it to the QUnit object *after* we expose the public API, +// otherwise `assert` will become a global variable in browsers (#341). +assert = { /** * Asserts rough true-ish result. * @name ok @@ -428,14 +514,14 @@ QUnit.assert = { message: msg }; - msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); + msg = escapeText( msg || (result ? "okay" : "failed" ) ); msg = "" + msg + ""; if ( !result ) { source = sourceFromStacktrace( 2 ); if ( source ) { details.source = source; - msg += "
    Source:
    " + escapeInnerText( source ) + "
    "; + msg += "
    Source:
    " + escapeText( source ) + "
    "; } } runLoggingCallbacks( "log", QUnit, details ); @@ -453,6 +539,7 @@ QUnit.assert = { * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ QUnit.push( expected == actual, actual, expected, message ); }, @@ -461,9 +548,30 @@ QUnit.assert = { * @function */ notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ QUnit.push( expected != actual, actual, expected, message ); }, + /** + * @name propEqual + * @function + */ + propEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notPropEqual + * @function + */ + notPropEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + /** * @name deepEqual * @function @@ -496,8 +604,9 @@ QUnit.assert = { QUnit.push( expected !== actual, actual, expected, message ); }, - throws: function( block, expected, message ) { + "throws": function( block, expected, message ) { var actual, + expectedOutput = expected, ok = false; // 'expected' is optional @@ -518,18 +627,20 @@ QUnit.assert = { // we don't want to validate thrown error if ( !expected ) { ok = true; + expectedOutput = null; // expected is a regexp } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( actual ); + ok = expected.test( errorString( actual ) ); // expected is a constructor } else if ( actual instanceof expected ) { ok = true; // expected is a validation function which returns true is validation passed } else if ( expected.call( {}, actual ) === true ) { + expectedOutput = null; ok = true; } - QUnit.push( ok, actual, null, message ); + QUnit.push( ok, actual, expectedOutput, message ); } else { QUnit.pushFailure( message, null, 'No exception was thrown.' ); } @@ -538,15 +649,16 @@ QUnit.assert = { /** * @deprecate since 1.8.0 - * Kept assertion helpers in root for backwards compatibility + * Kept assertion helpers in root for backwards compatibility. */ -extend( QUnit, QUnit.assert ); +extend( QUnit, assert ); /** * @deprecated since 1.9.0 - * Kept global "raises()" for backwards compatibility + * Kept root "raises()" for backwards compatibility. + * (Note that we don't introduce assert.raises). */ -QUnit.raises = QUnit.assert.throws; +QUnit.raises = assert[ "throws" ]; /** * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 @@ -622,6 +734,15 @@ config = { moduleDone: [] }; +// 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 QUnit object + window.QUnit = QUnit; +} + // Initialize more QUnit.config and QUnit.urlParams (function() { var i, @@ -655,18 +776,11 @@ config = { 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 QUnit object - window.QUnit = QUnit; -} - // Extend QUnit object, // these after set here because they should not be exposed as global functions extend( QUnit, { + assert: assert, + config: config, // Initialize the configuration options @@ -681,7 +795,7 @@ extend( QUnit, { autorun: false, filter: "", queue: [], - semaphore: 0 + semaphore: 1 }); var tests, banner, result, @@ -689,7 +803,7 @@ extend( QUnit, { if ( qunit ) { qunit.innerHTML = - "

    " + escapeInnerText( document.title ) + "

    " + + "

    " + escapeText( document.title ) + "

    " + "

    " + "
    " + "

    " + @@ -745,7 +859,7 @@ extend( QUnit, { // Safe object type checking is: function( type, obj ) { - return QUnit.objectType( obj ) == type; + return QUnit.objectType( obj ) === type; }, objectType: function( obj ) { @@ -757,7 +871,8 @@ extend( QUnit, { return "null"; } - var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; + var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), + type = match && match[1] || ""; switch ( type ) { case "Number": @@ -794,16 +909,16 @@ extend( QUnit, { expected: expected }; - message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); + message = escapeText( message ) || ( result ? "okay" : "failed" ); message = "" + message + ""; output = message; if ( !result ) { - expected = escapeInnerText( QUnit.jsDump.parse(expected) ); - actual = escapeInnerText( QUnit.jsDump.parse(actual) ); + expected = escapeText( QUnit.jsDump.parse(expected) ); + actual = escapeText( QUnit.jsDump.parse(actual) ); output += ""; - if ( actual != expected ) { + if ( actual !== expected ) { output += ""; output += ""; } @@ -812,7 +927,7 @@ extend( QUnit, { if ( source ) { details.source = source; - output += ""; + output += ""; } output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    Source:
    " + escapeText( source ) + "
    "; @@ -839,19 +954,19 @@ extend( QUnit, { message: message }; - message = escapeInnerText( message ) || "error"; + message = escapeText( message ) || "error"; message = "" + message + ""; output = message; output += ""; if ( actual ) { - output += ""; + output += ""; } if ( source ) { details.source = source; - output += ""; + output += ""; } output += "
    Result:
    " + escapeInnerText( actual ) + "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    Source:
    " + escapeText( source ) + "
    "; @@ -876,7 +991,8 @@ extend( QUnit, { querystring += encodeURIComponent( key ) + "=" + encodeURIComponent( params[ key ] ) + "&"; } - return window.location.pathname + querystring.slice( 0, -1 ); + return window.location.protocol + "//" + window.location.host + + window.location.pathname + querystring.slice( 0, -1 ); }, extend: extend, @@ -907,7 +1023,7 @@ extend( QUnit.constructor.prototype, { // testStart: { name } testStart: registerLoggingCallback( "testStart" ), - // testDone: { name, failed, passed, total } + // testDone: { name, failed, passed, total, duration } testDone: registerLoggingCallback( "testDone" ), // moduleStart: { name } @@ -925,9 +1041,10 @@ QUnit.load = function() { runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, - numModules = 0, - moduleFilterHtml = "", + var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, + urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, + numModules = 0, + moduleFilterHtml = "", urlConfigHtml = "", oldconfig = extend( {}, config ); @@ -948,14 +1065,24 @@ QUnit.load = function() { }; } config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; + urlConfigHtml += ""; } - moduleFilterHtml += ""; + for ( i in config.modules ) { if ( config.modules.hasOwnProperty( i ) ) { numModules += 1; - moduleFilterHtml += ""; + moduleFilterHtml += ""; } } moduleFilterHtml += ""; @@ -1014,22 +1141,28 @@ QUnit.load = function() { 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; + urlConfigCheckboxesContainer = document.createElement("span"); + urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; + urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" + // * Fallback from event.target to event.srcElement + addEvents( urlConfigCheckboxes, "click", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.checked ? true : undefined; window.location = QUnit.url( params ); }); - toolbar.appendChild( urlConfigCheckboxes ); + toolbar.appendChild( urlConfigCheckboxesContainer ); if (numModules > 1) { moduleFilter = document.createElement( 'span' ); moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter, "change", function() { + addEvent( moduleFilter.lastChild, "change", function() { var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); }); @@ -1106,7 +1239,7 @@ function done() { " milliseconds.
    ", "", passed, - " tests of ", + " assertions of ", config.stats.all, " passed, ", config.stats.bad, @@ -1199,7 +1332,7 @@ function validTest( test ) { function extractStacktrace( e, offset ) { offset = offset === undefined ? 3 : offset; - var stack, include, i, regex; + var stack, include, i; if ( e.stacktrace ) { // Opera @@ -1213,7 +1346,7 @@ function extractStacktrace( e, offset ) { if ( fileName ) { include = []; for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) != -1 ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { break; } include.push( stack[ i ] ); @@ -1242,17 +1375,27 @@ function sourceFromStacktrace( offset ) { } } -function escapeInnerText( s ) { +/** + * Escape text for attribute or text content. + */ +function escapeText( s ) { if ( !s ) { return ""; } s = s + ""; - return s.replace( /[\&<>]/g, function( s ) { + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { switch( s ) { - case "&": return "&"; - case "<": return "<"; - case ">": return ">"; - default: return s; + case '\'': + return '''; + case '"': + return '"'; + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; } }); } @@ -1300,7 +1443,7 @@ function saveGlobal() { } } -function checkPollution( name ) { +function checkPollution() { var newGlobals, deletedGlobals, old = config.pollution; @@ -1349,16 +1492,53 @@ function extend( a, b ) { return a; } +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ function addEvent( elem, type, fn ) { + // Standards-based browsers if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); + // IE } else { - fn(); + elem.attachEvent( "on" + type, fn ); } } +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[i], type, fn ); + } +} + +function hasClass( elem, name ) { + return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += (elem.className ? " " : "") + name; + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + // Class name may appear multiple times + while ( set.indexOf(" " + name + " ") > -1 ) { + set = set.replace(" " + name + " " , " "); + } + // If possible, trim it for prettiness, but not neccecarily + elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set ); +} + function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); @@ -1372,7 +1552,6 @@ function registerLoggingCallback( key ) { // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks( key, scope, args ) { - //debugger; var i, callbacks; if ( QUnit.hasOwnProperty( key ) ) { QUnit[ key ].call(scope, args ); @@ -1414,6 +1593,7 @@ QUnit.equiv = (function() { // for string, boolean, number and null function useStrictEquality( b, a ) { + /*jshint eqeqeq:false */ if ( b instanceof a.constructor || a instanceof b.constructor ) { // to catch short annotaion VS 'new' annotation of a // declaration @@ -1610,7 +1790,8 @@ QUnit.jsDump = (function() { var reName = /^function (\w+)/, jsDump = { - parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance + // type is used mostly internally, you can fix a (custom)type in advance + parse: function( obj, type, stack ) { stack = stack || [ ]; var inStack, res, parser = this.parsers[ type || this.typeOf(obj) ]; @@ -1618,18 +1799,16 @@ QUnit.jsDump = (function() { type = typeof parser; inStack = inArray( obj, stack ); - if ( inStack != -1 ) { + if ( inStack !== -1 ) { return "recursion(" + (inStack - stack.length) + ")"; } - //else - if ( type == "function" ) { + if ( type === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - // else - return ( type == "string" ) ? parser : this.parsers.error; + return ( type === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -1656,6 +1835,8 @@ QUnit.jsDump = (function() { ( 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 if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; } else { type = typeof obj; } @@ -1664,7 +1845,8 @@ QUnit.jsDump = (function() { separator: function() { return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; }, - indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + // extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { if ( !this.multiline ) { return ""; } @@ -1693,13 +1875,16 @@ QUnit.jsDump = (function() { parsers: { window: "[Window]", document: "[Document]", - error: "[ERROR]", //when no parser is found, shouldn"t happen + error: function(error) { + return "Error(\"" + error.message + "\")"; + }, 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 + // functions never have name in IE + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; if ( name ) { ret += " " + name; @@ -1715,13 +1900,9 @@ QUnit.jsDump = (function() { 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 = []; + for ( key in map ) { + keys.push( key ); } keys.sort(); for ( i = 0; i < keys.length; i++ ) { @@ -1733,21 +1914,34 @@ QUnit.jsDump = (function() { return join( "{", ret, "}" ); }, node: function( node ) { - var a, val, + var len, i, 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" ); + ret = open + tag, + attrs = node.attributes; + + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[i].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. + // Those have values like undefined, null, 0, false, "" or "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + } } } - return ret + close + open + "/" + tag + close; + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; }, - functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function + // function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { var args, l = fn.length; @@ -1757,54 +1951,34 @@ QUnit.jsDump = (function() { args = new Array(l); while ( l-- ) { - args[l] = String.fromCharCode(97+l);//97 is 'a' + // 97 is 'a' + args[l] = String.fromCharCode(97+l); } 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 + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, string: quote, date: quote, - regexp: literal, //regex + regexp: literal, 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. + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true }; return jsDump; }()); -// from Sizzle.js -function getText( elems ) { - var i, elem, - ret = ""; - - for ( i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -} - // from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { @@ -1835,13 +2009,14 @@ function inArray( elem, array ) { * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { + /*jshint eqeqeq:false, eqnull:true */ function diff( o, n ) { var i, ns = {}, os = {}; for ( i = 0; i < n.length; i++ ) { - if ( ns[ n[i] ] == null ) { + if ( !hasOwn.call( ns, n[i] ) ) { ns[ n[i] ] = { rows: [], o: null @@ -1851,7 +2026,7 @@ QUnit.diff = (function() { } for ( i = 0; i < o.length; i++ ) { - if ( os[ o[i] ] == null ) { + if ( !hasOwn.call( os, o[i] ) ) { os[ o[i] ] = { rows: [], n: null @@ -1864,7 +2039,7 @@ QUnit.diff = (function() { if ( !hasOwn.call( ns, i ) ) { continue; } - if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { + if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] @@ -1970,7 +2145,7 @@ QUnit.diff = (function() { // for CommonJS enviroments, export everything if ( typeof exports !== "undefined" ) { - extend(exports, QUnit); + extend( exports, QUnit ); } // get at whatever the global object is, like window in browsers diff --git a/resources/jquery/jquery.spinner.js b/resources/jquery/jquery.spinner.js index 4a6ec3b4..93e30b9a 100644 --- a/resources/jquery/jquery.spinner.js +++ b/resources/jquery/jquery.spinner.js @@ -86,7 +86,7 @@ * Injects a spinner after the elements in the jQuery collection * (as siblings, not children). Collection contents remain unchanged. * - * @param {Object} opts See createSpinner() for description. + * @param {Object|String} opts See createSpinner() for description. * @return {jQuery} */ $.fn.injectSpinner = function ( opts ) { diff --git a/resources/jquery/jquery.suggestions.js b/resources/jquery/jquery.suggestions.js index d80680fc..44382f0d 100644 --- a/resources/jquery/jquery.suggestions.js +++ b/resources/jquery/jquery.suggestions.js @@ -13,11 +13,11 @@ * * Options: * - * fetch(query): Callback that should fetch suggestions and set the suggestions property. Executed in the context of the - * textbox + * fetch(query): Callback that should fetch suggestions and set the suggestions property. + * Executed in the context of the textbox * Type: Function - * cancel: Callback function to call when any pending asynchronous suggestions fetches should be canceled. - * Executed in the context of the textbox + * cancel: Callback function to call when any pending asynchronous suggestions fetches + * should be canceled. Executed in the context of the textbox * Type: Function * special: Set of callbacks for rendering and selecting * Type: Object of Functions 'render' and 'select' @@ -33,12 +33,12 @@ * Type: Number, Range: 0 - 1200, Default: 120 * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked * Type: Boolean, Default: false - * 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. + * 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 * 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' ). + * 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 @@ -49,8 +49,8 @@ $.suggestions = { /** - * Cancel any delayed updateSuggestions() call and inform the user so - * they can cancel their result fetching if they use AJAX or something + * Cancel any delayed maybeFetch() call and callback the context so + * they can cancel any async fetching if they use AJAX or something. */ cancel: function ( context ) { if ( context.data.timerID !== null ) { @@ -60,28 +60,35 @@ $.suggestions = { context.config.cancel.call( context.data.$textbox ); } }, + /** - * Restore the text the user originally typed in the textbox, before it was overwritten by highlight(). This - * restores the value the currently displayed suggestions are based on, rather than the value just before + * Restore the text the user originally typed in the textbox, before it + * was overwritten by highlight(). This 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 ) { context.data.$textbox.val( context.data.prevText ); }, + /** - * Ask the user-specified callback for new suggestions. Any previous delayed call to this function still pending - * will be canceled. If the value in the textbox is empty or hasn't changed since the last time suggestions were fetched, this - * function does nothing. + * Ask the user-specified callback for new suggestions. Any previous delayed + * call to this function still pending will be canceled. If the value in the + * textbox is empty or hasn't changed since the last time suggestions were fetched, + * this function does nothing. * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time */ update: function ( context, delayed ) { - // Only fetch if the value in the textbox changed and is not empty + // Only fetch if the value in the textbox changed and is not empty, or if the results were hidden // if the textbox is empty then clear the result div, but leave other settings intouched function maybeFetch() { if ( context.data.$textbox.val().length === 0 ) { context.data.$container.hide(); context.data.prevText = ''; - } else if ( context.data.$textbox.val() !== context.data.prevText ) { + } else if ( + context.data.$textbox.val() !== context.data.prevText || + !context.data.$container.is( ':visible' ) + ) { if ( typeof context.config.fetch === 'function' ) { context.data.prevText = context.data.$textbox.val(); context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() ); @@ -89,18 +96,19 @@ $.suggestions = { } } - // Cancel previous call - if ( context.data.timerID !== null ) { - clearTimeout( context.data.timerID ); - } + // Cancels any delayed maybeFetch call, and invokes context.config.cancel. + $.suggestions.cancel( context ); + if ( delayed ) { - // Start a new asynchronous call + // To avoid many started/aborted requests while typing, we're gonna take a short + // break before trying to fetch data. context.data.timerID = setTimeout( maybeFetch, context.config.delay ); } else { maybeFetch(); } $.suggestions.special( context ); }, + special: function ( context ) { // Allow custom rendering - but otherwise don't do any rendering if ( typeof context.config.special.render === 'function' ) { @@ -108,17 +116,21 @@ $.suggestions = { setTimeout( function () { // Render special var $special = context.data.$container.find( '.suggestions-special' ); - context.config.special.render.call( $special, context.data.$textbox.val() ); + context.config.special.render.call( $special, context.data.$textbox.val(), context ); }, 1 ); } }, + /** * 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 ) { - var newCSS; + var newCSS, + $autoEllipseMe, $result, $results, childrenWidth, + i, expWidth, matchedText, maxWidth, text; + // Validate creation using fallback values switch( property ) { case 'fetch': @@ -212,55 +224,62 @@ $.suggestions = { } context.data.$container.css( newCSS ); - var $results = context.data.$container.children( '.suggestions-results' ); + $results = context.data.$container.children( '.suggestions-results' ); $results.empty(); - var expWidth = -1; - var $autoEllipseMe = $( [] ); - var matchedText = null; - for ( var i = 0; i < context.config.suggestions.length; i++ ) { + expWidth = -1; + $autoEllipseMe = $( [] ); + matchedText = null; + for ( i = 0; i < context.config.suggestions.length; i++ ) { /*jshint loopfunc:true */ - var text = context.config.suggestions[i]; - var $result = $( '
    ' ) + text = context.config.suggestions[i]; + $result = $( '
    ' ) .addClass( 'suggestions-result' ) .attr( 'rel', i ) .data( 'text', context.config.suggestions[i] ) - .mousemove( function ( e ) { + .mousemove( function () { context.data.selectedWithMouse = true; $.suggestions.highlight( - context, $(this).closest( '.suggestions-results div' ), false + context, + $(this).closest( '.suggestions-results .suggestions-result' ), + false ); } ) .appendTo( $results ); // Allow custom rendering if ( typeof context.config.result.render === 'function' ) { - context.config.result.render.call( $result, context.config.suggestions[i] ); + context.config.result.render.call( $result, context.config.suggestions[i], context ); } else { // Add with text - if( context.config.highlightInput ) { - matchedText = context.data.prevText; - } $result.append( $( '' ) .css( 'whiteSpace', 'nowrap' ) .text( text ) ); + } - // Widen results box if needed - // New width is only calculated here, applied later - var $span = $result.children( 'span' ); - if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) { - // factor in any padding, margin, or border space on the parent - expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width()); - } - $autoEllipseMe = $autoEllipseMe.add( $result ); + if ( context.config.highlightInput ) { + matchedText = context.data.prevText; } + + // Widen results box if needed + // New width is only calculated here, applied later + childrenWidth = $result.children().outerWidth(); + if ( childrenWidth > $result.width() && childrenWidth > expWidth ) { + // factor in any padding, margin, or border space on the parent + expWidth = childrenWidth + ( context.data.$container.width() - $result.width() ); + } + $autoEllipseMe = $autoEllipseMe.add( $result ); } // Apply new width for results box, if any if ( expWidth > context.data.$container.width() ) { - var maxWidth = context.config.maxExpandFactor*context.data.$textbox.width(); + maxWidth = context.config.maxExpandFactor*context.data.$textbox.width(); context.data.$container.width( Math.min( expWidth, maxWidth ) ); } // autoEllipse the results. Has to be done after changing the width - $autoEllipseMe.autoEllipsis( { hasSpan: true, tooltip: true, matchText: matchedText } ); + $autoEllipseMe.autoEllipsis( { + hasSpan: true, + tooltip: true, + matchText: matchedText + } ); } } break; @@ -280,6 +299,7 @@ $.suggestions = { break; } }, + /** * Highlight a result in the results table * @param result to highlight: jQuery object, or 'prev' or 'next' @@ -289,30 +309,40 @@ $.suggestions = { var selected = context.data.$container.find( '.suggestions-result-current' ); if ( !result.get || selected.get( 0 ) !== result.get( 0 ) ) { if ( result === 'prev' ) { - if( selected.is( '.suggestions-special' ) ) { + if( selected.hasClass( 'suggestions-special' ) ) { result = context.data.$container.find( '.suggestions-result:last' ); } else { result = selected.prev(); + if ( !( result.length && result.hasClass( 'suggestions-result' ) ) ) { + // there is something in the DOM between selected element and the wrapper, bypass it + result = selected.parents( '.suggestions-results > *' ).prev().find( '.suggestions-result' ).eq(0); + } + if ( selected.length === 0 ) { // we are at the beginning, so lets jump to the last item if ( context.data.$container.find( '.suggestions-special' ).html() !== '' ) { result = context.data.$container.find( '.suggestions-special' ); } else { - result = context.data.$container.find( '.suggestions-results div:last' ); + result = context.data.$container.find( '.suggestions-results .suggestions-result:last' ); } } } } else if ( result === 'next' ) { if ( selected.length === 0 ) { // No item selected, go to the first one - result = context.data.$container.find( '.suggestions-results div:first' ); + result = context.data.$container.find( '.suggestions-results .suggestions-result:first' ); if ( result.length === 0 && context.data.$container.find( '.suggestions-special' ).html() !== '' ) { // No suggestion exists, go to the special one directly result = context.data.$container.find( '.suggestions-special' ); } } else { result = selected.next(); - if ( selected.is( '.suggestions-special' ) ) { + if ( !( result.length && result.hasClass( 'suggestions-result' ) ) ) { + // there is something in the DOM between selected element and the wrapper, bypass it + result = selected.parents( '.suggestions-results > *' ).next().find( '.suggestions-result' ).eq(0); + } + + if ( selected.hasClass( 'suggestions-special' ) ) { result = $( [] ); } else if ( result.length === 0 && @@ -338,13 +368,16 @@ $.suggestions = { context.data.$textbox.trigger( 'change' ); } }, + /** * Respond to keypress event * @param key Integer Code of key pressed */ keypress: function ( e, context, key ) { - var wasVisible = context.data.$container.is( ':visible' ), + var selected, + wasVisible = context.data.$container.is( ':visible' ), preventDefault = false; + switch ( key ) { // Arrow down case 40: @@ -376,7 +409,7 @@ $.suggestions = { case 13: context.data.$container.hide(); preventDefault = wasVisible; - var selected = context.data.$container.find( '.suggestions-result-current' ); + 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 @@ -420,18 +453,18 @@ $.fn.suggestions = function () { if ( context === undefined || context === null ) { context = { config: { - 'fetch' : function () {}, - 'cancel': function () {}, - 'special': {}, - 'result': {}, - '$region': $(this), - 'suggestions': [], - 'maxRows': 7, - 'delay': 120, - 'submitOnClick': false, - 'maxExpandFactor': 3, - 'expandFrom': 'auto', - 'highlightInput': false + fetch: function () {}, + cancel: function () {}, + special: {}, + result: {}, + $region: $(this), + suggestions: [], + maxRows: 7, + delay: 120, + submitOnClick: false, + maxExpandFactor: 3, + expandFrom: 'auto', + highlightInput: false } }; } @@ -480,44 +513,56 @@ $.fn.suggestions = function () { .addClass( 'suggestions' ) .append( $( '
    ' ).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 + // 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 ) { - context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' ); + context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results .suggestions-result' ); } ) .mouseup( function ( e ) { - var $result = $( e.target ).closest( '.suggestions-results div' ); - var $other = context.data.mouseDownOn; + var $result = $( e.target ).closest( '.suggestions-results .suggestions-result' ), + $other = context.data.mouseDownOn; + context.data.mouseDownOn = $( [] ); if ( $result.get( 0 ) !== $other.get( 0 ) ) { return; } - $.suggestions.highlight( context, $result, true ); - context.data.$container.hide(); - if ( typeof context.config.result.select === 'function' ) { - context.config.result.select.call( $result, context.data.$textbox ); + // do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click) + if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) { + $.suggestions.highlight( context, $result, true ); + context.data.$container.hide(); + if ( typeof context.config.result.select === 'function' ) { + context.config.result.select.call( $result, context.data.$textbox ); + } } + // but still restore focus to the textbox, so that the suggestions will be hidden properly context.data.$textbox.focus(); } ) ) .append( $( '
    ' ).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 + // 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 ) { context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' ); } ) .mouseup( function ( e ) { - var $special = $( e.target ).closest( '.suggestions-special' ); - var $other = context.data.mouseDownOn; + var $special = $( e.target ).closest( '.suggestions-special' ), + $other = context.data.mouseDownOn; + context.data.mouseDownOn = $( [] ); if ( $special.get( 0 ) !== $other.get( 0 ) ) { return; } - context.data.$container.hide(); - if ( typeof context.config.special.select === 'function' ) { - context.config.special.select.call( $special, context.data.$textbox ); + // do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click) + if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) { + context.data.$container.hide(); + if ( typeof context.config.special.select === 'function' ) { + context.config.special.select.call( $special, context.data.$textbox ); + } } + // but still restore focus to the textbox, so that the suggestions will be hidden properly context.data.$textbox.focus(); } ) .mousemove( function ( e ) { diff --git a/resources/jquery/jquery.tablesorter.js b/resources/jquery/jquery.tablesorter.js index 3ef71d57..e08c9aaf 100644 --- a/resources/jquery/jquery.tablesorter.js +++ b/resources/jquery/jquery.tablesorter.js @@ -19,6 +19,9 @@ * @example $( 'table' ).tablesorter(); * @desc Create a simple tablesorter interface. * + * @example $( 'table' ).tablesorter( { sortList: [ { 0: 'desc' }, { 1: 'asc' } ] } ); + * @desc Create a tablesorter interface initially sorting on the first and second column. + * * @option String cssHeader ( optional ) A string of the class name to be appended * to sortable tr elements in the thead of the table. Default value: * "header" @@ -44,9 +47,16 @@ * tablesorter should cancel selection of the table headers text. * Default value: true * + * @option Array sortList ( optional ) An array containing objects specifying sorting. + * By passing more than one object, multi-sorting will be applied. Object structure: + * { : } + * Default value: [] + * * @option Boolean debug ( optional ) Boolean flag indicating if tablesorter * should display debuging information usefull for development. * + * @event sortEnd.tablesorter: Triggered as soon as any sorting has been applied. + * * @type jQuery * * @name tablesorter @@ -57,6 +67,7 @@ */ ( function ( $, mw ) { + /*jshint onevar:false */ /* Local scope */ @@ -75,7 +86,7 @@ return false; } - function getElementText( node ) { + function getElementSortKey( node ) { var $node = $( node ), // Use data-sort-value attribute. // Use data() instead of attr() so that live value changes @@ -87,15 +98,20 @@ // like charAt, toLowerCase and split are expected. return String( data ); } else { - return $node.text(); - } - } - - function getTextFromRowAndCellIndex( rows, rowIndex, cellIndex ) { - if ( rows[rowIndex] && rows[rowIndex].cells[cellIndex] ) { - return $.trim( getElementText( rows[rowIndex].cells[cellIndex] ) ); - } else { - return ''; + if ( !node ) { + return $node.text(); + } else if ( node.tagName.toLowerCase() === 'img' ) { + return $node.attr( 'alt' ) || ''; // handle undefined alt + } else { + return $.map( $.makeArray( node.childNodes ), function( elem ) { + // 1 is for document.ELEMENT_NODE (the constant is undefined on old browsers) + if ( elem.nodeType === 1 ) { + return getElementSortKey( elem ); + } else { + return $.text( elem ); + } + } ).join( '' ); + } } } @@ -108,8 +124,13 @@ concurrent = 0, needed = ( rows.length > 4 ) ? 5 : rows.length; - while( i < l ) { - nodeValue = getTextFromRowAndCellIndex( rows, rowIndex, cellIndex ); + while ( i < l ) { + if ( rows[rowIndex] && rows[rowIndex].cells[cellIndex] ) { + nodeValue = $.trim( getElementSortKey( rows[rowIndex].cells[cellIndex] ) ); + } else { + nodeValue = ''; + } + if ( nodeValue !== '') { if ( parsers[i].is( nodeValue, table ) ) { concurrent++; @@ -151,7 +172,7 @@ for ( i = 0; i < len; i++ ) { parser = false; - sortType = $headers.eq( i ).data( 'sort-type' ); + sortType = $headers.eq( i ).data( 'sortType' ); if ( sortType !== undefined ) { parser = getParserById( sortType ); } @@ -194,7 +215,7 @@ cache.row.push( $row ); for ( var j = 0; j < totalCells; ++j ) { - cols.push( parsers[j].format( getElementText( $row[0].cells[j] ), table, $row[0].cells[j] ) ); + cols.push( parsers[j].format( getElementSortKey( $row[0].cells[j] ), table, $row[0].cells[j] ) ); } cols.push( cache.normalized.length ); // add position for rowCache @@ -223,6 +244,8 @@ } table.tBodies[0].appendChild( fragment ); + + $( table ).trigger( 'sortEnd.tablesorter' ); } /** @@ -291,7 +314,7 @@ } if ( !this.sortDisabled ) { - var $th = $( this ).addClass( table.config.cssHeader ).attr( 'title', msg[1] ); + $( this ).addClass( table.config.cssHeader ).attr( 'title', msg[1] ); } // add cell to headerList @@ -312,20 +335,14 @@ return false; } - function setHeadersCss( table, $headers, list, css, msg ) { - // Remove all header information - $headers.removeClass( css[0] ).removeClass( css[1] ); - - var h = []; - $headers.each( function ( offset ) { - if ( !this.sortDisabled ) { - h[this.column] = $( this ); - } - } ); + function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) { + // Remove all header information and reset titles to default message + $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] ); - var l = list.length; - for ( var i = 0; i < l; i++ ) { - h[ list[i][0] ].addClass( css[ list[i][1] ] ).attr( 'title', msg[ list[i][1] ] ); + for ( var i = 0; i < list.length; i++ ) { + $headers.eq( columnToHeader[ list[i][0] ] ) + .addClass( css[ list[i][1] ] ) + .attr( 'title', msg[ list[i][1] ] ); } } @@ -368,8 +385,8 @@ ts.transformTable = {}; // Unpack the transform table - var ascii = separatorTransformTable[0].split( "\t" ).concat( digitTransformTable[0].split( "\t" ) ); - var localised = separatorTransformTable[1].split( "\t" ).concat( digitTransformTable[1].split( "\t" ) ); + var ascii = separatorTransformTable[0].split( '\t' ).concat( digitTransformTable[0].split( '\t' ) ); + var localised = separatorTransformTable[1].split( '\t' ).concat( digitTransformTable[1].split( '\t' ) ); // Construct regex for number identification for ( var i = 0; i < ascii.length; i++ ) { @@ -381,9 +398,9 @@ // We allow a trailing percent sign, which we just strip. This works fine // if percents and regular numbers aren't being mixed. - ts.numberRegex = new RegExp("^(" + "[-+\u2212]?[0-9][0-9,]*(\\.[0-9,]*)?(E[-+\u2212]?[0-9][0-9,]*)?" + // Fortran-style scientific - "|" + "[-+\u2212]?" + digitClass + "+[\\s\\xa0]*%?" + // Generic localised - ")$", "i"); + ts.numberRegex = new RegExp('^(' + '[-+\u2212]?[0-9][0-9,]*(\\.[0-9,]*)?(E[-+\u2212]?[0-9][0-9,]*)?' + // Fortran-style scientific + '|' + '[-+\u2212]?' + digitClass + '+[\\s\\xa0]*%?' + // Generic localised + ')$', 'i'); } function buildDateTable() { @@ -414,24 +431,86 @@ } + /** + * Replace all rowspanned cells in the body with clones in each row, so sorting + * need not worry about them. + * + * @param $table jQuery object for a + */ function explodeRowspans( $table ) { - // Split multi row cells into multiple cells with the same content - $table.find( '> tbody > tr > [rowspan]' ).each(function () { - var rowSpan = this.rowSpan; - this.rowSpan = 1; - var cell = $( this ); - var next = cell.parent().nextAll(); + var rowspanCells = $table.find( '> tbody > tr > [rowspan]' ).get(); + + // Short circuit + if ( !rowspanCells.length ) { + return; + } + + // First, we need to make a property like cellIndex but taking into + // account colspans. We also cache the rowIndex to avoid having to take + // cell.parentNode.rowIndex in the sorting function below. + $table.find( '> tbody > tr' ).each( function () { + var col = 0; + var l = this.cells.length; + for ( var i = 0; i < l; i++ ) { + this.cells[i].realCellIndex = col; + this.cells[i].realRowIndex = this.rowIndex; + col += this.cells[i].colSpan; + } + } ); + + // Split multi row cells into multiple cells with the same content. + // Sort by column then row index to avoid problems with odd table structures. + // Re-sort whenever a rowspanned cell's realCellIndex is changed, because it + // might change the sort order. + function resortCells() { + rowspanCells = rowspanCells.sort( function ( a, b ) { + var ret = a.realCellIndex - b.realCellIndex; + if ( !ret ) { + ret = a.realRowIndex - b.realRowIndex; + } + return ret; + } ); + $.each( rowspanCells, function () { + this.needResort = false; + } ); + } + resortCells(); + + var spanningRealCellIndex, rowSpan, colSpan; + function filterfunc() { + return this.realCellIndex >= spanningRealCellIndex; + } + + function fixTdCellIndex() { + this.realCellIndex += colSpan; + if ( this.rowSpan > 1 ) { + this.needResort = true; + } + } + + while ( rowspanCells.length ) { + if ( rowspanCells[0].needResort ) { + resortCells(); + } + + var cell = rowspanCells.shift(); + rowSpan = cell.rowSpan; + colSpan = cell.colSpan; + spanningRealCellIndex = cell.realCellIndex; + cell.rowSpan = 1; + var $nextRows = $( cell ).parent().nextAll(); for ( var i = 0; i < rowSpan - 1; i++ ) { - var td = next.eq( i ).children( 'td' ); - if ( !td.length ) { - next.eq( i ).append( cell.clone() ); - } else if ( this.cellIndex === 0 ) { - td.eq( this.cellIndex ).before( cell.clone() ); + var $tds = $( $nextRows[i].cells ).filter( filterfunc ); + var $clone = $( cell ).clone(); + $clone[0].realCellIndex = spanningRealCellIndex; + if ( $tds.length ) { + $tds.each( fixTdCellIndex ); + $tds.first().before( $clone ); } else { - td.eq( this.cellIndex - 1 ).after( cell.clone() ); + $nextRows.eq( i ).append( $clone ); } } - }); + } } function buildCollationTable() { @@ -480,6 +559,25 @@ }; } + /** + * Converts sort objects [ { Integer: String }, ... ] to the internally used nested array + * structure [ [ Integer , Integer ], ... ] + * + * @param sortObjects {Array} List of sort objects. + * @return {Array} List of internal sort definitions. + */ + + function convertSortList( sortObjects ) { + var sortList = []; + $.each( sortObjects, function( i, sortObject ) { + $.each ( sortObject, function( columnIndex, order ) { + var orderIndex = ( order === 'desc' ) ? 1 : 0; + sortList.push( [columnIndex, orderIndex] ); + } ); + } ); + return sortList; + } + /* Public scope */ $.tablesorter = { @@ -512,9 +610,9 @@ construct: function ( $tables, settings ) { return $tables.each( function ( i, table ) { // Declare and cache. - var $document, $headers, cache, config, sortOrder, + var $headers, cache, config, + headerToColumns, columnToHeader, colspanOffset, $table = $( table ), - shiftDown = 0, firstTime = true; // Quit if no tbody @@ -531,8 +629,9 @@ return; } } - $table.addClass( "jquery-tablesorter" ); + $table.addClass( 'jquery-tablesorter' ); + // FIXME config should probably not be stored in the plain table node // New config object. table.config = {}; @@ -540,7 +639,7 @@ config = $.extend( table.config, $.tablesorter.defaultOptions, settings ); // Save the settings where they read - $.data( table, 'tablesorter', config ); + $.data( table, 'tablesorter', { config: config } ); // Get the CSS class names, could be done else where. var sortCSS = [ config.cssDesc, config.cssAsc ]; @@ -558,9 +657,47 @@ // performance improvements in some browsers. cacheRegexs(); + function setupForFirstSort() { + firstTime = false; + + // Legacy fix of .sortbottoms + // Wrap them inside inside a tfoot (because that's what they actually want to be) & + // and put the at the end of the
    + var $sortbottoms = $table.find( '> tbody > tr.sortbottom' ); + if ( $sortbottoms.length ) { + var $tfoot = $table.children( 'tfoot' ); + if ( $tfoot.length ) { + $tfoot.eq(0).prepend( $sortbottoms ); + } else { + $table.append( $( '' ).append( $sortbottoms ) ); + } + } + + explodeRowspans( $table ); + + // try to auto detect column type, and store in tables config + table.config.parsers = buildParserCache( table, $headers ); + } + + // as each header can span over multiple columns (using colspan=N), + // we have to bidirectionally map headers to their columns and columns to their headers + headerToColumns = []; + columnToHeader = []; + colspanOffset = 0; + $headers.each( function ( headerIndex ) { + var columns = []; + for ( var i = 0; i < this.colSpan; i++ ) { + columnToHeader[ colspanOffset + i ] = headerIndex; + columns.push( colspanOffset + i ); + } + + headerToColumns[ headerIndex ] = columns; + colspanOffset += this.colSpan; + } ); + // Apply event handling to headers // this is too big, perhaps break it out? - $headers.click( function ( e ) { + $headers.filter( ':not(.unsortable)' ).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 @@ -568,24 +705,7 @@ } if ( firstTime ) { - firstTime = false; - - // Legacy fix of .sortbottoms - // Wrap them inside inside a tfoot (because that's what they actually want to be) & - // and put the at the end of the
    - var $sortbottoms = $table.find( '> tbody > tr.sortbottom' ); - if ( $sortbottoms.length ) { - var $tfoot = $table.children( 'tfoot' ); - if ( $tfoot.length ) { - $tfoot.eq(0).prepend( $sortbottoms ); - } else { - $table.append( $( '' ).append( $sortbottoms ) ); - } - } - - explodeRowspans( $table ); - // try to auto detect column type, and store in tables config - table.config.parsers = buildParserCache( table, $headers ); + setupForFirstSort(); } // Build the cache for the tbody cells @@ -598,46 +718,48 @@ var totalRows = ( $table[0].tBodies[0] && $table[0].tBodies[0].rows.length ) || 0; if ( !table.sortDisabled && totalRows > 0 ) { - - // Cache jQuery object - var $cell = $( this ); - - // Get current column index - var i = this.column; - // Get current column sort order this.order = this.count % 2; this.count++; - // User only wants to sort on one column - if ( !e[config.sortMultiSortKey] ) { - // Flush the sort list - config.sortList = []; - // Add column to sort list - config.sortList.push( [i, this.order] ); + var cell = this; + // Get current column index + var columns = headerToColumns[this.column]; + var newSortList = $.map( columns, function (c) { + // jQuery "helpfully" flattens the arrays... + return [[c, cell.order]]; + }); + // Index of first column belonging to this header + var i = columns[0]; - // Multi column sorting + if ( !e[config.sortMultiSortKey] ) { + // User only wants to sort on one column set + // Flush the sort list and add new columns + config.sortList = newSortList; } else { - // The user has clicked on an already sorted column. + // Multi column sorting + // It is not possible for one column to belong to multiple headers, + // so this is okay - we don't need to check for every value in the columns array if ( isValueInArray( i, config.sortList ) ) { + // The user has clicked on an already sorted column. // Reverse the sorting direction for all tables. for ( var j = 0; j < config.sortList.length; j++ ) { var s = config.sortList[j], o = config.headerList[s[0]]; - if ( s[0] === i ) { + if ( isValueInArray( s[0], newSortList ) ) { o.count = s[1]; o.count++; s[1] = o.count % 2; } } } else { - // Add column to sort list array - config.sortList.push( [i, this.order] ); + // Add columns to sort list array + config.sortList = config.sortList.concat( newSortList ); } } // Set CSS for headers - setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg ); + setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg, columnToHeader ); appendToTable( $table[0], multisort( $table[0], config.sortList, cache ) ); @@ -655,6 +777,44 @@ return false; } } ); + + /** + * Sorts the table. If no sorting is specified by passing a list of sort + * objects, the table is sorted according to the initial sorting order. + * Passing an empty array will reset sorting (basically just reset the headers + * making the table appear unsorted). + * + * @param sortList {Array} (optional) List of sort objects. + */ + $table.data( 'tablesorter' ).sort = function( sortList ) { + + if ( firstTime ) { + setupForFirstSort(); + } + + if ( sortList === undefined ) { + sortList = config.sortList; + } else if ( sortList.length > 0 ) { + sortList = convertSortList( sortList ); + } + + // re-build the cache for the tbody cells + cache = buildCache( table ); + + // set css for headers + setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, columnToHeader ); + + // sort the table and append it to the dom + appendToTable( table, multisort( table, sortList, cache ) ); + }; + + // sort initially + if ( config.sortList.length > 0 ) { + setupForFirstSort(); + config.sortList = convertSortList( config.sortList ); + $table.data( 'tablesorter' ).sort(); + } + } ); }, @@ -672,10 +832,10 @@ }, formatDigit: function ( s ) { + var out, c, p, i; if ( ts.transformTable !== false ) { - var out = '', - c; - for ( var p = 0; p < s.length; p++ ) { + out = ''; + for ( p = 0; p < s.length; p++ ) { c = s.charAt(p); if ( c in ts.transformTable ) { out += ts.transformTable[c]; @@ -685,31 +845,22 @@ } s = out; } - var i = parseFloat( s.replace( /[, ]/g, '' ).replace( "\u2212", '-' ) ); - return ( isNaN(i)) ? 0 : i; + i = parseFloat( s.replace( /[, ]/g, '' ).replace( '\u2212', '-' ) ); + return isNaN( i ) ? 0 : i; }, formatFloat: function ( s ) { var i = parseFloat(s); - return ( isNaN(i)) ? 0 : i; + return isNaN( i ) ? 0 : i; }, formatInt: function ( s ) { var i = parseInt( s, 10 ); - return ( isNaN(i)) ? 0 : i; + return isNaN( i ) ? 0 : i; }, clearTableBody: function ( table ) { - if ( $.browser.msie ) { - var empty = function ( el ) { - while ( el.firstChild ) { - el.removeChild( el.firstChild ); - } - }; - empty( table.tBodies[0] ); - } else { - table.tBodies[0].innerHTML = ''; - } + $( table.tBodies[0] ).empty(); } }; @@ -724,7 +875,7 @@ // Add default parsers ts.addParser( { id: 'text', - is: function ( s ) { + is: function () { return true; }, format: function ( s ) { @@ -815,7 +966,7 @@ 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 ) { var match; s = $.trim( s.toLowerCase() ); @@ -824,6 +975,10 @@ s = [ match[3], match[1], match[2] ]; } else if ( mw.config.get( 'wgDefaultDateFormat' ) === 'dmy' ) { s = [ match[3], match[2], match[1] ]; + } else { + // If we get here, we don't know which order the dd-dd-dddd + // date is in. So return something not entirely invalid. + return '99999999'; } } else if ( ( match = s.match( ts.dateRegex[1] ) ) !== null ) { s = [ match[3], '' + ts.monthNames[match[2]], match[1] ]; @@ -872,7 +1027,7 @@ ts.addParser( { id: 'number', - is: function ( s, table ) { + is: function ( s ) { return $.tablesorter.numberRegex.test( $.trim( s )); }, format: function ( s ) { diff --git a/resources/jquery/jquery.textSelection.js b/resources/jquery/jquery.textSelection.js index abb0fa3f..17fd0cd3 100644 --- a/resources/jquery/jquery.textSelection.js +++ b/resources/jquery/jquery.textSelection.js @@ -25,6 +25,11 @@ } $.fn.textSelection = function ( command, options ) { + var fn, + context, + hasIframe, + needSave, + retval; /** * Helper function to get an IE TextRange object for an element @@ -52,7 +57,7 @@ } } - var fn = { + fn = { /** * Get the contents of the textarea */ @@ -168,16 +173,16 @@ 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"; + 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"; + if ( range3.text !== '\r' && range3.text !== '\n' && range3.text !== '' ) { + insertText += '\n'; + post += '\n'; } } @@ -216,13 +221,13 @@ 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 ( 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"; + if ( this.value.charAt( endPos ) !== '\n' && this.value.charAt( endPos ) !== '\r' ) { + insertText += '\n'; + post += '\n'; } } this.value = this.value.substring( 0, startPos ) + insertText + @@ -230,9 +235,9 @@ // 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" ); + 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; @@ -261,7 +266,21 @@ */ getCaretPosition: function ( options ) { function getCaret( e ) { - var caretPos = 0, endPos = 0; + var caretPos = 0, + endPos = 0, + preText, rawPreText, periText, + rawPeriText, postText, rawPostText, + // IE Support + preFinished, + periFinished, + postFinished, + // Range containing text in the selection + periRange, + // Range containing text before the selection + preRange, + // Range containing text after the selection + postRange; + 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 @@ -269,20 +288,10 @@ // 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; + preFinished = false; + periFinished = false; + postFinished = false; + periRange = document.selection.createRange().duplicate(); preRange = rangeForElementIE( e ), // Move the end where we need it @@ -309,7 +318,7 @@ } else { preRange.moveEnd( 'character', -1 ); if ( preRange.text === preText ) { - rawPreText += "\r\n"; + rawPreText += '\r\n'; } else { preFinished = true; } @@ -321,7 +330,7 @@ } else { periRange.moveEnd( 'character', -1 ); if ( periRange.text === periText ) { - rawPeriText += "\r\n"; + rawPeriText += '\r\n'; } else { periFinished = true; } @@ -333,15 +342,15 @@ } else { postRange.moveEnd( 'character', -1 ); if ( postRange.text === postText ) { - rawPostText += "\r\n"; + 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; + 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; @@ -405,20 +414,22 @@ return Math.floor( e.scrollWidth / ( $.client.profile().platform === 'linux' ? 7 : 8 ) ); } 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; + var i, j, + nextSpace, + text = e.value.replace( /\r/g, '' ), + caret = $( e ).textSelection( 'getCaretPosition' ), + lineLength = getLineLength( e ), + row = 0, + charInLine = 0, + lastSpaceInLine = 0; + for ( i = 0; i < caret; i++ ) { charInLine++; if ( text.charAt( i ) === ' ' ) { lastSpaceInLine = charInLine; - } else if ( text.charAt( i ) === "\n" ) { + } else if ( text.charAt( i ) === '\n' ) { lastSpaceInLine = 0; charInLine = 0; row++; @@ -431,11 +442,11 @@ } } } - var nextSpace = 0; + nextSpace = 0; for ( j = caret; j < caret + lineLength; j++ ) { if ( text.charAt( j ) === ' ' || - text.charAt( j ) === "\n" || + text.charAt( j ) === '\n' || caret === text.length ) { nextSpace = j; @@ -542,16 +553,16 @@ break; } - var context = $(this).data( 'wikiEditor-context' ); - var hasIframe = typeof context !== 'undefined' && context && typeof context.$iframe !== 'undefined'; + context = $(this).data( 'wikiEditor-context' ); + hasIframe = context !== undefined && context && context.$iframe !== undefined; // IE selection restore voodoo - var needSave = false; + needSave = false; if ( hasIframe && context.savedSelection !== null ) { context.fn.restoreSelection(); needSave = true; } - var retval = ( hasIframe ? context.fn : fn )[command].call( this, options ); + retval = ( hasIframe ? context.fn : fn )[command].call( this, options ); if ( hasIframe && needSave ) { context.fn.saveSelection(); } diff --git a/resources/mediawiki.action/mediawiki.action.edit.js b/resources/mediawiki.action/mediawiki.action.edit.js index 1c51c974..2835c9cc 100644 --- a/resources/mediawiki.action/mediawiki.action.edit.js +++ b/resources/mediawiki.action/mediawiki.action.edit.js @@ -71,7 +71,7 @@ * Apply tagOpen/tagClose to selection in textarea, * use sampleText instead of selection if there is none. */ - insertTags: function ( tagOpen, tagClose, sampleText, selectText ) { + insertTags: function ( tagOpen, tagClose, sampleText ) { if ( currentFocused && currentFocused.length ) { currentFocused.textSelection( 'encapsulateSelection', { diff --git a/resources/mediawiki.action/mediawiki.action.edit.preview.js b/resources/mediawiki.action/mediawiki.action.edit.preview.js index cddf6ccf..602aadb0 100644 --- a/resources/mediawiki.action/mediawiki.action.edit.preview.js +++ b/resources/mediawiki.action/mediawiki.action.edit.preview.js @@ -102,6 +102,12 @@ } $( document ).ready( function () { + // Do not enable on user .js/.css pages, as there's no sane way of "previewing" + // the scripts or styles without reloading the page. + if ( $( '#mw-userjsyoucanpreview' ).length || $( '#mw-usercssyoucanpreview' ).length ) { + return; + } + // 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 diff --git a/resources/mediawiki.action/mediawiki.action.history.diff.css b/resources/mediawiki.action/mediawiki.action.history.diff.css index 10473be7..31ca1078 100644 --- a/resources/mediawiki.action/mediawiki.action.history.diff.css +++ b/resources/mediawiki.action/mediawiki.action.history.diff.css @@ -1,8 +1,36 @@ /* ** Diff rendering */ -table.diff, td.diff-otitle, td.diff-ntitle { +table.diff { background-color: white; + border: none; + border-spacing: 4px; + margin: 0; + width: 100%; + /* Ensure that colums are of equal width */ + table-layout: fixed; +} + +table.diff td { + padding: 0.33em 0.5em; +} + +table.diff td.diff-marker { + /* Compensate padding for increased font-size */ + padding: 0.25em; +} + +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; } td.diff-otitle, @@ -10,14 +38,14 @@ td.diff-ntitle { text-align: center; } -td.diff-marker { - text-align: right; +td.diff-lineno { font-weight: bold; - font-size: 1.25em; } -td.diff-lineno { +td.diff-marker { + text-align: right; font-weight: bold; + font-size: 1.25em; } td.diff-addedline, @@ -27,10 +55,6 @@ td.diff-context { 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; @@ -45,12 +69,9 @@ td.diff-deletedline { } td.diff-context { - background: #f3f3f3; - color: #333333; - border-style: solid; - border-width: 1px 1px 1px 4px; + background: #f9f9f9; border-color: #e6e6e6; - border-radius: 0.33em; + color: #333333; } .diffchange { @@ -58,15 +79,6 @@ td.diff-context { text-decoration: none; } -table.diff { - border: none; - width: 98%; - border-spacing: 4px; - - /* Ensure that colums are of equal width */ - table-layout: fixed; -} - td.diff-addedline .diffchange, td.diff-deletedline .diffchange { border-radius: 0.33em; @@ -80,25 +92,3 @@ td.diff-addedline .diffchange { td.diff-deletedline .diffchange { background: #feeec8; } - -table.diff td { - 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 - */ - overflow: auto; -} diff --git a/resources/mediawiki.action/mediawiki.action.history.js b/resources/mediawiki.action/mediawiki.action.history.js index 55f799e5..e9d320c1 100644 --- a/resources/mediawiki.action/mediawiki.action.history.js +++ b/resources/mediawiki.action/mediawiki.action.history.js @@ -119,7 +119,7 @@ jQuery( document ).ready( function ( $ ) { e.preventDefault(); return false; // Because the submit is special, return false as well. } - + // Continue natural browser handling other wise return true; } ); diff --git a/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js b/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js index 7a9ceee5..727a5251 100644 --- a/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js @@ -1,14 +1,12 @@ /** - * This module enables double-click-to-edit functionality + * This module enables double-click-to-edit functionality. */ ( function ( mw, $ ) { $( function () { - var url = $( '#ca-edit a' ).attr( 'href' ); - if ( url ) { - mw.util.$content.dblclick( function ( e ) { - e.preventDefault(); - window.location = url; - } ); - } + mw.util.$content.dblclick( function ( e ) { + e.preventDefault(); + // Trigger native HTMLElement click instead of opening URL (bug 43052) + $( '#ca-edit a' ).get( 0 ).click(); + } ); } ); }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.view.postEdit.js b/resources/mediawiki.action/mediawiki.action.view.postEdit.js new file mode 100644 index 00000000..a11233fa --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.view.postEdit.js @@ -0,0 +1,15 @@ +( function ( mw, $ ) { + // Only a view can be a post-edit. + if ( mw.config.get( 'wgAction' ) !== 'view' ) { + return; + } + + // Matches EditPage::POST_EDIT_COOKIE_KEY_PREFIX + var cookieKey = mw.config.get( 'wgCookiePrefix' ) + 'PostEditRevision' + mw.config.get( 'wgCurRevisionId' ); + + if ( $.cookie( cookieKey ) === '1' ) { + // We just saved this page + $.cookie( cookieKey, null, { path: '/' } ); + mw.config.set( 'wgPostEdit', true ); + } +} ( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js index d02d4327..61d9d150 100644 --- a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js @@ -1,4 +1,4 @@ -/* +/** * JavaScript to enable right click edit functionality. * When the user right-clicks in a heading, it will open the * edit screen. @@ -8,23 +8,19 @@ jQuery( function ( $ ) { // 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' ); + var $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; + if ( e.target.nodeName.toLowerCase() !== 'a' ) { + // Trigger native HTMLElement click instead of opening URL (bug 43052) e.preventDefault(); + $edit.get( 0 ).click(); } } ); } ); diff --git a/resources/mediawiki.api/mediawiki.api.category.js b/resources/mediawiki.api/mediawiki.api.category.js index cc6f704f..4de52911 100644 --- a/resources/mediawiki.api/mediawiki.api.category.js +++ b/resources/mediawiki.api/mediawiki.api.category.js @@ -1,104 +1,130 @@ /** - * Additional mw.Api methods to assist with API calls related to categories. + * @class mw.Api.plugin.category */ ( function ( mw, $ ) { $.extend( mw.Api.prototype, { /** * Determine if a category exists. - * @param title {mw.Title} - * @param success {Function} callback to pass boolean of category's existence - * @param err {Function} optional callback to run if api error - * @return ajax call object + * @param {mw.Title} title + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {boolean} return.done.isCategory Whether the category 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 ); - }; + isCategory: function ( title, ok, err ) { + var d = $.Deferred(); + // Backwards compatibility (< MW 1.20) + d.done( ok ); + d.fail( err ); - return this.get( params, { ok: ok, err: err } ); + this.get( { + prop: 'categoryinfo', + titles: title.toString() + } ) + .done( function ( data ) { + var exists = false; + if ( data.query && data.query.pages ) { + $.each( data.query.pages, function ( id, page ) { + if ( page.categoryinfo ) { + exists = true; + } + } ); + } + d.resolve( exists ); + }) + .fail( d.reject ); + + return d.promise(); }, /** * Get a list of categories that match a certain prefix. * e.g. given "Foo", return "Food", "Foolish people", "Foosball tables" ... - * @param prefix {String} prefix to match - * @param success {Function} callback to pass matched categories to - * @param err {Function} optional callback to run if api error - * @return {jqXHR} + * @param {string} prefix Prefix to match. + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {String[]} return.done.categories Matched categories */ - getCategoriesByPrefix: function ( prefix, success, err ) { + getCategoriesByPrefix: function ( prefix, ok, err ) { + var d = $.Deferred(); + // Backwards compatibility (< MW 1.20) + d.done( ok ); + d.fail( 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 - }; - ok = function ( data ) { - var texts = []; - if ( data.query && data.query.allpages ) { - $.each( data.query.allpages, function ( i, category ) { - texts.push( new mw.Title( category.title ).getNameText() ); - } ); - } - success( texts ); - }; + this.get( { + list: 'allpages', + apprefix: prefix, + apnamespace: mw.config.get('wgNamespaceIds').category + } ) + .done( function ( data ) { + var texts = []; + if ( data.query && data.query.allpages ) { + $.each( data.query.allpages, function ( i, category ) { + texts.push( new mw.Title( category.title ).getNameText() ); + } ); + } + d.resolve( texts ); + }) + .fail( d.reject ); - return this.get( params, { ok: ok, err: err } ); + return d.promise(); }, /** * Get the categories that a particular page on the wiki belongs to - * @param title {mw.Title} - * @param success {Function} callback to pass categories to (or false, if title not found) - * @param err {Function} optional callback to run if api error - * @param async {Boolean} optional asynchronousness (default = true = async) - * @return {jqXHR} + * @param {mw.Title} title + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) + * @param {boolean} [async=true] Asynchronousness + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {boolean|mw.Title[]} return.done.categories List of category titles or false + * if title was not found. */ - getCategories: function ( title, success, err, async ) { - var params, ok; - params = { - prop: 'categories', - titles: title.toString() - }; - if ( async === undefined ) { - async = true; - } - ok = function ( data ) { - var ret = false; - if ( data.query && data.query.pages ) { - $.each( data.query.pages, function ( id, page ) { - if ( page.categories ) { - if ( typeof ret !== 'object' ) { - ret = []; + getCategories: function ( title, ok, err, async ) { + var d = $.Deferred(); + // Backwards compatibility (< MW 1.20) + d.done( ok ); + d.fail( err ); + + this.get( { + prop: 'categories', + titles: title.toString() + }, { + async: async === undefined ? true : async + } ) + .done( function ( data ) { + var ret = false; + if ( data.query && data.query.pages ) { + $.each( data.query.pages, function ( id, page ) { + if ( page.categories ) { + if ( typeof ret !== 'object' ) { + ret = []; + } + $.each( page.categories, function ( i, cat ) { + ret.push( new mw.Title( cat.title ) ); + } ); } - $.each( page.categories, function ( i, cat ) { - ret.push( new mw.Title( cat.title ) ); - } ); - } - } ); - } - success( ret ); - }; + } ); + } + d.resolve( ret ); + }) + .fail( d.reject ); - return this.get( params, { ok: ok, err: err, async: async } ); + return d.promise(); } } ); + /** + * @class mw.Api + * @mixins mw.Api.plugin.category + */ + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.edit.js b/resources/mediawiki.api/mediawiki.api.edit.js index 49af9375..3c775ad0 100644 --- a/resources/mediawiki.api/mediawiki.api.edit.js +++ b/resources/mediawiki.api/mediawiki.api.edit.js @@ -1,5 +1,5 @@ /** - * Additional mw.Api methods to assist with API calls related to editing wiki pages. + * @class mw.Api.plugin.edit */ ( function ( mw, $ ) { @@ -13,10 +13,10 @@ * If we have a cached token try using that, and if it fails, blank out the * cached token and start over. * - * @param params {Object} API parameters - * @param ok {Function} callback for success - * @param err {Function} [optional] error callback - * @return {jqXHR} + * @param {Object} params API parameters + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) + * @return {jQuery.Promise} See #post */ postWithEditToken: function ( params, ok, err ) { var useTokenToPost, getTokenIfBad, @@ -48,67 +48,74 @@ }, /** - * Api helper to grab an edit token + * Api helper to grab an edit token. * - * token callback has signature ( String token ) - * error callback has signature ( String code, Object results, XmlHttpRequest xhr, Exception exception ) - * Note that xhr and exception are only available for 'http_*' errors - * code may be any http_* error code (see mw.Api), or 'token_missing' - * - * @param tokenCallback {Function} received token callback - * @param err {Function} error callback - * @return {jqXHR} + * @param {Function} [ok] Success callback + * @param {Function} [err] Error callback + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {string} return.done.token Received token. */ - getEditToken: function ( tokenCallback, err ) { - var parameters = { + getEditToken: function ( ok, err ) { + var d = $.Deferred(); + // Backwards compatibility (< MW 1.20) + d.done( ok ); + d.fail( err ); + + this.get( { action: 'tokens', type: 'edit' - }, - ok = function ( data ) { + }, { + // Due to the API assuming we're logged out if we pass the callback-parameter, + // we have to disable jQuery's callback system, and instead parse JSON string, + // by setting 'jsonp' to false. + // TODO: This concern seems genuine but no other module has it. Is it still + // needed and/or should we pass this by default? + jsonp: false + } ) + .done( function ( data ) { var token; // 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 ); + d.resolve( token ); } else { - err( 'token-missing', data ); + d.reject( 'token-missing', data ); } - }, - ajaxOptions = { - ok: ok, - err: err, - // Due to the API assuming we're logged out if we pass the callback-parameter, - // we have to disable jQuery's callback system, and instead parse JSON string, - // by setting 'jsonp' to false. - jsonp: false - }; + }) + .fail( d.reject ); - return this.get( parameters, ajaxOptions ); + return d.promise(); }, /** * Create a new section of the page. - * @param title {mw.Title|String} target page - * @param header {String} - * @param message {String} wikitext message - * @param ok {Function} success handler - * @param err {Function} error handler - * @return {jqXHR} + * @see #postWithEditToken + * @param {mw.Title|String} title Target page + * @param {string} header + * @param {string} message wikitext message + * @param {Function} [ok] Success handler + * @param {Function} [err] Error handler + * @return {jQuery.Promise} */ newSection: function ( title, header, message, ok, err ) { - var params = { + return this.postWithEditToken( { action: 'edit', section: 'new', format: 'json', title: title.toString(), summary: header, text: message - }; - return this.postWithEditToken( params, ok, err ); + }, ok, err ); } } ); + /** + * @class mw.Api + * @mixins mw.Api.plugin.edit + */ + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.js b/resources/mediawiki.api/mediawiki.api.js index a184e3ca..cf7443f3 100644 --- a/resources/mediawiki.api/mediawiki.api.js +++ b/resources/mediawiki.api/mediawiki.api.js @@ -1,15 +1,9 @@ -/** - * mw.Api objects represent the API of a particular MediaWiki server. - */ ( function ( mw, $ ) { - /** - * @var defaultOptions {Object} - * We allow people to omit these default parameters from API requests - * there is very customizable error handling here, on a per-call basis - * wondering, would it be simpler to make it easy to clone the api object, - * change error handling, and use that instead? - */ + // We allow people to omit these default parameters from API requests + // there is very customizable error handling here, on a per-call basis + // wondering, would it be simpler to make it easy to clone the api object, + // change error handling, and use that instead? var defaultOptions = { // Query parameters for API requests @@ -30,22 +24,23 @@ /** * Constructor to create an object to interact with the API of a particular MediaWiki server. + * mw.Api objects represent the API of a particular MediaWiki server. + * + * TODO: Share API objects with exact same config. * - * @todo Share API objects with exact same config. - * @example - * - * var api = new mw.Api(); - * api.get( { - * action: 'query', - * meta: 'userinfo' - * }, { - * ok: function () { console.log( arguments ); } - * } ); - * + * var api = new mw.Api(); + * api.get( { + * action: 'query', + * meta: 'userinfo' + * } ).done ( function ( data ) { + * console.log( data ); + * } ); + * + * @class * * @constructor - * @param options {Object} See defaultOptions documentation above. Ajax options can also be - * overridden for each individual request to jQuery.ajax() later on. + * @param {Object} options See defaultOptions documentation above. Ajax options can also be + * overridden for each individual request to {@link jQuery#ajax} later on. */ mw.Api = function ( options ) { @@ -69,13 +64,12 @@ /** * Normalize the ajax options for compatibility and/or convenience methods. * - * @param {undefined|Object|Function} An object contaning one or more of options.ajax, - * or just a success function (options.ajax.ok). + * @param {Object} [arg] An object contaning one or more of options.ajax. * @return {Object} Normalized ajax options. */ normalizeAjaxOptions: function ( arg ) { // Arg argument is usually empty - // (before MW 1.20 it was often used to pass ok/err callbacks) + // (before MW 1.20 it was used to pass ok callbacks) var opts = arg || {}; // Options can also be a success callback handler if ( typeof arg === 'function' ) { @@ -87,8 +81,8 @@ /** * Perform API get request * - * @param {Object} request parameters - * @param {Object|Function} [optional] ajax options + * @param {Object} parameters + * @param {Object|Function} [ajaxOptions] * @return {jQuery.Promise} */ get: function ( parameters, ajaxOptions ) { @@ -99,10 +93,11 @@ /** * Perform API post request - * @todo Post actions for nonlocal will need proxy * - * @param {Object} request parameters - * @param {Object|Function} [optional] ajax options + * TODO: Post actions for non-local hostnames will need proxy. + * + * @param {Object} parameters + * @param {Object|Function} [ajaxOptions] * @return {jQuery.Promise} */ post: function ( parameters, ajaxOptions ) { @@ -114,11 +109,9 @@ /** * Perform the API call. * - * @param {Object} request parameters - * @param {Object} ajax options - * @return {jQuery.Promise} - * - done: API response data as first argument - * - fail: errorcode as first arg, details (string or object) as second arg. + * @param {Object} parameters + * @param {Object} [ajaxOptions] + * @return {jQuery.Promise} Done: API response data. Fail: Error code */ ajax: function ( parameters, ajaxOptions ) { var token, @@ -187,7 +180,9 @@ }; /** - * @var {Array} List of errors we might receive from the API. + * @static + * @property {Array} + * List of errors we might receive from the API. * For now, this just documents our expectation that there should be similar messages * available. */ @@ -237,7 +232,9 @@ ]; /** - * @var {Array} List of warnings we might receive from the API. + * @static + * @property {Array} + * List of warnings we might receive from the API. * For now, this just documents our expectation that there should be similar messages * available. */ diff --git a/resources/mediawiki.api/mediawiki.api.parse.js b/resources/mediawiki.api/mediawiki.api.parse.js index e8d1b3e6..ea0388c1 100644 --- a/resources/mediawiki.api/mediawiki.api.parse.js +++ b/resources/mediawiki.api/mediawiki.api.parse.js @@ -1,42 +1,43 @@ /** - * mw.Api methods for parsing wikitext. + * @class mw.Api.plugin.parse */ ( function ( mw, $ ) { $.extend( mw.Api.prototype, { /** - * Convinience method for 'action=parse'. Parses wikitext into HTML. + * Convinience method for 'action=parse'. * - * @param wikiText {String} - * @param ok {Function} [optional] deprecated (success callback) - * @param err {Function} [optional] deprecated (error callback) + * @param {string} wikitext + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) * @return {jQuery.Promise} + * @return {Function} return.done + * @return {string} return.done.data Parsed HTML of `wikitext`. */ - parse: function ( wikiText, ok, err ) { - var apiDeferred = $.Deferred(); - + parse: function ( wikitext, ok, err ) { + var d = $.Deferred(); // Backwards compatibility (< MW 1.20) - if ( ok ) { - apiDeferred.done( ok ); - } - if ( err ) { - apiDeferred.fail( err ); - } + d.done( ok ); + d.fail( err ); this.get( { action: 'parse', - text: wikiText + text: wikitext } ) .done( function ( data ) { if ( data.parse && data.parse.text && data.parse.text['*'] ) { - apiDeferred.resolve( data.parse.text['*'] ); + d.resolve( data.parse.text['*'] ); } } ) - .fail( apiDeferred.reject ); + .fail( d.reject ); - // Return the promise - return apiDeferred.promise(); + return d.promise(); } } ); + /** + * @class mw.Api + * @mixins mw.Api.plugin.parse + */ + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.titleblacklist.js b/resources/mediawiki.api/mediawiki.api.titleblacklist.js deleted file mode 100644 index 1f7e275a..00000000 --- a/resources/mediawiki.api/mediawiki.api.titleblacklist.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Additional mw.Api methods to assist with API calls to the API module of the TitleBlacklist extension. - */ - -( function ( mw, $ ) { - - $.extend( mw.Api.prototype, { - /** - * Convinience method for 'action=titleblacklist'. - * Note: This action is not provided by MediaWiki core, but as part of the TitleBlacklist extension. - * - * @param title {mw.Title} - * @param success {Function} Called on successfull request. First argument is false if title wasn't blacklisted, - * object with 'reason', 'line' and 'message' properties if title was blacklisted. - * @param err {Function} optional callback to run if api error - * @return {jqXHR} - */ - isBlacklisted: function ( title, success, err ) { - var params = { - action: 'titleblacklist', - tbaction: 'create', - tbtitle: title.toString() - }, - ok = function ( data ) { - var result; - - // this fails open (if nothing valid is returned by the api, allows the title) - // also fails open when the API is not present, which will be most of the time - // as this API module is part of the TitleBlacklist extension. - if ( data.titleblacklist && data.titleblacklist.result && data.titleblacklist.result === 'blacklisted') { - if ( data.titleblacklist.reason ) { - result = { - reason: data.titleblacklist.reason, - line: data.titleblacklist.line, - message: data.titleblacklist.message - }; - } else { - mw.log('mw.Api.titleblacklist::isBlacklisted> no reason data for blacklisted title', 'debug'); - result = { reason: 'Blacklisted, but no reason supplied', line: 'Unknown', message: null }; - } - success( result ); - } else { - success ( false ); - } - }; - - return this.get( params, { ok: ok, err: err } ); - } - - } ); - -}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.watch.js b/resources/mediawiki.api/mediawiki.api.watch.js index d3234421..c86a90a7 100644 --- a/resources/mediawiki.api/mediawiki.api.watch.js +++ b/resources/mediawiki.api/mediawiki.api.watch.js @@ -1,56 +1,72 @@ /** - * Additional mw.Api methods to assist with (un)watching wiki pages. + * @class mw.Api.plugin.watch * @since 1.19 */ ( function ( mw, $ ) { /** - * @context {mw.Api} + * @private + * @context mw.Api + * + * @param {String|mw.Title} page Full page name or instance of mw.Title + * @param {Function} [ok] Success callback (deprecated) + * @param {Function} [err] Error callback (deprecated) + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {Object} return.done.watch + * @return {string} return.done.watch.title Full pagename + * @return {boolean} return.done.watch.watched + * @return {string} return.done.watch.message Parsed HTML of the confirmational interface message */ - function doWatchInternal( page, success, err, addParams ) { - var params = { + function doWatchInternal( page, ok, err, addParams ) { + var params, d = $.Deferred(); + // Backwards compatibility (< MW 1.20) + d.done( ok ); + d.fail( err ); + + 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 } ); + + this.post( params ) + .done( function ( data ) { + d.resolve( data.watch ); + } ) + .fail( d.reject ); + + return d.promise(); } $.extend( mw.Api.prototype, { /** - * Convinience method for 'action=watch'. + * Convenience 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 properties 'title' (full pagename), 'watched' (boolean) and - * 'message' (parsed HTML of the 'addedwatchtext' message). - * @param err {Function} Error callback (optional) - * @return {jqXHR} + * @inheritdoc #doWatchInternal */ - watch: function ( page, success, err ) { - return doWatchInternal.call( this, page, success, err ); + watch: function ( page, ok, err ) { + return doWatchInternal.call( this, page, ok, err ); }, /** - * Convinience method for 'action=watch&unwatch=1'. + * Convenience 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 properties 'title' (full pagename), 'watched' (boolean) and - * 'message' (parsed HTML of the 'removedwatchtext' message). - * @param err {Function} Error callback (optional) - * @return {jqXHR} + * @inheritdoc #doWatchInternal */ - unwatch: function ( page, success, err ) { - return doWatchInternal.call( this, page, success, err, { unwatch: 1 } ); + unwatch: function ( page, ok, err ) { + return doWatchInternal.call( this, page, ok, err, { unwatch: 1 } ); } } ); + /** + * @class mw.Api + * @mixins mw.Api.plugin.watch + */ + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.language/languages/bs.js b/resources/mediawiki.language/languages/bs.js index c0c77aaf..65eb5a6d 100644 --- a/resources/mediawiki.language/languages/bs.js +++ b/resources/mediawiki.language/languages/bs.js @@ -2,10 +2,10 @@ * Bosnian (bosanski) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'bs', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'bs', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'instrumental': // instrumental diff --git a/resources/mediawiki.language/languages/dsb.js b/resources/mediawiki.language/languages/dsb.js index a42a8f7f..b2c9c081 100644 --- a/resources/mediawiki.language/languages/dsb.js +++ b/resources/mediawiki.language/languages/dsb.js @@ -2,10 +2,10 @@ * Lower Sorbian (Dolnoserbski) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'dsb', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'dsb', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'instrumental': // instrumental diff --git a/resources/mediawiki.language/languages/fi.js b/resources/mediawiki.language/languages/fi.js index 374698dc..61c6c104 100644 --- a/resources/mediawiki.language/languages/fi.js +++ b/resources/mediawiki.language/languages/fi.js @@ -3,21 +3,24 @@ * @author Santhosh Thottingal */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'fi', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms, aou, origWord; + + grammarForms = mediaWiki.language.getData( 'fi', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } // vowel harmony flag - var aou = word.match( /[aou][^äöy]*$/i ); - var origWord = word; + aou = word.match( /[aou][^äöy]*$/i ); + origWord = word; if ( word.match( /wiki$/i ) ) { aou = false; } //append i after final consonant - if ( word.match( /[bcdfghjklmnpqrstvwxz]$/i ) ) + if ( word.match( /[bcdfghjklmnpqrstvwxz]$/i ) ) { word += 'i'; + } switch ( form ) { case 'genitive': diff --git a/resources/mediawiki.language/languages/ga.js b/resources/mediawiki.language/languages/ga.js index a27b489d..c13e8321 100644 --- a/resources/mediawiki.language/languages/ga.js +++ b/resources/mediawiki.language/languages/ga.js @@ -2,10 +2,11 @@ * Irish (Gaeilge) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'ga', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + /*jshint onecase:true */ + var grammarForms = mediaWiki.language.getData( 'ga', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'ainmlae': diff --git a/resources/mediawiki.language/languages/he.js b/resources/mediawiki.language/languages/he.js index d35f77ed..13d457b2 100644 --- a/resources/mediawiki.language/languages/he.js +++ b/resources/mediawiki.language/languages/he.js @@ -3,26 +3,26 @@ */ mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'he', 'grammarForms' ); + var grammarForms = mediaWiki.language.getData( 'he', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } 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; + if ( word.substr( 0, 1 ) === 'ו' && word.substr( 0, 2 ) !== 'וו' ) { + word = 'ו' + word; } // Remove the "He" if prefixed - if ( word.substr( 0, 1 ) === "ה" ) { + 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; + if ( word.substr( 0, 1 ) < 'א' || word.substr( 0, 1 ) > 'ת' ) { + word = '־' + word; } } return word; diff --git a/resources/mediawiki.language/languages/hsb.js b/resources/mediawiki.language/languages/hsb.js index 211d67b5..77dca75e 100644 --- a/resources/mediawiki.language/languages/hsb.js +++ b/resources/mediawiki.language/languages/hsb.js @@ -2,10 +2,10 @@ * Upper Sorbian (Hornjoserbsce) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms =mw.language.getData( 'hsb', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'hsb', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'instrumental': // instrumental diff --git a/resources/mediawiki.language/languages/hu.js b/resources/mediawiki.language/languages/hu.js index eb3f1f3a..23b0c125 100644 --- a/resources/mediawiki.language/languages/hu.js +++ b/resources/mediawiki.language/languages/hu.js @@ -3,10 +3,10 @@ * @author Santhosh Thottingal */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'hu', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'hu', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'rol': diff --git a/resources/mediawiki.language/languages/hy.js b/resources/mediawiki.language/languages/hy.js index 215e7504..65081bdd 100644 --- a/resources/mediawiki.language/languages/hy.js +++ b/resources/mediawiki.language/languages/hy.js @@ -2,10 +2,11 @@ * Armenian (Հայերեն) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'hy', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + /*jshint onecase:true */ + var grammarForms = mediaWiki.language.getData( 'hy', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } // These rules are not perfect, but they are currently only used for site names so it doesn't @@ -13,14 +14,15 @@ mediaWiki.language.convertGrammar = function( word, form ) { switch ( form ) { case 'genitive': // սեռական հոլով - if ( word.substr( -1 ) === 'ա' ) + if ( word.substr( -1 ) === 'ա' ) { word = word.substr( 0, word.length -1 ) + 'այի'; - else if ( word.substr( -1 ) === 'ո' ) + } else if ( word.substr( -1 ) === 'ո' ) { word = word.substr( 0, word.length - 1 ) + 'ոյի'; - else if ( word.substr( -4 ) === 'գիրք' ) + } else if ( word.substr( -4 ) === 'գիրք' ) { word = word.substr( 0, word.length - 4 ) + 'գրքի'; - else + } else { word = word + 'ի'; + } break; } return word; diff --git a/resources/mediawiki.language/languages/la.js b/resources/mediawiki.language/languages/la.js index 313bb1c2..27110241 100644 --- a/resources/mediawiki.language/languages/la.js +++ b/resources/mediawiki.language/languages/la.js @@ -3,10 +3,10 @@ * @author Santhosh Thottingal */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'la', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'la', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'genitive': diff --git a/resources/mediawiki.language/languages/os.js b/resources/mediawiki.language/languages/os.js index 431e38c8..682b3903 100644 --- a/resources/mediawiki.language/languages/os.js +++ b/resources/mediawiki.language/languages/os.js @@ -4,23 +4,24 @@ */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'os', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'os', 'grammarForms' ), + // Ending for allative case + endAllative = 'мæ', + // Variable for 'j' beetwen vowels + jot = '', + // Variable for "-" for not Ossetic words + hyphen = '', + // Variable for ending + ending = ''; + if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + 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 = 'æм'; + endAllative = 'æм'; } // Works if word is in singular form. // Checking if word ends on one of the vowels: е, ё, и, о, ы, э, ю, я. @@ -45,10 +46,10 @@ mediaWiki.language.convertGrammar = function( word, form ) { ending = hyphen + jot + 'æн'; break; case 'allative': - ending = hyphen + end_allative; + ending = hyphen + endAllative; break; case 'ablative': - if ( jot == 'й' ) { + if ( jot === 'й' ) { ending = hyphen + jot + 'æ'; } else { diff --git a/resources/mediawiki.language/languages/ru.js b/resources/mediawiki.language/languages/ru.js index cfdbfc3b..1bc06326 100644 --- a/resources/mediawiki.language/languages/ru.js +++ b/resources/mediawiki.language/languages/ru.js @@ -2,27 +2,51 @@ * Russian (Русский) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'ru', 'grammarForms' ); +// These tests were originally made for names of Wikimedia +// websites, so they don't currently cover all the possible +// cases. + +mediaWiki.language.convertGrammar = function ( word, form ) { + 'use strict'; + + var grammarForms = mediaWiki.language.getData( 'ru', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'genitive': // родительный падеж - if ( ( word.substr( word.length - 4 ) == 'вики' ) || ( word.substr( word.length - 4 ) == 'Вики' ) ) { - } - else if ( word.substr( word.length - 1 ) == 'ь' ) + if ( word.substr( word.length - 1 ) === 'ь' ) { word = word.substr(0, word.length - 1 ) + 'я'; - else if ( word.substr( word.length - 2 ) == 'ия' ) + } else if ( word.substr( word.length - 2 ) === 'ия' ) { word = word.substr(0, word.length - 2 ) + 'ии'; - else if ( word.substr( word.length - 2 ) == 'ка' ) + } else if ( word.substr( word.length - 2 ) === 'ка' ) { word = word.substr(0, word.length - 2 ) + 'ки'; - else if ( word.substr( word.length - 2 ) == 'ти' ) + } else if ( word.substr( word.length - 2 ) === 'ти' ) { word = word.substr(0, word.length - 2 ) + 'тей'; - else if ( word.substr( word.length - 2 ) == 'ды' ) + } else if ( word.substr( word.length - 2 ) === 'ды' ) { word = word.substr(0, word.length - 2 ) + 'дов'; - else if ( word.substr( word.length - 3 ) == 'ник' ) + } else if ( word.substr( word.length - 3 ) === 'ные' ) { + word = word.substr(0, word.length - 3 ) + 'ных'; + } else if ( word.substr( word.length - 3 ) === 'ник' ) { word = word.substr(0, word.length - 3 ) + 'ника'; + } + break; + case 'prepositional': // предложный падеж + 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 ) + 'ных'; + } else if ( word.substr( word.length - 3 ) === 'ник' ) { + word = word.substr(0, word.length - 3 ) + 'нике'; + } break; } return word; diff --git a/resources/mediawiki.language/languages/sl.js b/resources/mediawiki.language/languages/sl.js index acd00bfd..fb335b6a 100644 --- a/resources/mediawiki.language/languages/sl.js +++ b/resources/mediawiki.language/languages/sl.js @@ -2,10 +2,10 @@ * Slovenian (Slovenščina) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'sl', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + var grammarForms = mediaWiki.language.getData( 'sl', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'mestnik': // locative diff --git a/resources/mediawiki.language/languages/uk.js b/resources/mediawiki.language/languages/uk.js index ee110b06..5e56b66f 100644 --- a/resources/mediawiki.language/languages/uk.js +++ b/resources/mediawiki.language/languages/uk.js @@ -2,33 +2,35 @@ * Ukrainian (Українська) language functions */ -mediaWiki.language.convertGrammar = function( word, form ) { - var grammarForms = mw.language.getData( 'uk', 'grammarForms' ); +mediaWiki.language.convertGrammar = function ( word, form ) { + /*jshint noempty:false */ + var grammarForms = mediaWiki.language.getData( 'uk', 'grammarForms' ); if ( grammarForms && grammarForms[form] ) { - return grammarForms[form][word] ; + return grammarForms[form][word]; } switch ( form ) { case 'genitive': // родовий відмінок - if ( ( word.substr( word.length - 4 ) == 'вікі' ) || ( word.substr( word.length - 4 ) == 'Вікі' ) ) { - } - else if ( word.substr( word.length - 1 ) == 'ь' ) + 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 ) == 'ія' ) + } else if ( word.substr( word.length - 2 ) === 'ія' ) { word = word.substr(0, word.length - 2 ) + 'ії'; - else if ( word.substr( word.length - 2 ) == 'ка' ) + } else if ( word.substr( word.length - 2 ) === 'ка' ) { word = word.substr(0, word.length - 2 ) + 'ки'; - else if ( word.substr( word.length - 2 ) == 'ти' ) + } else if ( word.substr( word.length - 2 ) === 'ти' ) { word = word.substr(0, word.length - 2 ) + 'тей'; - else if ( word.substr( word.length - 2 ) == 'ды' ) + } else if ( word.substr( word.length - 2 ) === 'ды' ) { word = word.substr(0, word.length - 2 ) + 'дов'; - else if ( word.substr( word.length - 3 ) == 'ник' ) + } 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 ) == 'Вікі' ) ) { + if ( ( word.substr( word.length - 4 ) === 'вікі' ) || ( word.substr( word.length - 4 ) === 'Вікі' ) ) { } - else if ( word.substr( word.length - 2 ) == 'ія' ) + else if ( word.substr( word.length - 2 ) === 'ія' ) { word = word.substr(0, word.length - 2 ) + 'ію'; + } break; } return word; diff --git a/resources/mediawiki.language/mediawiki.cldr.js b/resources/mediawiki.language/mediawiki.cldr.js index 6660eca4..c3023cd5 100644 --- a/resources/mediawiki.language/mediawiki.cldr.js +++ b/resources/mediawiki.language/mediawiki.cldr.js @@ -1,8 +1,8 @@ /** - * CLDR related utility methods + * CLDR related utility methods. */ -( function( mw ) { - "use strict"; +( function ( mw ) { + 'use strict'; var cldr = { /** @@ -10,19 +10,20 @@ * 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 + * @param {Array} pluralRules + * @return {number} plural form index */ - getPluralForm: function( number, pluralRules ) { - var pluralFormIndex = 0; - for ( pluralFormIndex = 0; pluralFormIndex < pluralRules.length; pluralFormIndex++ ) { - if ( mw.libs.pluralRuleParser( pluralRules[pluralFormIndex], number ) ) { + getPluralForm: function ( number, pluralRules ) { + var i; + for ( i = 0; i < pluralRules.length; i++ ) { + if ( mw.libs.pluralRuleParser( pluralRules[i], number ) ) { break; } } - return pluralFormIndex; + return i; } }; mw.cldr = cldr; -} )( mediaWiki ); + +}( mediaWiki ) ); diff --git a/resources/mediawiki.language/mediawiki.language.init.js b/resources/mediawiki.language/mediawiki.language.init.js index 30307a37..937b89bb 100644 --- a/resources/mediawiki.language/mediawiki.language.init.js +++ b/resources/mediawiki.language/mediawiki.language.init.js @@ -2,7 +2,7 @@ * Base language object with methods for storing and getting * language data. */ -( function ( mw, $ ) { +( function ( mw ) { var language = { /** @@ -58,4 +58,4 @@ mw.language = language; -}( mediaWiki, jQuery ) ); +}( mediaWiki ) ); diff --git a/resources/mediawiki.language/mediawiki.language.js b/resources/mediawiki.language/mediawiki.language.js index 935d4ff6..7f729bdc 100644 --- a/resources/mediawiki.language/mediawiki.language.js +++ b/resources/mediawiki.language/mediawiki.language.js @@ -43,12 +43,14 @@ var language = { * @param forms array List of plural forms * @return string Correct form for quantifier in this language */ - convertPlural: function( count, forms ) { - var pluralFormIndex = 0; + convertPlural: function ( count, forms ) { + var pluralRules, + pluralFormIndex = 0; + if ( !forms || forms.length === 0 ) { return ''; } - var pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); + pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); if ( !pluralRules ) { // default fallback. return ( count === 1 ) ? forms[0] : forms[1]; @@ -72,43 +74,6 @@ var language = { 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 ) { - var i, tmp, transformTable; - - if ( !mw.language.digitTransformTable ) { - return num; - } - // Set the target Transform table: - transformTable = mw.language.digitTransformTable; - // Check if the "restore" to Latin number flag is set: - if ( integer ) { - if ( parseInt( num, 10 ) === num ) { - return num; - } - tmp = []; - for ( i in transformTable ) { - tmp[ transformTable[ i ] ] = i; - } - transformTable = tmp; - } - var numberString = '' + num; - var convertedNumber = ''; - for ( i = 0; i < numberString.length; i++ ) { - if ( transformTable[ numberString[i] ] ) { - convertedNumber += transformTable[numberString[i]]; - } else { - convertedNumber += numberString[i]; - } - } - return integer ? parseInt( convertedNumber, 10 ) : convertedNumber; - }, - /** * Provides an alternative text depending on specified gender. * Usage {{gender:[gender|user object]|masculine|feminine|neutral}}. @@ -121,7 +86,7 @@ var language = { * * @return string */ - gender: function( gender, forms ) { + gender: function ( gender, forms ) { if ( !forms || forms.length === 0 ) { return ''; } @@ -151,10 +116,8 @@ var language = { return grammarForms[form][word] || word; } return word; - }, + } - // Digit Transform Table, populated by language classes where applicable - digitTransformTable: mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'digitTransformTable' ) }; $.extend( mw.language, language ); diff --git a/resources/mediawiki.language/mediawiki.language.numbers.js b/resources/mediawiki.language/mediawiki.language.numbers.js new file mode 100644 index 00000000..fada6ce1 --- /dev/null +++ b/resources/mediawiki.language/mediawiki.language.numbers.js @@ -0,0 +1,243 @@ +/* + * Number related utilities for mediawiki.language + */ +( function ( mw, $ ) { + + /** + * Pad a string to guarantee that it is at least `size` length by + * filling with the character `ch` at either the start or end of the + * string. Pads at the start, by default. + * example: + * Fill the string to length 10 with '+' characters on the right. Yields 'blah++++++'. + * pad('blah', 10, '+', true); + * + * @param {string} text The string to pad + * @param {Number} size To provide padding + * @param {string} ch Character to pad, defaults to '0' + * @param {Boolean} end Adds padding at the end if true, otherwise pads at start + * @return {string} + */ + function pad ( text, size, ch, end ) { + if ( !ch ) { + ch = '0'; + } + + var out = String( text ), + padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) ); + + return end ? out + padStr : padStr + out; + } + + /** + * Efficiently replicate a string n times. + * + * @param {string} str The string to replicate + * @param {Number} num Number of times to replicate the string + * @return {string} + */ + function replicate ( str, num ) { + if ( num <= 0 || !str ) { + return ''; + } + + var buf = []; + while (num) { + buf.push( str ); + str += str; + } + return buf.join( '' ); + } + + /** + * Apply numeric pattern to absolute value using options. Gives no + * consideration to local customs. + * + * Adapted from dojo/number library with thanks + * http://dojotoolkit.org/reference-guide/1.8/dojo/number.html + * + * @param {Number} value the number to be formatted, ignores sign + * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`) + * @param {string} options.decimalThe decimal separator + * @param {string} options.group The group separator + * + * @return {string} + */ + function commafyNumber( value, pattern, options ) { + options = options || { + group: ',', + decimal: '.' + }; + + if ( isNaN( value) ) { + return value; + } + + var padLength, + patternDigits, + index, + whole, + off, + remainder, + patternParts = pattern.split( '.' ), + maxPlaces = ( patternParts[1] || [] ).length, + valueParts = String( Math.abs( value ) ).split( '.' ), + fractional = valueParts[1] || '', + groupSize = 0, + groupSize2 = 0, + pieces = []; + + if ( patternParts[1] ) { + // Pad fractional with trailing zeros + padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 ); + + if ( padLength > fractional.length ) { + valueParts[1] = pad( fractional, padLength, '0', true ); + } + + // Truncate fractional + if ( maxPlaces < fractional.length ) { + valueParts[1] = fractional.substr( 0, maxPlaces ); + } + } else { + if ( valueParts[1] ) { + valueParts.pop(); + } + } + + // Pad whole with leading zeros + patternDigits = patternParts[0].replace( ',', '' ); + + padLength = patternDigits.indexOf( '0' ); + + if ( padLength !== -1 ) { + padLength = patternDigits.length - padLength; + + if ( padLength > valueParts[0].length ) { + valueParts[0] = pad( valueParts[0], padLength ); + } + + // Truncate whole + if ( patternDigits.indexOf( '#' ) === -1 ) { + valueParts[0] = valueParts[0].substr( valueParts[0].length - padLength ); + } + } + + // Add group separators + index = patternParts[0].lastIndexOf( ',' ); + + if ( index !== -1 ) { + groupSize = patternParts[0].length - index - 1; + remainder = patternParts[0].substr( 0, index ); + index = remainder.lastIndexOf( ',' ); + if ( index !== -1 ) { + groupSize2 = remainder.length - index - 1; + } + } + + for ( whole = valueParts[0]; whole; ) { + off = whole.length - groupSize; + + pieces.push( ( off > 0 ) ? whole.substr( off ) : whole ); + whole = ( off > 0 ) ? whole.slice( 0, off ) : ''; + + if ( groupSize2 ) { + groupSize = groupSize2; + } + } + valueParts[0] = pieces.reverse().join( options.group ); + + return valueParts.join( options.decimal ); + } + + $.extend( mw.language, { + + /** + * Converts a number using digitTransformTable. + * + * @param {Number} num Value to be converted + * @param {boolean} integer Convert the return value to an integer + * @return {Number|string} Formatted number + */ + convertNumber: function ( num, integer ) { + var i, tmp, transformTable, numberString, convertedNumber, pattern; + + pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ), + 'digitGroupingPattern' ) || '#,##0.###'; + + // Set the target transform table: + transformTable = mw.language.getDigitTransformTable(); + + if ( !transformTable ) { + return num; + } + + // Check if the 'restore' to Latin number flag is set: + if ( integer ) { + if ( parseInt( num, 10 ) === num ) { + return num; + } + tmp = []; + for ( i in transformTable ) { + tmp[ transformTable[ i ] ] = i; + } + transformTable = tmp; + numberString = num + ''; + } else { + numberString = mw.language.commafy( num, pattern ); + } + + convertedNumber = ''; + for ( i = 0; i < numberString.length; i++ ) { + if ( transformTable[ numberString[i] ] ) { + convertedNumber += transformTable[numberString[i]]; + } else { + convertedNumber += numberString[i]; + } + } + return integer ? parseInt( convertedNumber, 10 ) : convertedNumber; + }, + + getDigitTransformTable: function () { + return mw.language.getData( mw.config.get( 'wgUserLanguage' ), + 'digitTransformTable' ) || []; + }, + + getSeparatorTransformTable: function () { + return mw.language.getData( mw.config.get( 'wgUserLanguage' ), + 'separatorTransformTable' ) || []; + }, + + /** + * Apply pattern to format value as a string using as per + * unicode.org TR35 - http://www.unicode.org/reports/tr35/#Number_Format_Patterns. + * + * @param {Number} value + * @param {string} pattern Pattern string as described by Unicode TR35 + * @throws Error + * @returns {String} + */ + commafy: function ( value, pattern ) { + var numberPattern, + transformTable = mw.language.getSeparatorTransformTable(), + group = transformTable[','] || ',', + numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough + decimal = transformTable['.'] || '.', + patternList = pattern.split( ';' ), + positivePattern = patternList[0]; + + pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern ); + numberPattern = positivePattern.match( numberPatternRE ); + + if ( !numberPattern ) { + throw new Error( 'unable to find a number expression in pattern: ' + pattern ); + } + + return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], { + decimal: decimal, + group: group + } ) ); + } + + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.libs/CLDRPluralRuleParser.js b/resources/mediawiki.libs/CLDRPluralRuleParser.js index 91bdc07d..441bc91f 100644 --- a/resources/mediawiki.libs/CLDRPluralRuleParser.js +++ b/resources/mediawiki.libs/CLDRPluralRuleParser.js @@ -207,6 +207,7 @@ function pluralRuleParser(rule, number) { } function range() { + var i; var result = sequence([digits, _range_, digits]); if (result !== null) { debug(" -- passed range"); diff --git a/resources/mediawiki.page/mediawiki.page.patrol.ajax.js b/resources/mediawiki.page/mediawiki.page.patrol.ajax.js new file mode 100644 index 00000000..d7a07d71 --- /dev/null +++ b/resources/mediawiki.page/mediawiki.page.patrol.ajax.js @@ -0,0 +1,63 @@ +/** + * Animate patrol links to use asynchronous API requests to + * patrol pages, rather than navigating to a different URI. + * + * @since 1.21 + * @author Marius Hoch + */ +( function ( mw, $ ) { + if ( !mw.user.tokens.exists( 'patrolToken' ) ) { + // Current user has no patrol right, or an old cached version of user.tokens + // that didn't have patrolToken yet. + return; + } + $( document ).ready( function () { + var $patrolLinks = $( '.patrollink a' ); + $patrolLinks.on( 'click', function ( e ) { + var $spinner, href, rcid, apiRequest; + + // Hide the link and create a spinner to show it inside the brackets. + $spinner = $.createSpinner( { + size: 'small', + type: 'inline' + } ); + $( this ).hide().after( $spinner ); + + href = $( this ).attr( 'href' ); + rcid = mw.util.getParamValue( 'rcid', href ); + apiRequest = new mw.Api(); + + apiRequest.post( { + action: 'patrol', + token: mw.user.tokens.get( 'patrolToken' ), + rcid: rcid + } ) + .done( function ( data ) { + // Remove all patrollinks from the page (including any spinners inside). + $patrolLinks.closest( '.patrollink' ).remove(); + if ( data.patrol !== undefined ) { + // Success + var title = new mw.Title( data.patrol.title ); + mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); + } else { + // This should never happen as errors should trigger fail + mw.notify( mw.msg( 'markedaspatrollederrornotify' ) ); + } + } ) + .fail( function ( error ) { + $spinner.remove(); + // Restore the patrol link. This allows the user to try again + // (or open it in a new window, bypassing this ajax module). + $patrolLinks.show(); + if ( error === 'noautopatrol' ) { + // Can't patrol own + mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ) ); + } else { + mw.notify( mw.msg( 'markedaspatrollederrornotify' ) ); + } + } ); + + e.preventDefault(); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.ready.js b/resources/mediawiki.page/mediawiki.page.ready.js index 370c3a19..684f582f 100644 --- a/resources/mediawiki.page/mediawiki.page.ready.js +++ b/resources/mediawiki.page/mediawiki.page.ready.js @@ -1,24 +1,28 @@ -jQuery( document ).ready( function( $ ) { +( function ( mw, $ ) { + $( function () { + var $sortableTables; - /* Emulate placeholder if not supported by browser */ - if ( !( 'placeholder' in document.createElement( 'input' ) ) ) { - $( 'input[placeholder]' ).placeholder(); - } + /* Emulate placeholder if not supported by browser */ + if ( !( 'placeholder' in document.createElement( 'input' ) ) ) { + $( 'input[placeholder]' ).placeholder(); + } - /* Enable makeCollapsible */ - $( '.mw-collapsible' ).makeCollapsible(); + /* Enable makeCollapsible */ + $( '.mw-collapsible' ).makeCollapsible(); - /* Lazy load jquery.tablesorter */ - if ( $( 'table.sortable' ).length ) { - mw.loader.using( 'jquery.tablesorter', function() { - $( 'table.sortable' ).tablesorter(); - }); - } + /* Lazy load jquery.tablesorter */ + $sortableTables = $( 'table.sortable' ); + if ( $sortableTables.length ) { + mw.loader.using( 'jquery.tablesorter', function () { + $sortableTables.tablesorter(); + }); + } - /* Enable CheckboxShiftClick */ - $( 'input[type=checkbox]:not(.noshiftselect)' ).checkboxShiftClick(); + /* Enable CheckboxShiftClick */ + $( 'input[type=checkbox]:not(.noshiftselect)' ).checkboxShiftClick(); - /* Add accesskey hints to the tooltips */ - mw.util.updateTooltipAccessKeys(); + /* Add accesskey hints to the tooltips */ + mw.util.updateTooltipAccessKeys(); -} ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.watch.ajax.js b/resources/mediawiki.page/mediawiki.page.watch.ajax.js index a7e059c4..f945fa9d 100644 --- a/resources/mediawiki.page/mediawiki.page.watch.ajax.js +++ b/resources/mediawiki.page/mediawiki.page.watch.ajax.js @@ -24,13 +24,14 @@ 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 ) { + if ( state === undefined ) { $li.trigger( 'watchpage.mw', otherAction ); } @@ -96,7 +97,7 @@ // Expose local methods mw.page.watch = { - 'updateWatchLink': updateWatchLink + updateWatchLink: updateWatchLink }; $( document ).ready( function () { @@ -134,7 +135,9 @@ otherAction = action === 'watch' ? 'unwatch' : 'watch'; $li = $link.closest( 'li' ); - mw.notify( $.parseHTML( watchResponse.message ), { tag: 'watch-self' } ); + mw.notify( $.parseHTML( watchResponse.message ), { + tag: 'watch-self' + } ); // Set link to opposite updateWatchLink( $link, otherAction ); @@ -144,7 +147,7 @@ if ( watchResponse.watched !== undefined ) { $( '#wpWatchthis' ).prop( 'checked', true ); } else { - $( '#wpWatchthis' ).removeProp( 'checked' ); + $( '#wpWatchthis' ).prop( 'checked', false ); } }, // Error @@ -162,14 +165,14 @@ title: cleanTitle }, cleanTitle ); - msg = mw.messsage( 'watcherrortext', link ); + msg = mw.message( 'watcherrortext', link ); // Report to user about the error mw.notify( msg, { tag: 'watch-self' } ); } ); - }); - }); + } ); + } ); }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.block.js b/resources/mediawiki.special/mediawiki.special.block.js index 6f79929b..2a158dfb 100644 --- a/resources/mediawiki.special/mediawiki.special.block.js +++ b/resources/mediawiki.special/mediawiki.special.block.js @@ -1,46 +1,46 @@ -/* JavaScript for Special:Block */ +/** + * JavaScript for Special:Block + */ +( function ( mw, $ ) { + $( document ).ready( function () { + var $blockTarget = $( '#mw-bi-target' ), + $anonOnlyRow = $( '#mw-input-wpHardBlock' ).closest( 'tr' ), + $enableAutoblockRow = $( '#mw-input-wpAutoBlock' ).closest( 'tr' ), + $hideUser = $( '#mw-input-wpHideUser' ).closest( 'tr' ), + $watchUser = $( '#mw-input-wpWatch' ).closest( 'tr' ); -jQuery( function( $ ) { + function updateBlockOptions( instant ) { + var blocktarget = $.trim( $blockTarget.val() ), + isEmpty = blocktarget === '', + isIp = mw.util.isIPv4Address( blocktarget, true ) || mw.util.isIPv6Address( blocktarget, true ), + isIpRange = isIp && blocktarget.match( /\/\d+$/ ); - var DO_INSTANT = true, - $blockTarget = $( '#mw-bi-target' ), - $anonOnlyRow = $( '#mw-input-wpHardBlock' ).closest( 'tr' ), - $enableAutoblockRow = $( '#mw-input-wpAutoBlock' ).closest( 'tr' ), - $hideUser = $( '#mw-input-wpHideUser' ).closest( 'tr' ), - $watchUser = $( '#mw-input-wpWatch' ).closest( 'tr' ); - - var updateBlockOptions = function( instant ) { - if ( !$blockTarget.length ) { - return; + if ( isIp && !isEmpty ) { + $enableAutoblockRow.goOut( instant ); + $hideUser.goOut( instant ); + } else { + $enableAutoblockRow.goIn( instant ); + $hideUser.goIn( instant ); + } + if ( !isIp && !isEmpty ) { + $anonOnlyRow.goOut( instant ); + } else { + $anonOnlyRow.goIn( instant ); + } + if ( isIpRange && !isEmpty ) { + $watchUser.goOut( instant ); + } else { + $watchUser.goIn( instant ); + } } - var blocktarget = $.trim( $blockTarget.val() ); - var isEmpty = ( blocktarget === '' ); - var isIp = mw.util.isIPv4Address( blocktarget, true ) || mw.util.isIPv6Address( blocktarget, true ); - var isIpRange = isIp && blocktarget.match( /\/\d+$/ ); + if ( $blockTarget.length ) { + // Bind functions so they're checked whenever stuff changes + $blockTarget.keyup( updateBlockOptions ); - if ( isIp && !isEmpty ) { - $enableAutoblockRow.goOut( instant ); - $hideUser.goOut( instant ); - } else { - $enableAutoblockRow.goIn( instant ); - $hideUser.goIn( instant ); - } - if ( !isIp && !isEmpty ) { - $anonOnlyRow.goOut( instant ); - } else { - $anonOnlyRow.goIn( instant ); + // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours) + updateBlockOptions( /* instant= */ true ); } - if ( isIpRange && !isEmpty ) { - $watchUser.goOut( instant ); - } else { - $watchUser.goIn( instant ); - } - }; - - // Bind functions so they're checked whenever stuff changes - $blockTarget.keyup( updateBlockOptions ); + } ); +}( mediaWiki, jQuery ) ); - // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours) - updateBlockOptions( DO_INSTANT ); -}); diff --git a/resources/mediawiki.special/mediawiki.special.changeemail.js b/resources/mediawiki.special/mediawiki.special.changeemail.js index cab0bbd3..14c2f036 100644 --- a/resources/mediawiki.special/mediawiki.special.changeemail.js +++ b/resources/mediawiki.special/mediawiki.special.changeemail.js @@ -1,42 +1,42 @@ -/* +/** * JavaScript for Special:ChangeEmail */ ( function ( mw, $ ) { + /** + * Given an email validity status (true, false, null) update the label CSS class + */ + function updateMailValidityLabel( mail ) { + var isValid = mw.util.validateEmail( mail ), + $label = $( '#mw-emailaddress-validity' ); -/** - * Given an email validity status (true, false, null) update the label CSS class - */ -function updateMailValidityLabel( mail ) { - var isValid = mw.util.validateEmail( mail ), - $label = $( '#mw-emailaddress-validity' ); - - // We allow empty address - if( isValid === null ) { - $label.text( '' ).removeClass( 'valid invalid' ); + // We allow empty address + if ( isValid === null ) { + $label.text( '' ).removeClass( 'valid invalid' ); - // Valid - } else if ( isValid ) { - $label.text( mw.msg( 'email-address-validity-valid' ) ).addClass( 'valid' ).removeClass( 'invalid' ); + // Valid + } else if ( isValid ) { + $label.text( mw.msg( 'email-address-validity-valid' ) ).addClass( 'valid' ).removeClass( 'invalid' ); - // Not valid - } else { - $label.text( mw.msg( 'email-address-validity-invalid' ) ).addClass( 'invalid' ).removeClass( 'valid' ); + // Not valid + } else { + $label.text( mw.msg( 'email-address-validity-invalid' ) ).addClass( 'invalid' ).removeClass( 'valid' ); + } } -} -$( 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( '' ); - } - updateMailValidityLabel( $(this).val() ); - $(this).keyup( function () { - updateMailValidityLabel( $(this).val() ); + $( 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 immediate feedback. + $( '#wpNewEmail' ).one( 'blur', function () { + var $this = $( this ); + if ( $( '#mw-emailaddress-validity' ).length === 0 ) { + $this.after( '' ); + } + + updateMailValidityLabel( $this.val() ); + $this.keyup( function () { + updateMailValidityLabel( $this.val() ); + } ); } ); } ); -} ); - }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.changeslist.css b/resources/mediawiki.special/mediawiki.special.changeslist.css index 8a5421e8..fcdeba1b 100644 --- a/resources/mediawiki.special/mediawiki.special.changeslist.css +++ b/resources/mediawiki.special/mediawiki.special.changeslist.css @@ -31,12 +31,12 @@ table.mw-enhanced-rc td.mw-enhanced-rc-nested { float: none; } -/* If JS is disabled, the arrow shouldn't be shown */ -.client-nojs .mw-enhancedchanges-arrow.mw-collapsible-toggle { +/* If JS is disabled, the arrows or the placeholder space shouldn't be shown */ +.client-nojs .mw-enhancedchanges-arrow-space { display: none; } -.mw-enhancedchanges-arrow { +.mw-enhancedchanges-arrow-space { display: inline-block; *display: inline; /* IE7 and below */ zoom: 1; @@ -44,8 +44,9 @@ table.mw-enhanced-rc td.mw-enhanced-rc-nested { height: 15px; } -.mw-enhancedchanges-arrow.mw-enhancedchanges-arrow-space { - background: none; +/* let it look like it is clickable */ +.mw-enhancedchanges-arrow.mw-collapsible-toggle { + cursor: pointer; } .mw-enhancedchanges-arrow.mw-collapsible-toggle-collapsed { diff --git a/resources/mediawiki.special/mediawiki.special.javaScriptTest.js b/resources/mediawiki.special/mediawiki.special.javaScriptTest.js index 808d5fe8..a560ca95 100644 --- a/resources/mediawiki.special/mediawiki.special.javaScriptTest.js +++ b/resources/mediawiki.special/mediawiki.special.javaScriptTest.js @@ -8,7 +8,7 @@ // (only if a framework was found, not on error pages). $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function () { - var $html = $( '

    ' ), diff --git a/resources/mediawiki.special/mediawiki.special.js b/resources/mediawiki.special/mediawiki.special.js index 3526cef4..8edb1cbe 100644 --- a/resources/mediawiki.special/mediawiki.special.js +++ b/resources/mediawiki.special/mediawiki.special.js @@ -1 +1,5 @@ -mw.special = {}; +/* + * Namespace for mediawiki.special.* modules + */ + +mediaWiki.special = {}; diff --git a/resources/mediawiki.special/mediawiki.special.movePage.js b/resources/mediawiki.special/mediawiki.special.movePage.js index 68c2ed07..f719d07c 100644 --- a/resources/mediawiki.special/mediawiki.special.movePage.js +++ b/resources/mediawiki.special/mediawiki.special.movePage.js @@ -1,5 +1,6 @@ -/* JavaScript for Special:MovePage */ - -jQuery( function( $ ) { +/** + * JavaScript for Special:MovePage + */ +jQuery( document ).ready( function ( $ ) { $( '#wpReason, #wpNewTitleMain' ).byteLimit(); -}); +} ); diff --git a/resources/mediawiki.special/mediawiki.special.preferences.js b/resources/mediawiki.special/mediawiki.special.preferences.js index 47872907..6eaec6a1 100644 --- a/resources/mediawiki.special/mediawiki.special.preferences.js +++ b/resources/mediawiki.special/mediawiki.special.preferences.js @@ -1,175 +1,199 @@ -/* +/** * JavaScript for Special:Preferences */ jQuery( document ).ready( function ( $ ) { -$( '#prefsubmit' ).attr( 'id', 'prefcontrol' ); -var $preftoc = $('
      '); -var $preferences = $( '#preferences' ) - .addClass( 'jsprefs' ) - .before( $preftoc ); - -var $fieldsets = $preferences.children( 'fieldset' ) - .hide() - .addClass( 'prefsection' ); + var $preftoc, $preferences, $fieldsets, $legends, + hash, + $tzSelect, $tzTextbox, $localtimeHolder, servertime; + + $( '#prefsubmit' ).attr( 'id', 'prefcontrol' ); + + $preftoc = $('
        '); + $preferences = $( '#preferences' ) + .addClass( 'jsprefs' ) + .before( $preftoc ); + $fieldsets = $preferences.children( 'fieldset' ) + .hide() + .addClass( 'prefsection' ); + $legends = $fieldsets + .children( 'legend' ) + .addClass( 'mainLegend' ); + + /** + * It uses document.getElementById for security reasons (HTML injections in $()). + * + * @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(); + } + } -var $legends = $fieldsets.children( 'legend' ) - .addClass( 'mainLegend' ); + // Populate the prefToc + $legends.each( function ( i, legend ) { + var $legend = $(legend), + ident, $li, $a; + if ( i === 0 ) { + $legend.parent().show(); + } + ident = $legend.parent().attr( 'id' ); + + $li = $( '
      • ' ) + .addClass( i === 0 ? 'selected' : '' ); + $a = $( '' ) + .attr( { + id: ident.replace( 'mw-prefsection', 'preftab' ), + href: '#' + ident + } ) + .text( $legend.text() ); + $li.append( $a ); + $preftoc.append( $li ); + } ); + + // If we've reloaded the page or followed an open-in-new-window, + // make the selected tab visible. + hash = window.location.hash; + if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) { + switchPrefTab( hash.replace( '#mw-prefsection-' , '' ) ); + } -/** - * 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; + // In browsers that support the onhashchange event we will not bind click + // handlers and instead let the browser do the default behavior (clicking the + // 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). + // Note the special check for IE "compatibility" mode. + if ( 'onhashchange' in window && + ( document.documentMode === undefined || document.documentMode >= 8 ) + ) { + $(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(); + }); } - $( 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(); + + /** + * Timezone functions. + * Guesses Timezone from browser and updates fields onchange + */ + + $tzSelect = $( '#mw-input-wptimecorrection' ); + $tzTextbox = $( '#mw-input-wptimecorrection-other' ); + $localtimeHolder = $( '#wpLocalTime' ); + servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 ); + + function minutesToHours( min ) { + var tzHour = Math.floor( Math.abs( min ) / 60 ), + tzMin = Math.abs( min ) % 60, + tzString = ( ( min >= 0 ) ? '' : '-' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour + + ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin; + return tzString; } -} -// Populate the prefToc -$legends.each( function ( i, legend ) { - var $legend = $(legend); - if ( i === 0 ) { - $legend.parent().show(); + function hoursToMinutes( hour ) { + var minutes, + arr = hour.split( ':' ); + + arr[0] = parseInt( arr[0], 10 ); + + if ( arr.length === 1 ) { + // Specification is of the form [-]XX + minutes = arr[0] * 60; + } else { + // Specification is of the form [-]XX:XX + minutes = Math.abs( arr[0] ) * 60 + parseInt( arr[1], 10 ); + if ( arr[0] < 0 ) { + minutes *= -1; + } + } + // Gracefully handle non-numbers. + if ( isNaN( minutes ) ) { + return 0; + } else { + return minutes; + } } - var ident = $legend.parent().attr( 'id' ); - - var $li = $( '
      • ', { - 'class' : ( i === 0 ) ? 'selected' : null - }); - var $a = $( '', { - text : $legend.text(), - id : ident.replace( 'mw-prefsection', 'preftab' ), - href : '#' + ident - }); - $li.append( $a ); - $preftoc.append( $li ); -} ); -// If we've reloaded the page or followed an open-in-new-window, -// make the selected tab visible. -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 -// 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' ); + function updateTimezoneSelection () { + var minuteDiff, localTime, + type = $tzSelect.val(); + + if ( type === 'guess' ) { + // Get browser timezone & fill it in + minuteDiff = -( new Date().getTimezoneOffset() ); + $tzTextbox.val( minutesToHours( minuteDiff ) ); + $tzSelect.val( 'other' ); + $tzTextbox.prop( 'disabled', false ); + } else if ( type === 'other' ) { + // Grab data from the textbox, parse it. + minuteDiff = hoursToMinutes( $tzTextbox.val() ); + } else { + // Grab data from the $tzSelect value + minuteDiff = parseInt( type.split( '|' )[1], 10 ) || 0; + $tzTextbox.val( minutesToHours( minuteDiff ) ); } - }); -// 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. -* Guesses Timezone from browser and updates fields onchange -*/ - -var $tzSelect = $( '#mw-input-wptimecorrection' ); -var $tzTextbox = $( '#mw-input-wptimecorrection-other' ); - -var $localtimeHolder = $( '#wpLocalTime' ); -var servertime = parseInt( $( 'input[name=wpServerTime]' ).val(), 10 ); -var minuteDiff = 0; - -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 + - ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin; - return tzString; -}; - -var hoursToMinutes = function ( hour ) { - var arr = hour.split( ':' ); - arr[0] = parseInt( arr[0], 10 ); - - var minutes; - if ( arr.length == 1 ) { - // Specification is of the form [-]XX - minutes = arr[0] * 60; - } else { - // Specification is of the form [-]XX:XX - minutes = Math.abs( arr[0] ) * 60 + parseInt( arr[1], 10 ); - if ( arr[0] < 0 ) { - minutes *= -1; + // Determine local time from server time and minutes difference, for display. + localTime = servertime + minuteDiff; + + // Bring time within the [0,1440) range. + while ( localTime < 0 ) { + localTime += 1440; } + while ( localTime >= 1440 ) { + localTime -= 1440; + } + $localtimeHolder.text( minutesToHours( localTime ) ); } - // Gracefully handle non-numbers. - if ( isNaN( minutes ) ) { - return 0; - } else { - return minutes; - } -}; - -var updateTimezoneSelection = function () { - var type = $tzSelect.val(); - if ( type == 'guess' ) { - // Get browser timezone & fill it in - minuteDiff = -new Date().getTimezoneOffset(); - $tzTextbox.val( minutesToHours( minuteDiff ) ); - $tzSelect.val( 'other' ); - $tzTextbox.get( 0 ).disabled = false; - } else if ( type == 'other' ) { - // Grab data from the textbox, parse it. - minuteDiff = hoursToMinutes( $tzTextbox.val() ); - } else { - // Grab data from the $tzSelect value - minuteDiff = parseInt( type.split( '|' )[1], 10 ) || 0; - $tzTextbox.val( minutesToHours( minuteDiff ) ); + + if ( $tzSelect.length && $tzTextbox.length ) { + $tzSelect.change( updateTimezoneSelection ); + $tzTextbox.blur( updateTimezoneSelection ); + updateTimezoneSelection(); } - // Determine local time from server time and minutes difference, for display. - var localTime = servertime + minuteDiff; + // Preserve the tab after saving the preferences + // Not using cookies, because their deletion results are inconsistent. + // Not using jStorage due to its enormous size (for this feature) + if ( window.sessionStorage ) { + if ( sessionStorage.getItem( 'mediawikiPreferencesTab' ) !== null ) { + switchPrefTab( sessionStorage.getItem( 'mediawikiPreferencesTab' ), 'noHash' ); + } + // Deleting the key, the tab states should be reset until we press Save + sessionStorage.removeItem( 'mediawikiPreferencesTab' ); - // Bring time within the [0,1440) range. - while ( localTime < 0 ) { - localTime += 1440; - } - while ( localTime >= 1440 ) { - localTime -= 1440; + $( '#mw-prefs-form' ).submit( function () { + var storageData = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' ); + sessionStorage.setItem( 'mediawikiPreferencesTab', storageData ); + } ); } - $localtimeHolder.text( minutesToHours( localTime ) ); -}; - -if ( $tzSelect.length && $tzTextbox.length ) { - $tzSelect.change( function () { updateTimezoneSelection(); } ); - $tzTextbox.blur( function () { updateTimezoneSelection(); } ); - updateTimezoneSelection(); -} } ); diff --git a/resources/mediawiki.special/mediawiki.special.recentchanges.js b/resources/mediawiki.special/mediawiki.special.recentchanges.js index 7996d935..d1c1354f 100644 --- a/resources/mediawiki.special/mediawiki.special.recentchanges.js +++ b/resources/mediawiki.special/mediawiki.special.recentchanges.js @@ -1,39 +1,34 @@ -/* JavaScript for Special:RecentChanges */ +/** + * JavaScript for Special:RecentChanges + */ ( function ( mw, $ ) { + var rc, $checkboxes, $select; - var checkboxes = [ 'nsassociated', 'nsinvert' ]; - - /** - * @var select {jQuery} - */ - var $select = null; - - var rc = mw.special.recentchanges = { - + rc = { /** * Handler to disable/enable the namespace selector checkboxes when the * special 'all' namespace is selected/unselected respectively. */ updateCheckboxes: function () { // The option element for the 'all' namespace has an empty value - var isAllNS = $select.find('option:selected').val() === ''; + var isAllNS = $select.val() === ''; // Iterates over checkboxes and propagate the selected option - $.each( checkboxes, function ( i, id ) { - $( '#' + id ).prop( 'disabled', isAllNS ); - }); + $checkboxes.prop( 'disabled', isAllNS ); }, init: function () { - // Populate $select = $( '#namespace' ); + $checkboxes = $( '#nsassociated, #nsinvert' ); // Bind to change event, and trigger once to set the initial state of the checkboxes. - $select.change( rc.updateCheckboxes ).change(); + rc.updateCheckboxes(); + $select.change( rc.updateCheckboxes ); } }; - // Run when document is ready - $( rc.init ); + $( document ).ready( rc.init ); + + mw.special.recentchanges = rc; }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.search.js b/resources/mediawiki.special/mediawiki.special.search.js index 04954e8d..2dab3026 100644 --- a/resources/mediawiki.special/mediawiki.special.search.js +++ b/resources/mediawiki.special/mediawiki.special.search.js @@ -1,49 +1,53 @@ -/* +/** * JavaScript for Special:Search */ -( function( $, mw ) { $( function() { +( function ( mw, $ ) { + $( document ).ready( function () { + var $checkboxes, $headerLinks; -// Emulate HTML5 autofocus behavior in non HTML5 compliant browsers -if ( !( 'autofocus' in document.createElement( 'input' ) ) ) { - $( 'input[autofocus]:first' ).focus(); -} + // Emulate HTML5 autofocus behavior in non HTML5 compliant browsers + if ( !( 'autofocus' in document.createElement( 'input' ) ) ) { + $( 'input[autofocus]' ).eq( 0 ).focus(); + } -// Create check all/none button -var $checkboxes = $('#powersearch input[id^=mw-search-ns]'); -$('#mw-search-togglebox').append( - $('' ) - .attr( { 'href': fb.title.getUrl(), 'target': '_blank' } ) - .css( { 'white-space': 'nowrap' } ); + $feedbackPageLink = $( '' ) + .attr( { + href: fb.title.getUrl(), + target: '_blank' + } ) + .css( { + whiteSpace: 'nowrap' + } ); - var $bugNoteLink = $( '' ).attr( { 'href': '#' } ).click( function () { + $bugNoteLink = $( '' ).attr( { href: '#' } ).click( function () { fb.displayBugs(); } ); - var $bugsListLink = $( '' ).attr( { 'href': fb.bugsListLink, 'target': '_blank' } ); + $bugsListLink = $( '' ).attr( { + href: fb.bugsListLink, + target: '_blank' + } ); // TODO: Use a stylesheet instead of these inline styles this.$dialog = @@ -108,7 +119,7 @@ ), $( '' ).append( mw.msg( 'feedback-adding' ), - $( '
        ' ), + $( '
        ' ), $( '' ) ), $( '' ).msg( @@ -148,9 +159,9 @@ }, displayBugs: function () { - var fb = this; + var fb = this, + bugsButtons = {}; this.display( 'bugs' ); - var bugsButtons = {}; bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function () { window.open( fb.bugsLink, '_blank' ); }; @@ -163,9 +174,9 @@ }, displayThanks: function () { - var fb = this; + var fb = this, + closeButton = {}; this.display( 'thanks' ); - var closeButton = {}; closeButton[ mw.msg( 'feedback-close' ) ] = function () { fb.$dialog.dialog( 'close' ); }; @@ -181,14 +192,14 @@ * message: {String} */ displayForm: function ( contents ) { - var fb = this; + var fb = this, + formButtons = {}; 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 () { fb.submit(); }; @@ -199,10 +210,10 @@ }, displayError: function ( message ) { - var fb = this; + var fb = this, + closeButton = {}; this.display( 'error' ); this.$dialog.find( '.feedback-error-msg' ).msg( message ); - var closeButton = {}; closeButton[ mw.msg( 'feedback-close' ) ] = function () { fb.$dialog.dialog( 'close' ); }; @@ -231,7 +242,7 @@ } } - function err( code, info ) { + function err() { // ajax request failed fb.displayError( 'feedback-error3' ); } diff --git a/resources/mediawiki/mediawiki.hidpi.js b/resources/mediawiki/mediawiki.hidpi.js new file mode 100644 index 00000000..ecee450c --- /dev/null +++ b/resources/mediawiki/mediawiki.hidpi.js @@ -0,0 +1,5 @@ +jQuery( function ( $ ) { + // Apply hidpi images on DOM-ready + // Some may have already partly preloaded at low resolution. + $( 'body' ).hidpi(); +} ); diff --git a/resources/mediawiki/mediawiki.htmlform.js b/resources/mediawiki/mediawiki.htmlform.js index a4753b99..83bf2e3a 100644 --- a/resources/mediawiki/mediawiki.htmlform.js +++ b/resources/mediawiki/mediawiki.htmlform.js @@ -1,64 +1,62 @@ /** - * Utility functions for jazzing up HTMLForm elements + * Utility functions for jazzing up HTMLForm elements. */ ( function ( $ ) { -/** - * jQuery plugin to fade or snap to visible state. - * - * @param boolean instantToggle (optional) - * @return jQuery - */ -$.fn.goIn = function ( instantToggle ) { - if ( instantToggle === true ) { - return $(this).show(); - } - return $(this).stop( true, true ).fadeIn(); -}; - -/** - * jQuery plugin to fade or snap to hiding state. - * - * @param boolean instantToggle (optional) - * @return jQuery - */ -$.fn.goOut = function ( instantToggle ) { - if ( instantToggle === true ) { - return $(this).hide(); - } - return $(this).stop( true, true ).fadeOut(); -}; - -/** - * Bind a function to the jQuery object via live(), and also immediately trigger - * 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 ){ - $(this) - .live( 'change', callback ) - .each( function ( index, element ){ - callback.call( this, true ); - } ); -}; - -// Document ready: -$( function () { - - // Animate the SelectOrOther fields, to only show the text field when - // 'other' is selected. - $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) { - var $other = $( '#' + $(this).attr( 'id' ) + '-other' ); - $other = $other.add( $other.siblings( 'br' ) ); - if ( $(this).val() === 'other' ) { - $other.goIn( instant ); - } else { - $other.goOut( instant ); + /** + * jQuery plugin to fade or snap to visible state. + * + * @param {boolean} instantToggle [optional] + * @return {jQuery} + */ + $.fn.goIn = function ( instantToggle ) { + if ( instantToggle === true ) { + return $(this).show(); } - }); - -}); - + return $(this).stop( true, true ).fadeIn(); + }; + + /** + * jQuery plugin to fade or snap to hiding state. + * + * @param {boolean} instantToggle [optional] + * @return jQuery + */ + $.fn.goOut = function ( instantToggle ) { + if ( instantToggle === true ) { + return $(this).hide(); + } + return $(this).stop( true, true ).fadeOut(); + }; + + /** + * Bind a function to the jQuery object via live(), and also immediately trigger + * the function on the objects with an 'instant' parameter set to true. + * @param {Function} callback Takes one parameter, which is {true} when the + * event is called immediately, and {jQuery.Event} when triggered from an event. + */ + $.fn.liveAndTestAtStart = function ( callback ){ + $(this) + .live( 'change', callback ) + .each( function () { + callback.call( this, true ); + } ); + }; + + $( function () { + + // Animate the SelectOrOther fields, to only show the text field when + // 'other' is selected. + $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) { + var $other = $( '#' + $(this).attr( 'id' ) + '-other' ); + $other = $other.add( $other.siblings( 'br' ) ); + if ( $(this).val() === 'other' ) { + $other.goIn( instant ); + } else { + $other.goOut( instant ); + } + }); + + } ); }( jQuery ) ); diff --git a/resources/mediawiki/mediawiki.jqueryMsg.js b/resources/mediawiki/mediawiki.jqueryMsg.js index 86af31ff..183b525e 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/mediawiki/mediawiki.jqueryMsg.js @@ -5,13 +5,26 @@ * @author neilk@wikimedia.org */ ( function ( mw, $ ) { - var slice = Array.prototype.slice, + var oldParser, + slice = Array.prototype.slice, parserDefaults = { magic : { 'SITENAME' : mw.config.get( 'wgSiteName' ) }, messages : mw.messages, - language : mw.language + language : mw.language, + + // Same meaning as in mediawiki.js. + // + // Only 'text', 'parse', and 'escaped' are supported, and the + // actual escaping for 'escaped' is done by other code (generally + // through jqueryMsg). + // + // However, note that this default only + // applies to direct calls to jqueryMsg. The default for mediawiki.js itself + // is 'text', including when it uses jqueryMsg. + format: 'parse' + }; /** @@ -30,8 +43,8 @@ * @return {jQuery} */ return function ( args ) { - var key = args[0]; - var argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 ); + var key = args[0], + argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 ); try { return parser.parse( key, argsArray ); } catch ( e ) { @@ -56,19 +69,32 @@ * @return {Function} function suitable for assigning to window.gM */ mw.jqueryMsg.getMessageFunction = function ( options ) { - var failableParserFn = getFailableParserFn( options ); + var failableParserFn = getFailableParserFn( options ), + format; + + if ( options && options.format !== undefined ) { + format = options.format; + } else { + format = parserDefaults.format; + } + /** * 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]) * - * @param {String} message key - * @param {Array} optional replacements (can also specify variadically) - * @return {String} rendered HTML as string + * @param {string} key Message key. + * @param {Array|mixed} replacements Optional variable replacements (variadically or an array). + * @return {string} Rendered HTML. */ - return function ( /* key, replacements */ ) { - return failableParserFn( arguments ).html(); + return function () { + var failableResult = failableParserFn( arguments ); + if ( format === 'text' || format === 'escaped' ) { + return failableResult.text(); + } else { + return failableResult.html(); + } }; }; @@ -93,12 +119,14 @@ * 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) + * @param {string} key Message key. + * @param {Array|mixed} replacements Optional variable replacements (variadically or an array). * @return {jQuery} this */ - return function ( /* key, replacements */ ) { + return function () { var $target = this.empty(); + // TODO: Simply $target.append( failableParserFn( arguments ).contents() ) + // or Simply $target.append( failableParserFn( arguments ) ) $.each( failableParserFn( arguments ).contents(), function ( i, node ) { $target.append( node ); } ); @@ -113,20 +141,36 @@ */ mw.jqueryMsg.parser = function ( options ) { this.settings = $.extend( {}, parserDefaults, options ); + this.settings.onlyCurlyBraceTransform = ( this.settings.format === 'text' || this.settings.format === 'escaped' ); + 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). + /** + * Cache mapping MediaWiki message keys and the value onlyCurlyBraceTransform, 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). + * + * The two parts of the key are separated by colon. For example: + * + * "message-key:true": ast + * + * if they key is "message-key" and onlyCurlyBraceTransform is true. + * + * This cache is shared by all instances of mw.jqueryMsg.parser. + * + * @static + */ astCache: {}, /** * Where the magic happens. * Parses a message from the key, and swaps in replacements as necessary, wraps in jQuery * If an error is thrown, returns original key, and logs the error - * @param {String} message key - * @param {Array} replacements for $1, $2... $n + * @param {String} key Message key. + * @param {Array} replacements Variable replacements for $1, $2... $n * @return {jQuery} */ parse: function ( key, replacements ) { @@ -139,16 +183,19 @@ * @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 ) { - var wikiText = this.settings.messages.get( key ); + var cacheKey = [key, this.settings.onlyCurlyBraceTransform].join( ':' ), wikiText; + + if ( this.astCache[ cacheKey ] === undefined ) { + wikiText = this.settings.messages.get( key ); if ( typeof wikiText !== 'string' ) { - wikiText = "\\[" + key + "\\]"; + wikiText = '\\[' + key + '\\]'; } - this.astCache[ key ] = this.wikiTextToAst( wikiText ); + this.astCache[ cacheKey ] = this.wikiTextToAst( wikiText ); } - return this.astCache[ key ]; + return this.astCache[ cacheKey ]; }, - /* + + /** * 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. @@ -159,18 +206,27 @@ * @return {Mixed} abstract syntax tree */ wikiTextToAst: function ( input ) { + var pos, + regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, regularLiteralWithSquareBrackets, + backslash, anyCharacter, escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral, + whitespace, dollar, digits, + openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openLink, closeLink, templateName, pipe, colon, + templateContents, openTemplate, closeTemplate, + nonWhitespaceExpression, paramExpression, expression, curlyBraceTransformExpression, result; // Indicates current position in input as we parse through it. // Shared among all parsing functions below. - var pos = 0; + pos = 0; + // ========================================================= // parsing combinators - could be a library on its own // ========================================================= // Try parsers until one works, if none work return null function choice( ps ) { return function () { - for ( var i = 0; i < ps.length; i++ ) { - var result = ps[i](); + var i, result; + for ( i = 0; i < ps.length; i++ ) { + result = ps[i](); if ( result !== null ) { return result; } @@ -181,10 +237,11 @@ // 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++ ) { - var res = ps[i](); + var i, res, + originalPos = pos, + result = []; + for ( i = 0; i < ps.length; i++ ) { + res = ps[i](); if ( res === null ) { pos = originalPos; return null; @@ -197,9 +254,9 @@ // must succeed a minimum of n times or return null function nOrMore( n, p ) { return function () { - var originalPos = pos; - var result = []; - var parsed = p(); + var originalPos = pos, + result = [], + parsed = p(); while ( parsed !== null ) { result.push( parsed ); parsed = p(); @@ -258,11 +315,12 @@ // 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 backslash = makeStringParser( "\\" ); - var anyCharacter = makeRegexParser( /^./ ); + regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ ); + regularLiteralWithoutBar = makeRegexParser(/^[^{}\[\]$\\|]/); + regularLiteralWithoutSpace = makeRegexParser(/^[^{}\[\]$\s]/); + regularLiteralWithSquareBrackets = makeRegexParser( /^[^{}$\\]/ ); + backslash = makeStringParser( '\\' ); + anyCharacter = makeRegexParser( /^./ ); function escapedLiteral() { var result = sequence( [ backslash, @@ -270,36 +328,50 @@ ] ); return result === null ? null : result[1]; } - var escapedOrLiteralWithoutSpace = choice( [ + escapedOrLiteralWithoutSpace = choice( [ escapedLiteral, regularLiteralWithoutSpace ] ); - var escapedOrLiteralWithoutBar = choice( [ + escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] ); - var escapedOrRegularLiteral = choice( [ + 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(''); + 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 // it is not a literal in the parameter function literalWithoutBar() { - var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); - return result === null ? null : result.join(''); + var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); + return result === null ? null : result.join(''); } + + // Used for wikilink page names. Like literalWithoutBar, but + // without allowing escapes. + function unescapedLiteralWithoutBar() { + var result = nOrMore( 1, regularLiteralWithoutBar )(); + return result === null ? null : result.join(''); + } + function literal() { - var result = nOrMore( 1, escapedOrRegularLiteral )(); - return result === null ? null : result.join(''); + var result = nOrMore( 1, escapedOrRegularLiteral )(); + return result === null ? null : result.join(''); } - var whitespace = makeRegexParser( /^\s+/ ); - var dollar = makeStringParser( '$' ); - var digits = makeRegexParser( /^\d+/ ); + + function curlyBraceTransformExpressionLiteral() { + var result = nOrMore( 1, regularLiteralWithSquareBrackets )(); + return result === null ? null : result.join(''); + } + + whitespace = makeRegexParser( /^\s+/ ); + dollar = makeStringParser( '$' ); + digits = makeRegexParser( /^\d+/ ); function replacement() { var result = sequence( [ @@ -311,12 +383,13 @@ } return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ]; } - var openExtlink = makeStringParser( '[' ); - var closeExtlink = makeStringParser( ']' ); + openExtlink = makeStringParser( '[' ); + closeExtlink = makeStringParser( ']' ); // this extlink MUST have inner text, e.g. [foo] not allowed; [foo bar] is allowed function extlink() { - var result = null; - var parsedResult = sequence( [ + var result, parsedResult; + result = null; + parsedResult = sequence( [ openExtlink, nonWhitespaceExpression, whitespace, @@ -343,39 +416,73 @@ } return [ 'LINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ]; } - var openLink = makeStringParser( '[[' ); - var closeLink = makeStringParser( ']]' ); + openLink = makeStringParser( '[[' ); + closeLink = makeStringParser( ']]' ); + pipe = makeStringParser( '|' ); + + function template() { + var result = sequence( [ + openTemplate, + templateContents, + closeTemplate + ] ); + return result === null ? null : result[1]; + } + + wikilinkPage = choice( [ + unescapedLiteralWithoutBar, + template + ] ); + + function pipedWikilink() { + var result = sequence( [ + wikilinkPage, + pipe, + expression + ] ); + return result === null ? null : [ result[0], result[2] ]; + } + + wikilinkContents = choice( [ + pipedWikilink, + wikilinkPage // unpiped link + ] ); + function link() { - var result = null; - var parsedResult = sequence( [ + var result, parsedResult, parsedLinkContents; + result = null; + + parsedResult = sequence( [ openLink, - expression, + wikilinkContents, closeLink ] ); if ( parsedResult !== null ) { - result = [ 'WLINK', parsedResult[1] ]; + parsedLinkContents = parsedResult[1]; + result = [ 'WLINK' ].concat( parsedLinkContents ); } return result; } - var templateName = transform( + 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(); } ); function templateParam() { - var result = sequence( [ + var expr, result; + result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] ); if ( result === null ) { return null; } - var expr = result[1]; - // use a "CONCAT" operator if there are multiple nodes, otherwise return the first node, raw. - return expr.length > 1 ? [ "CONCAT" ].concat( expr ) : expr[0]; + expr = result[1]; + // 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, @@ -392,8 +499,8 @@ ] ); return result === null ? null : [ result[0], result[2] ]; } - var colon = makeStringParser(':'); - var templateContents = choice( [ + colon = makeStringParser(':'); + templateContents = choice( [ function () { var res = sequence( [ // templates can have placeholders for dynamic replacement eg: {{PLURAL:$1|one car|$1 cars}} @@ -414,17 +521,9 @@ return [ res[0] ].concat( res[1] ); } ] ); - var openTemplate = makeStringParser('{{'); - var closeTemplate = makeStringParser('}}'); - function template() { - var result = sequence( [ - openTemplate, - templateContents, - closeTemplate - ] ); - return result === null ? null : result[1]; - } - var nonWhitespaceExpression = choice( [ + openTemplate = makeStringParser('{{'); + closeTemplate = makeStringParser('}}'); + nonWhitespaceExpression = choice( [ template, link, extLinkParam, @@ -432,7 +531,7 @@ replacement, literalWithoutSpace ] ); - var paramExpression = choice( [ + paramExpression = choice( [ template, link, extLinkParam, @@ -440,7 +539,8 @@ replacement, literalWithoutBar ] ); - var expression = choice( [ + + expression = choice( [ template, link, extLinkParam, @@ -448,25 +548,42 @@ replacement, literal ] ); - function start() { - var result = nOrMore( 0, expression )(); + + // Used when only {{-transformation is wanted, for 'text' + // or 'escaped' formats + curlyBraceTransformExpression = choice( [ + template, + replacement, + curlyBraceTransformExpressionLiteral + ] ); + + + /** + * Starts the parse + * + * @param {Function} rootExpression root parse function + */ + function start( rootExpression ) { + var result = nOrMore( 0, rootExpression )(); if ( result === null ) { return null; } - return [ "CONCAT" ].concat( result ); + 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(); + + // If you add another possible rootExpression, you must update the astCache key scheme. + result = start( this.settings.onlyCurlyBraceTransform ? curlyBraceTransformExpression : expression ); /* * 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. */ - if (result === null || pos !== input.length) { - throw new Error( "Parse error at position " + pos.toString() + " in input: " + input ); + if ( result === null || pos !== input.length ) { + throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + input ); } return result; } @@ -491,18 +608,20 @@ * @return {Mixed} single-string node or array of nodes suitable for jQuery appending */ this.emit = function ( node, replacements ) { - var ret = null; - var jmsg = this; + var ret, subnodes, operation, + 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 ) { + // typeof returns object for arrays + case 'object': + // node is an array of nodes + subnodes = $.map( node.slice( 1 ), function ( n ) { return jmsg.emit( n, replacements ); } ); - var operation = node[0].toLowerCase(); + operation = node[0].toLowerCase(); if ( typeof jmsg[operation] === 'function' ) { ret = jmsg[ operation ]( subnodes, replacements ); } else { @@ -543,8 +662,9 @@ $span.append( childNode ); } ); } else { - // strings, integers, anything else - $span.append( node ); + // Let jQuery append nodes, arrays of nodes and jQuery objects + // other things (strings, numbers, ..) are appended as text nodes (not as HTML strings) + $span.append( $.type( node ) === 'object' ? node : document.createTextNode( node ) ); } } ); return $span; @@ -555,7 +675,7 @@ * 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" ) - * TODO throw error if nodes.length > 1 ? + * TODO: Throw error if nodes.length > 1 ? * @param {Array} of one element, integer, n >= 0 * @return {String} replacement */ @@ -563,13 +683,7 @@ var index = parseInt( nodes[0], 10 ); 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]; - } + return replacements[index]; } else { // index not found, fallback to displaying variable return '$' + ( index + 1 ); @@ -578,10 +692,41 @@ /** * Transform wiki-link - * TODO unimplemented + * + * TODO: + * It only handles basic cases, either no pipe, or a pipe with an explicit + * anchor. + * + * It does not attempt to handle features like the pipe trick. + * However, the pipe trick should usually not be present in wikitext retrieved + * from the server, since the replacement is done at save time. + * It may, though, if the wikitext appears in extension-controlled content. + * + * @param nodes */ wlink: function ( nodes ) { - return 'unimplemented'; + var page, anchor, url; + + page = nodes[0]; + url = mw.util.wikiGetlink( page ); + + // [[Some Page]] or [[Namespace:Some Page]] + if ( nodes.length === 1 ) { + anchor = page; + } + + /* + * [[Some Page|anchor text]] or + * [[Namespace:Some Page|anchor] + */ + else { + anchor = nodes[1]; + } + + return $( '
        ' ).attr( { + title: page, + href: url + } ).text( anchor ); }, /** @@ -594,9 +739,9 @@ * @return {jQuery} */ link: function ( nodes ) { - var arg = nodes[0]; - var contents = nodes[1]; - var $el; + var $el, + arg = nodes[0], + contents = nodes[1]; if ( arg instanceof jQuery ) { $el = arg; } else { @@ -639,25 +784,32 @@ * @return {String} selected pluralized form according to current language */ plural: function ( nodes ) { - var count = parseFloat( this.language.convertNumber( nodes[0], true ) ); - var forms = nodes.slice(1); + var forms, count; + count = parseFloat( this.language.convertNumber( nodes[0], true ) ); + forms = nodes.slice(1); return forms.length ? this.language.convertPlural( count, forms ) : ''; }, /** - * Transform parsed structure into gender - * Usage {{gender:[gender| mw.user object ] | masculine|feminine|neutral}}. - * @param {Array} of nodes, [ {String|mw.User}, {String}, {String} , {String} ] + * Transform parsed structure according to gender. + * Usage {{gender:[ gender | mw.user object ] | masculine form|feminine form|neutral form}}. + * The first node is either a string, which can be "male" or "female", + * or a User object (not a username). + * + * @param {Array} of nodes, [ {String|mw.User}, {String}, {String}, {String} ] * @return {String} selected gender form according to current language */ gender: function ( nodes ) { - var gender; - if ( nodes[0] && nodes[0].options instanceof mw.Map ){ + var gender, forms; + + if ( nodes[0] && nodes[0].options instanceof mw.Map ) { gender = nodes[0].options.get( 'gender' ); } else { gender = nodes[0]; } - var forms = nodes.slice(1); + + forms = nodes.slice( 1 ); + return this.language.gender( gender, forms ); }, @@ -668,9 +820,33 @@ * @return {String} selected grammatical form according to current language */ grammar: function ( nodes ) { - var form = nodes[0]; - var word = nodes[1]; + var form = nodes[0], + word = nodes[1]; return word && form && this.language.convertGrammar( word, form ); + }, + + /** + * Tranform parsed structure into a int: (interface language) message include + * Invoked by putting {{int:othermessage}} into a message + * @param {Array} of nodes + * @return {string} Other message + */ + int: function ( nodes ) { + return mw.jqueryMsg.getMessageFunction()( nodes[0].toLowerCase() ); + }, + + /** + * Takes an unformatted number (arab, no group separators and . as decimal separator) + * and outputs it in the localized digit script and formatted with decimal + * separator, according to the current language + * @param {Array} of nodes + * @return {Number|String} formatted number + */ + formatnum: function ( nodes ) { + var isInteger = ( nodes[1] && nodes[1] === 'R' ) ? true : false, + number = nodes[0]; + + return this.language.convertNumber( number, isInteger ); } }; // Deprecated! don't rely on gM existing. @@ -681,17 +857,24 @@ $.fn.msg = mw.jqueryMsg.getPlugin(); // Replace the default message parser with jqueryMsg - var oldParser = mw.Message.prototype.parser; + oldParser = mw.Message.prototype.parser; mw.Message.prototype.parser = function () { + var messageFunction; + // 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 ) { + if ( this.format === 'plain' || !/\{\{|\[/.test(this.map.get( this.key ) ) ) { // Fall back to mw.msg's simple parser return oldParser.apply( this ); } - var messageFunction = mw.jqueryMsg.getMessageFunction( { 'messages': this.map } ); + + messageFunction = mw.jqueryMsg.getMessageFunction( { + 'messages': this.map, + // For format 'escaped', escaping part is handled by mediawiki.js + 'format': this.format + } ); return messageFunction( this.key, this.parameters ); }; diff --git a/resources/mediawiki/mediawiki.jqueryMsg.peg b/resources/mediawiki/mediawiki.jqueryMsg.peg index e059ed1d..7879d6fa 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.peg +++ b/resources/mediawiki/mediawiki.jqueryMsg.peg @@ -37,6 +37,7 @@ templateParam templateName = tn:[A-Za-z_]+ { return tn.join('').toUpperCase() } +/* TODO: Update to reflect separate piped and unpiped handling */ link = "[[" w:expression "]]" { return [ 'WLINK', w ]; } diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index 19112aed..ca987543 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -1,9 +1,9 @@ /* * Core MediaWiki JavaScript Library */ -/*global mw:true */ + var mw = ( function ( $, undefined ) { - "use strict"; + 'use strict'; /* Private Members */ @@ -13,14 +13,13 @@ var mw = ( function ( $, undefined ) { /* Object constructors */ /** - * Map - * * Creates an object that can be read from or written to from prototype functions * that allow both single and multiple variables at once. + * @class mw.Map * - * @param global boolean Whether to store the values in the global window + * @constructor + * @param {boolean} global Whether to store the values in the global window * object or a exclusively in the object property 'values'. - * @return Map */ function Map( global ) { this.values = global === true ? window : {}; @@ -39,26 +38,26 @@ var mw = ( function ( $, undefined ) { * If selection was an array, returns an object of key/values (value is null if not found), * If selection was not passed or invalid, will return the 'values' object member (be careful as * objects are always passed by reference in JavaScript!). - * @return Values as a string or object, null if invalid/inexistant. + * @return {string|Object|null} Values as a string or object, null if invalid/inexistant. */ get: function ( selection, fallback ) { var results, i; + // If we only do this in the `return` block, it'll fail for the + // call to get() from the mutli-selection block. + fallback = arguments.length > 1 ? fallback : null; if ( $.isArray( selection ) ) { selection = slice.call( selection ); results = {}; - for ( i = 0; i < selection.length; i += 1 ) { + for ( i = 0; i < selection.length; i++ ) { results[selection[i]] = this.get( selection[i], fallback ); } return results; } if ( typeof selection === 'string' ) { - if ( this.values[selection] === undefined ) { - if ( fallback !== undefined ) { - return fallback; - } - return null; + if ( !hasOwn.call( this.values, selection ) ) { + return fallback; } return this.values[selection]; } @@ -87,7 +86,7 @@ var mw = ( function ( $, undefined ) { } return true; } - if ( typeof selection === 'string' && value !== undefined ) { + if ( typeof selection === 'string' && arguments.length > 1 ) { this.values[selection] = value; return true; } @@ -98,36 +97,35 @@ var mw = ( function ( $, undefined ) { * Checks if one or multiple keys exist. * * @param selection {mixed} String key or array of keys to check - * @return {Boolean} Existence of key(s) + * @return {boolean} Existence of key(s) */ exists: function ( selection ) { var s; if ( $.isArray( selection ) ) { - for ( s = 0; s < selection.length; s += 1 ) { - if ( this.values[selection[s]] === undefined ) { + for ( s = 0; s < selection.length; s++ ) { + if ( typeof selection[s] !== 'string' || !hasOwn.call( this.values, selection[s] ) ) { return false; } } return true; } - return this.values[selection] !== undefined; + return typeof selection === 'string' && hasOwn.call( this.values, selection ); } }; /** - * Message - * * Object constructor for messages, * similar to the Message class in MediaWiki PHP. + * @class mw.Message * - * @param map Map Instance of mw.Map - * @param key String - * @param parameters Array - * @return Message + * @constructor + * @param {mw.Map} map Message storage + * @param {string} key + * @param {Array} [parameters] */ function Message( map, key, parameters ) { - this.format = 'plain'; + this.format = 'text'; this.map = map; this.key = key; this.parameters = parameters === undefined ? [] : slice.call( parameters ); @@ -136,9 +134,13 @@ var mw = ( function ( $, undefined ) { Message.prototype = { /** - * Simple message parser, does $N replacement and nothing else. + * Simple message parser, does $N replacement, HTML-escaping (only for + * 'escaped' format), and nothing else. + * * This may be overridden to provide a more complex message parser. * + * The primary override is in mediawiki.jqueryMsg. + * * This function will not be called for nonexistent messages. */ parser: function () { @@ -152,8 +154,8 @@ var mw = ( function ( $, undefined ) { /** * Appends (does not replace) parameters for replacement to the .parameters property. * - * @param parameters Array - * @return Message + * @param {Array} parameters + * @chainable */ params: function ( parameters ) { var i; @@ -166,25 +168,21 @@ var mw = ( function ( $, undefined ) { /** * Converts message object to it's string form based on the state of format. * - * @return string Message as a string in the current form or if key does not exist. + * @return {string} Message as a string in the current form or `` if key does not exist. */ toString: function () { var text; if ( !this.exists() ) { // Use as text if key does not exist - if ( this.format !== 'plain' ) { - // format 'escape' and 'parse' need to have the brackets and key html escaped + if ( this.format === 'escaped' || this.format === 'parse' ) { + // format 'escaped' and 'parse' need to have the brackets and key html escaped return mw.html.escape( '<' + this.key + '>' ); } return '<' + this.key + '>'; } - if ( this.format === 'plain' ) { - // @todo FIXME: Although not applicable to core Message, - // Plugins like jQueryMsg should be able to distinguish - // between 'plain' (only variable replacement and plural/gender) - // and actually parsing wikitext to HTML. + if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) { text = this.parser(); } @@ -193,15 +191,16 @@ var mw = ( function ( $, undefined ) { text = mw.html.escape( text ); } - if ( this.format === 'parse' ) { - text = this.parser(); - } - return text; }, /** - * Changes format to parse and converts message to string + * Changes format to 'parse' and converts message to string + * + * If jqueryMsg is loaded, this parses the message text from wikitext + * (where supported) to HTML + * + * Otherwise, it is equivalent to plain. * * @return {string} String form of parsed message */ @@ -211,7 +210,10 @@ var mw = ( function ( $, undefined ) { }, /** - * Changes format to plain and converts message to string + * Changes format to 'plain' and converts message to string + * + * This substitutes parameters, but otherwise does not change the + * message text. * * @return {string} String form of plain message */ @@ -221,7 +223,23 @@ var mw = ( function ( $, undefined ) { }, /** - * Changes the format to html escaped and converts message to string + * Changes format to 'text' and converts message to string + * + * If jqueryMsg is loaded, {{-transformation is done where supported + * (such as {{plural:}}, {{gender:}}, {{int:}}). + * + * Otherwise, it is equivalent to plain. + */ + text: function () { + this.format = 'text'; + return this.toString(); + }, + + /** + * Changes the format to 'escaped' and converts message to string + * + * This is equivalent to using the 'text' format (see text method), then + * HTML-escaping the output. * * @return {string} String form of html escaped message */ @@ -233,13 +251,19 @@ var mw = ( function ( $, undefined ) { /** * Checks if message exists * - * @return {string} String form of parsed message + * @see mw.Map#exists + * @return {boolean} */ exists: function () { return this.map.exists( this.key ); } }; + /** + * @class mw + * @alternateClassName mediaWiki + * @singleton + */ return { /* Public Members */ @@ -249,77 +273,72 @@ var mw = ( function ( $, undefined ) { */ log: function () { }, - /** - * @var constructor Make the Map constructor publicly available. - */ + // Make the Map constructor publicly available. Map: Map, - /** - * @var constructor Make the Message constructor publicly available. - */ + // Make the Message constructor publicly available. Message: Message, /** * List of configuration values * * Dummy placeholder. Initiated in startUp module as a new instance of mw.Map(). - * If $wgLegacyJavaScriptGlobals is true, this Map will have its values + * If `$wgLegacyJavaScriptGlobals` is true, this Map will have its values * in the global window object. + * @property */ config: null, /** - * @var object - * * Empty object that plugins can be installed in. + * @property */ libs: {}, /* Extension points */ + /** + * @property + */ legacy: {}, /** * Localization system + * @property {mw.Map} */ messages: new Map(), /* Public Methods */ /** - * Gets a message object, similar to wfMessage() + * Gets a message object, similar to wfMessage(). * - * @param key string Key of message to get - * @param parameter_1 mixed First argument in a list of variadic arguments, - * each a parameter for $N replacement in messages. - * @return Message + * @param {string} key Key of message to get + * @param {Mixed...} parameters Parameters for the $N replacements in messages. + * @return {mw.Message} */ - message: function ( key, parameter_1 /* [, parameter_2] */ ) { - var parameters; - // Support variadic arguments - if ( parameter_1 !== undefined ) { - parameters = slice.call( arguments ); - parameters.shift(); - } else { - parameters = []; - } + message: function ( key ) { + // Variadic arguments + var parameters = slice.call( arguments, 1 ); return new Message( mw.messages, key, parameters ); }, /** * 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. + * @see mw.Message#toString + * @param {string} key Key of message to get + * @param {Mixed...} parameters Parameters for the $N replacements in messages. + * @return {string} */ - msg: function ( /* key, parameter_1, parameter_2, .. */ ) { + msg: function ( /* key, parameters... */ ) { return mw.message.apply( mw.message, arguments ).toString(); }, /** * Client-side module loader which integrates with the MediaWiki ResourceLoader + * @class mw.loader + * @singleton */ loader: ( function () { @@ -338,29 +357,32 @@ var mw = ( function ( $, undefined ) { * mw.loader.implement. * * Format: - * { - * 'moduleName': { - * 'version': ############## (unix timestamp), - * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {} - * 'group': 'somegroup', (or) null, - * 'source': 'local', 'someforeignwiki', (or) null - * 'state': 'registered', 'loading', 'loaded', 'ready', 'error' or 'missing' - * 'script': ..., - * 'style': ..., - * 'messages': { 'key': 'value' }, - * } - * } + * { + * 'moduleName': { + * 'version': ############## (unix timestamp), + * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {} + * 'group': 'somegroup', (or) null, + * 'source': 'local', 'someforeignwiki', (or) null + * 'state': 'registered', 'loaded', 'loading', 'ready', 'error' or 'missing' + * 'script': ..., + * 'style': ..., + * 'messages': { 'key': 'value' }, + * } + * } + * + * @property + * @private */ var registry = {}, - /** - * Mapping of sources, keyed by source-id, values are objects. - * Format: - * { - * 'sourceId': { - * 'loadScript': 'http://foo.bar/w/load.php' - * } - * } - */ + // + // Mapping of sources, keyed by source-id, values are objects. + // Format: + // { + // 'sourceId': { + // 'loadScript': 'http://foo.bar/w/load.php' + // } + // } + // sources = {}, // List of modules which will be loaded as when ready batch = [], @@ -369,7 +391,11 @@ var mw = ( function ( $, undefined ) { // List of callback functions waiting for modules to be ready to be called jobs = [], // Selector cache for the marker element. Use getMarker() to get/use the marker! - $marker = null; + $marker = null, + // Buffer for addEmbeddedCSS. + cssBuffer = '', + // Callbacks for addEmbeddedCSS. + cssCallbacks = $.Callbacks(); /* Private methods */ @@ -392,10 +418,11 @@ var mw = ( function ( $, undefined ) { /** * 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 . - * @return HTMLStyleElement + * @private + * @param {string} text CSS text + * @param {Mixed} [nextnode] An Element or jQuery object for an element where + * the style tag should be inserted before. Otherwise appended to the ``. + * @return {HTMLElement} Node reference to the created `