summaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
Diffstat (limited to 'resources')
-rw-r--r--resources/Resources.php118
-rw-r--r--resources/Resources.php.orig968
-rw-r--r--resources/jquery.effects/jquery.effects.blind.js2
-rw-r--r--resources/jquery.effects/jquery.effects.bounce.js2
-rw-r--r--resources/jquery.effects/jquery.effects.clip.js2
-rw-r--r--resources/jquery.effects/jquery.effects.core.js4
-rw-r--r--resources/jquery.effects/jquery.effects.drop.js2
-rw-r--r--resources/jquery.effects/jquery.effects.explode.js2
-rw-r--r--resources/jquery.effects/jquery.effects.fade.js2
-rw-r--r--resources/jquery.effects/jquery.effects.fold.js2
-rw-r--r--resources/jquery.effects/jquery.effects.highlight.js2
-rw-r--r--resources/jquery.effects/jquery.effects.pulsate.js2
-rw-r--r--resources/jquery.effects/jquery.effects.scale.js2
-rw-r--r--resources/jquery.effects/jquery.effects.shake.js2
-rw-r--r--resources/jquery.effects/jquery.effects.slide.js2
-rw-r--r--resources/jquery.effects/jquery.effects.transfer.js2
-rw-r--r--resources/jquery.ui/i18n/jquery.ui.datepicker-zh-CN.js4
-rw-r--r--resources/jquery.ui/i18n/jquery.ui.datepicker-zh-HK.js4
-rw-r--r--resources/jquery.ui/i18n/jquery.ui.datepicker-zh-TW.js4
-rw-r--r--resources/jquery.ui/jquery.ui.accordion.js4
-rw-r--r--resources/jquery.ui/jquery.ui.autocomplete.js2
-rw-r--r--resources/jquery.ui/jquery.ui.button.js2
-rw-r--r--resources/jquery.ui/jquery.ui.core.js4
-rw-r--r--resources/jquery.ui/jquery.ui.datepicker.js6
-rw-r--r--resources/jquery.ui/jquery.ui.dialog.js4
-rw-r--r--resources/jquery.ui/jquery.ui.draggable.js13
-rw-r--r--resources/jquery.ui/jquery.ui.droppable.js11
-rw-r--r--resources/jquery.ui/jquery.ui.mouse.js2
-rw-r--r--resources/jquery.ui/jquery.ui.position.js2
-rw-r--r--resources/jquery.ui/jquery.ui.progressbar.js4
-rw-r--r--resources/jquery.ui/jquery.ui.resizable.js4
-rw-r--r--resources/jquery.ui/jquery.ui.selectable.js4
-rw-r--r--resources/jquery.ui/jquery.ui.slider.js4
-rw-r--r--resources/jquery.ui/jquery.ui.sortable.js34
-rw-r--r--resources/jquery.ui/jquery.ui.tabs.js4
-rw-r--r--resources/jquery.ui/jquery.ui.widget.js2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.accordion.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.autocomplete.css4
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.button.css114
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.core.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.datepicker.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.dialog.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.progressbar.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.resizable.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.selectable.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.slider.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.tabs.css2
-rw-r--r--resources/jquery.ui/themes/default/jquery.ui.theme.css2
-rw-r--r--resources/jquery.ui/themes/vector/jquery.ui.button.css148
-rw-r--r--resources/jquery/jquery.arrowSteps.js11
-rw-r--r--resources/jquery/jquery.badge.css37
-rw-r--r--resources/jquery/jquery.badge.js121
-rw-r--r--resources/jquery/jquery.byteLimit.js5
-rw-r--r--resources/jquery/jquery.checkboxShiftClick.js10
-rw-r--r--resources/jquery/jquery.client.js152
-rw-r--r--resources/jquery/jquery.collapsibleTabs.js126
-rw-r--r--resources/jquery/jquery.colorUtil.js21
-rw-r--r--resources/jquery/jquery.delayedBind.js6
-rw-r--r--resources/jquery/jquery.expandableField.js8
-rw-r--r--resources/jquery/jquery.hidpi.js117
-rw-r--r--resources/jquery/jquery.highlightText.js2
-rw-r--r--resources/jquery/jquery.jStorage.js853
-rw-r--r--resources/jquery/jquery.js206
-rw-r--r--resources/jquery/jquery.json.js250
-rw-r--r--resources/jquery/jquery.localize.js59
-rw-r--r--resources/jquery/jquery.makeCollapsible.js621
-rw-r--r--resources/jquery/jquery.mw-jump.js10
-rw-r--r--resources/jquery/jquery.mwExtension.js5
-rw-r--r--resources/jquery/jquery.qunit.completenessTest.js4
-rw-r--r--resources/jquery/jquery.qunit.css15
-rw-r--r--resources/jquery/jquery.qunit.js493
-rw-r--r--resources/jquery/jquery.spinner.js2
-rw-r--r--resources/jquery/jquery.suggestions.js207
-rw-r--r--resources/jquery/jquery.tablesorter.js371
-rw-r--r--resources/jquery/jquery.textSelection.js111
-rw-r--r--resources/mediawiki.action/mediawiki.action.edit.js2
-rw-r--r--resources/mediawiki.action/mediawiki.action.edit.preview.js6
-rw-r--r--resources/mediawiki.action/mediawiki.action.history.diff.css80
-rw-r--r--resources/mediawiki.action/mediawiki.action.history.js2
-rw-r--r--resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js14
-rw-r--r--resources/mediawiki.action/mediawiki.action.view.postEdit.js15
-rw-r--r--resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js14
-rw-r--r--resources/mediawiki.api/mediawiki.api.category.js174
-rw-r--r--resources/mediawiki.api/mediawiki.api.edit.js85
-rw-r--r--resources/mediawiki.api/mediawiki.api.js73
-rw-r--r--resources/mediawiki.api/mediawiki.api.parse.js39
-rw-r--r--resources/mediawiki.api/mediawiki.api.titleblacklist.js52
-rw-r--r--resources/mediawiki.api/mediawiki.api.watch.js68
-rw-r--r--resources/mediawiki.language/languages/bs.js6
-rw-r--r--resources/mediawiki.language/languages/dsb.js6
-rw-r--r--resources/mediawiki.language/languages/fi.js15
-rw-r--r--resources/mediawiki.language/languages/ga.js7
-rw-r--r--resources/mediawiki.language/languages/he.js14
-rw-r--r--resources/mediawiki.language/languages/hsb.js6
-rw-r--r--resources/mediawiki.language/languages/hu.js6
-rw-r--r--resources/mediawiki.language/languages/hy.js16
-rw-r--r--resources/mediawiki.language/languages/la.js6
-rw-r--r--resources/mediawiki.language/languages/os.js29
-rw-r--r--resources/mediawiki.language/languages/ru.js46
-rw-r--r--resources/mediawiki.language/languages/sl.js6
-rw-r--r--resources/mediawiki.language/languages/uk.js28
-rw-r--r--resources/mediawiki.language/mediawiki.cldr.js23
-rw-r--r--resources/mediawiki.language/mediawiki.language.init.js4
-rw-r--r--resources/mediawiki.language/mediawiki.language.js51
-rw-r--r--resources/mediawiki.language/mediawiki.language.numbers.js243
-rw-r--r--resources/mediawiki.libs/CLDRPluralRuleParser.js1
-rw-r--r--resources/mediawiki.page/mediawiki.page.patrol.ajax.js63
-rw-r--r--resources/mediawiki.page/mediawiki.page.ready.js40
-rw-r--r--resources/mediawiki.page/mediawiki.page.watch.ajax.js17
-rw-r--r--resources/mediawiki.special/mediawiki.special.block.js78
-rw-r--r--resources/mediawiki.special/mediawiki.special.changeemail.js62
-rw-r--r--resources/mediawiki.special/mediawiki.special.changeslist.css11
-rw-r--r--resources/mediawiki.special/mediawiki.special.javaScriptTest.js2
-rw-r--r--resources/mediawiki.special/mediawiki.special.js6
-rw-r--r--resources/mediawiki.special/mediawiki.special.movePage.js9
-rw-r--r--resources/mediawiki.special/mediawiki.special.preferences.js336
-rw-r--r--resources/mediawiki.special/mediawiki.special.recentchanges.js31
-rw-r--r--resources/mediawiki.special/mediawiki.special.search.js90
-rw-r--r--resources/mediawiki.special/mediawiki.special.undelete.js13
-rw-r--r--resources/mediawiki.special/mediawiki.special.upload.js91
-rw-r--r--resources/mediawiki.special/mediawiki.special.userLogin.signup.js10
-rw-r--r--resources/mediawiki/mediawiki.Title.js103
-rw-r--r--resources/mediawiki/mediawiki.Uri.js64
-rw-r--r--resources/mediawiki/mediawiki.debug.css1
-rw-r--r--resources/mediawiki/mediawiki.debug.js6
-rw-r--r--resources/mediawiki/mediawiki.feedback.js45
-rw-r--r--resources/mediawiki/mediawiki.hidpi.js5
-rw-r--r--resources/mediawiki/mediawiki.htmlform.js112
-rw-r--r--resources/mediawiki/mediawiki.jqueryMsg.js423
-rw-r--r--resources/mediawiki/mediawiki.jqueryMsg.peg1
-rw-r--r--resources/mediawiki/mediawiki.js699
-rw-r--r--resources/mediawiki/mediawiki.log.js2
-rw-r--r--resources/mediawiki/mediawiki.notification.js145
-rw-r--r--resources/mediawiki/mediawiki.notify.js13
-rw-r--r--resources/mediawiki/mediawiki.searchSuggest.css16
-rw-r--r--resources/mediawiki/mediawiki.searchSuggest.js110
-rw-r--r--resources/mediawiki/mediawiki.user.js25
-rw-r--r--resources/mediawiki/mediawiki.util.js236
-rw-r--r--resources/startup.js23
139 files changed, 6236 insertions, 3132 deletions
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 @@
<?php
+/**
+ * Definition of core ResourceLoader modules.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if( !defined( 'MEDIAWIKI' ) ) {
+ die( 'Not an entry point.' );
+}
return array(
- /* Special modules who have their own classes */
+ /**
+ * Special modules who have their own classes
+ */
// Scripts managed by the local wiki (stored in the MediaWiki namespace)
'site' => 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 @@
+<?php
+
+return array(
+
+ /* Special modules who have their own classes */
+
+ // Scripts managed by the local wiki (stored in the MediaWiki namespace)
+ 'site' => array( 'class' => 'ResourceLoaderSiteModule' ),
+ 'noscript' => array( 'class' => 'ResourceLoaderNoscriptModule' ),
+ 'startup' => array( 'class' => 'ResourceLoaderStartUpModule' ),
+ '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 = $( '<div>' )
- .addClass( 'mw-badge' )
- .append(
- $( '<span>' )
- .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 = $( '<div class="mw-badge"></div>' )
+ .addClass( badgeStyleClass )
+ .toggleClass( 'mw-badge-important', isImportant )
+ .append(
+ $( '<span class="mw-badge-content"></span>' ).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 <krinklemail@gmail.com>
+ * @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
- *
- * <value> 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<len; i++){
+ if(_observers[keys[i]]){
+ for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
+ _observers[keys[i]][j](keys[i], action);
+ }
+ }
+ }
+ }
+
+ /**
+ * Publishes key change to listeners
+ */
+ function _publishChange(){
+ var updateTime = (+new Date()).toString();
+
+ if(_backend == "localStorage" || _backend == "globalStorage"){
+ _storage_service.jStorage_update = updateTime;
+ }else if(_backend == "userDataBehavior"){
+ _storage_elm.setAttribute("jStorage_update", updateTime);
+ _storage_elm.save("jStorage");
+ }
+
+ _storageObserver();
}
/**
* Loads the data from the storage based on the supported mechanism
- * @returns undefined
*/
function _load_storage(){
/* if jStorage string is retrieved, then decode it */
if(_storage_service.jStorage){
try{
- _storage = json_decode(String(_storage_service.jStorage));
+ _storage = JSON.parse(String(_storage_service.jStorage));
}catch(E6){_storage_service.jStorage = "{}";}
}else{
_storage_service.jStorage = "{}";
}
_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
+
+ if(!_storage.__jstorage_meta){
+ _storage.__jstorage_meta = {};
+ }
+ if(!_storage.__jstorage_meta.CRC32){
+ _storage.__jstorage_meta.CRC32 = {};
+ }
}
/**
* This functions provides the "save" mechanism to store the jStorage object
- * @returns undefined
*/
function _save(){
+ _dropOldEvents(); // remove expired events
try{
- _storage_service.jStorage = json_encode(_storage);
+ _storage_service.jStorage = JSON.stringify(_storage);
// If userData is used as the storage engine, additional
if(_storage_elm) {
_storage_elm.setAttribute("jStorage",_storage_service.jStorage);
@@ -261,12 +684,14 @@
/**
* Function checks if a key is set and is string or numberic
+ *
+ * @param {String} key Key name
*/
function _checkKey(key){
- if(!key || (typeof key !== "string" && typeof key !== "number")){
+ if(!key || (typeof key != "string" && typeof key != "number")){
throw new TypeError('Key name must be string or numeric');
}
- if(key === "__jstorage_meta"){
+ if(key == "__jstorage_meta"){
throw new TypeError('Reserved key name');
}
return true;
@@ -276,23 +701,27 @@
* Removes expired keys
*/
function _handleTTL(){
- var curtime, i, TTL, nextExpire = Infinity, changed = false;
+ var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];
clearTimeout(_ttl_timeout);
- if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL !== "object"){
+ if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
// nothing to do here
return;
}
curtime = +new Date();
TTL = _storage.__jstorage_meta.TTL;
+
+ CRC32 = _storage.__jstorage_meta.CRC32;
for(i in TTL){
if(TTL.hasOwnProperty(i)){
if(TTL[i] <= curtime){
delete TTL[i];
+ delete CRC32[i];
delete _storage[i];
changed = true;
+ deleted.push(i);
}else if(TTL[i] < nextExpire){
nextExpire = TTL[i];
}
@@ -307,47 +736,158 @@
// save changes
if(changed){
_save();
+ _publishChange();
+ _fireObservers(deleted, "deleted");
}
}
+ /**
+ * Checks if there's any events on hold to be fired to listeners
+ */
+ function _handlePubSub(){
+ if(!_storage.__jstorage_meta.PubSub){
+ return;
+ }
+ var pubelm,
+ _pubsubCurrent = _pubsub_last;
+
+ for(var i=len=_storage.__jstorage_meta.PubSub.length-1; 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<len; i++){
+ // send immutable data that can't be modified by listeners
+ _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
+ }
+ }
+ }
+
+ /**
+ * Remove old events from the publish stream (at least 2sec old)
+ */
+ function _dropOldEvents(){
+ if(!_storage.__jstorage_meta.PubSub){
+ return;
+ }
+
+ var retire = +new Date() - 2000;
+
+ for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
+ if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
+ // deleteCount is needed for IE6
+ _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
+ break;
+ }
+ }
+
+ if(!_storage.__jstorage_meta.PubSub.length){
+ delete _storage.__jstorage_meta.PubSub;
+ }
+
+ }
+
+ /**
+ * Publish payload to a channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to send to the subscribers
+ */
+ function _publish(channel, payload){
+ if(!_storage.__jstorage_meta){
+ _storage.__jstorage_meta = {};
+ }
+ if(!_storage.__jstorage_meta.PubSub){
+ _storage.__jstorage_meta.PubSub = [];
+ }
+
+ _storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);
+
+ _save();
+ _publishChange();
+ }
+
+ /**
+ * CRC32 calculation based on http://noteslog.com/post/crc32-for-javascript/
+ *
+ * @param {String} str String to be hashed
+ * @param {Number} [crc] Last crc value in case of streams
+ */
+ function _crc32(str, crc){
+ crc = crc || 0;
+
+ var n = 0, //a number between 0 and 255
+ x = 0; //an hex number
+
+ crc = crc ^ (-1);
+ for(var i = 0, len = str.length; i < len; i++){
+ n = (crc ^ str.charCodeAt(i)) & 0xFF;
+ x = "0x" + _crc32Table.substr(n * 9, 8);
+ crc = (crc >>> 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 <null>
+ * @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 = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+ // 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 <http://www.opensource.org/licenses/mit-license.php>
*/
+(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 <html:msg /> elements with localized text and adding
+ * localized title and alt attributes to elements with title-msg and alt-msg attributes
+ * respectively.
*
- * Call on a selection of HTML which contains <html:msg key="message-key" /> elements or elements
+ * Call on a selection of HTML which contains `<html:msg key="message-key" />` elements or elements
* with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes.
- * <html:msg /> elements will be replaced with localized text, *-msg attributes will be replaced
+ * `<html:msg />` elements will be replaced with localized text, *-msg attributes will be replaced
* with attributes that do not have the "-msg" suffix and contain a localized message.
*
* Example:
@@ -77,34 +99,12 @@
* Appends something like this to the body...
* <p>You may not get there all in one piece.</p>
*
- */
-( function ( $, mw ) {
-
-/**
- * Gets a localized message, using parameters from options if present.
- *
- * @function
- * @param {String} key Message key to get localized message for
- * @returns {String} Localized message
- */
-function msg( options, key ) {
- var args = options.params[key] || [];
- // Format: mw.msg( key [, p1, p2, ...] )
- args.unshift( options.prefix + ( options.keys[key] || key ) );
- return mw.msg.apply( mw, args );
-}
-
-/**
- * Localizes a DOM selection by replacing <html:msg /> elements with localized text and adding
- * localized title and alt attributes to elements with title-msg and alt-msg attributes
- * respectively.
- *
* @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 <krinklemail@gmail.com>
+ * @author Krinkle, 2011-2012
*
* Dual license:
* @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0>
* @license GPL2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
*/
( function ( $, 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 { // <div>, <p> 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: <div>, <p> 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 { // <div>, <p> 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 (&nbsp;[text]&nbsp;)
- var $toggleLink =
- $( '<a href="#"></a>' )
- .text( collapsetext )
- .wrap( '<span class="mw-collapsible-toggle"></span>' )
- .parent()
- .prepend( '&nbsp;[' )
- .append( ']&nbsp;' )
- .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 (&nbsp;[text]&nbsp;)
+ $toggleLink =
+ $( '<a href="#"></a>' )
+ .text( collapsetext )
+ .wrap( '<span class="mw-collapsible-toggle"></span>' )
+ .parent()
+ .prepend( '&nbsp;[' )
+ .append( ']&nbsp;' )
+ .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( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
- } else {
- $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
- toggleLinkPremade( $toggle, e );
- } );
- }
- } else { // <div>, <p> 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( '<li class="mw-collapsible-toggle-li"></li>' ).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 { // <div>, <p> etc.
- // If a direct child .content-wrapper does not exists, create it
- if ( !$that.find( '> .mw-collapsible-content' ).length ) {
- $that.wrapInner( '<div class="mw-collapsible-content"></div>' );
- }
+ // 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( '<div class="mw-collapsible-content"></div>' );
+ }
+
+ // 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: <br/>" + this.name;
+ running.innerHTML = "Running: <br/>" + 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 + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+ b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
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 = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
+ nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
if ( arguments.length === 2 ) {
callback = expected;
@@ -329,11 +398,11 @@ QUnit = {
}
if ( config.currentModule ) {
- name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
+ nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + 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 = "<span class='test-message'>" + msg + "</span>";
if ( !result ) {
source = sourceFromStacktrace( 2 );
if ( source ) {
details.source = source;
- msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
}
}
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,10 +548,31 @@ 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 =
- "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
"<h2 id='qunit-userAgent'></h2>" +
@@ -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 = "<span class='test-message'>" + message + "</span>";
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 += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
- if ( actual != expected ) {
+ if ( actual !== expected ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
}
@@ -812,7 +927,7 @@ extend( QUnit, {
if ( source ) {
details.source = source;
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
@@ -839,19 +954,19 @@ extend( QUnit, {
message: message
};
- message = escapeInnerText( message ) || "error";
+ message = escapeText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>";
output = message;
output += "<table>";
if ( actual ) {
- output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
}
if ( source ) {
details.source = source;
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
@@ -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 += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
+ "' name='" + escapeText( val.id ) +
+ "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
+ " title='" + escapeText( val.tooltip ) +
+ "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
+ "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
}
- moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+ ( config.module === undefined ? "selected='selected'" : "" ) +
+ ">< All Modules ></option>";
+
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
- moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
+ moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " +
+ ( config.module === i ? "selected='selected'" : "" ) +
+ ">" + escapeText(i) + "</option>";
}
}
moduleFilterHtml += "</select>";
@@ -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.<br/>",
"<span class='passed'>",
passed,
- "</span> tests of <span class='total'>",
+ "</span> assertions of <span class='total'>",
config.stats.all,
"</span> passed, <span class='failed'>",
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 "&amp;";
- case "<": return "&lt;";
- case ">": return "&gt;";
- default: return s;
+ case '\'':
+ return '&#039;';
+ case '"':
+ return '&quot;';
+ case '<':
+ return '&lt;';
+ case '>':
+ return '&gt;';
+ case '&':
+ return '&amp;';
}
});
}
@@ -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 ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
},
- 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 ? "&lt;" : "<",
close = QUnit.jsDump.HTML ? "&gt;" : ">",
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 <del>brown </del> fox <del>jumped </del><ins>jumps </ins> 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 = $( '<div>' )
+ text = context.config.suggestions[i];
+ $result = $( '<div>' )
.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 <span> with text
- if( context.config.highlightInput ) {
- matchedText = context.data.prevText;
- }
$result.append( $( '<span>' )
.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 <tr> 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(
$( '<div>' ).addClass( 'suggestions-results' )
- // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
- // listen for a mousedown followed by a mouseup on the same div
+ // 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(
$( '<div>' ).addClass( 'suggestions-special' )
- // Can't use click() because the container div is hidden when the textbox loses focus. Instead,
- // listen for a mousedown followed by a mouseup on the same div
+ // 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:
+ * { <Integer column index>: <String 'asc' or 'desc'> }
+ * 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 <table>
+ */
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 <tfoot> at the end of the <table>
+ 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( $( '<tfoot>' ).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 <tfoot> at the end of the <table>
- 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( $( '<tfoot>' ).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
- * <code>
- * var api = new mw.Api();
- * api.get( {
- * action: 'query',
- * meta: 'userinfo'
- * }, {
- * ok: function () { console.log( arguments ); }
- * } );
- * </code>
+ * 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];
@@ -73,43 +75,6 @@ var language = {
},
/**
- * 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}}.
* If second or third parameter are not specified, masculine is used.
@@ -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 <hoo@online.de>
+ */
+( 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( '<label for="wpNewEmail" id="mw-emailaddress-validity"></label>' );
- }
- 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( '<label for="wpNewEmail" id="mw-emailaddress-validity"></label>' );
+ }
+
+ 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 = $( '<p><label for="useskin">'
+ var $html = $( '<p><label for="useskin">'
+ mw.message( 'javascripttest-pagetext-skins' ).escaped()
+ ' '
+ '</label></p>' ),
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 = $('<ul id="preftoc"></ul>');
-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 = $('<ul id="preftoc"></ul>');
+ $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 = $( '<li>' )
+ .addClass( i === 0 ? 'selected' : '' );
+ $a = $( '<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
+ // <a href="#.."> will naturally set the hash, handled by onhashchange.
+ // But other things that change the hash will also be catched (e.g. using
+ // the Back and Forward browser navigation).
+ // 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 = $( '<li/>', {
- 'class' : ( i === 0 ) ? 'selected' : null
- });
- var $a = $( '<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
-// <a href="#.."> will naturally set the hash, handled by onhashchange.
-// But other things that change the hash will also be catched (e.g. using
-// the Back and Forward browser navigation).
-if ( 'onhashchange' in window ) {
- $(window).on( 'hashchange' , function () {
- var hash = window.location.hash;
- if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) {
- switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
- } else if ( hash === '' ) {
- switchPrefTab( 'personal', 'noHash' );
+ 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(
- $('<label />')
- .text(mw.msg('powersearch-togglelabel'))
-).append(
- $('<input type="button" />')
- .attr('id', 'mw-search-toggleall')
- .attr('value', mw.msg('powersearch-toggleall'))
- .click( function() {
- $checkboxes.prop('checked', true);
- } )
-).append(
- $('<input type="button" />')
- .attr('id', 'mw-search-togglenone')
- .attr('value', mw.msg('powersearch-togglenone'))
- .click( function() {
- $checkboxes.prop('checked', false);
- } )
-);
+ // Create check all/none button
+ $checkboxes = $('#powersearch input[id^=mw-search-ns]');
+ $('#mw-search-togglebox').append(
+ $('<label>')
+ .text(mw.msg('powersearch-togglelabel'))
+ ).append(
+ $('<input type="button" />')
+ .attr( 'id', 'mw-search-toggleall' )
+ .prop( 'value', mw.msg('powersearch-toggleall' ) )
+ .click( function () {
+ $checkboxes.prop('checked', true);
+ } )
+ ).append(
+ $('<input type="button" />')
+ .attr( 'id', 'mw-search-togglenone' )
+ .prop( 'value', mw.msg('powersearch-togglenone' ) )
+ .click( function() {
+ $checkboxes.prop( 'checked', false );
+ } )
+ );
-// Change the header search links to what user entered
-var headerLinks = $('.search-types a');
-$('#searchText, #powerSearchText').change(function() {
- var searchterm = $(this).val();
- headerLinks.each( function() {
- var parts = $(this).attr('href').split( 'search=' );
- var lastpart = '';
- var prefix = 'search=';
- if( parts.length > 1 && parts[1].indexOf('&') >= 0 ) {
- lastpart = parts[1].substring( parts[1].indexOf('&') );
- } else {
- prefix = '&search=';
- }
- this.href = parts[0] + prefix + encodeURIComponent( searchterm ) + lastpart;
- });
-}).trigger('change');
+ // Change the header search links to what user entered
+ $headerLinks = $( '.search-types a' );
+ $( '#searchText, #powerSearchText' ).change( function () {
+ var searchterm = $(this).val();
+ $headerLinks.each( function () {
+ var parts = $(this).attr('href').split( 'search=' ),
+ lastpart = '',
+ prefix = 'search=';
+ if ( parts.length > 1 && parts[1].indexOf('&') >= 0 ) {
+ lastpart = parts[1].substring( parts[1].indexOf('&') );
+ } else {
+ prefix = '&search=';
+ }
+ this.href = parts[0] + prefix + encodeURIComponent( searchterm ) + lastpart;
+ });
+ }).trigger( 'change' );
+
+ } );
-} ); } )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki.special/mediawiki.special.undelete.js b/resources/mediawiki.special/mediawiki.special.undelete.js
index 33b80275..d20aab51 100644
--- a/resources/mediawiki.special/mediawiki.special.undelete.js
+++ b/resources/mediawiki.special/mediawiki.special.undelete.js
@@ -1,10 +1,11 @@
-/*
- * JavaScript for Specical:Undelete
+/**
+ * JavaScript for Special:Undelete
*/
-jQuery( document ).ready( function( $ ) {
- $( '#mw-undelete-invert' ).click( function( e ) {
+jQuery( document ).ready( function ( $ ) {
+ $( '#mw-undelete-invert' ).click( function ( e ) {
+ $( '#undelete input[type="checkbox"]' ).prop( 'checked', function ( i, val ) {
+ return !val;
+ } );
e.preventDefault();
- $( '#undelete' ).find( 'input:checkbox' )
- .prop( 'checked', function( i, val ) { return !val; } );
} );
} );
diff --git a/resources/mediawiki.special/mediawiki.special.upload.js b/resources/mediawiki.special/mediawiki.special.upload.js
index 63e89713..75532f18 100644
--- a/resources/mediawiki.special/mediawiki.special.upload.js
+++ b/resources/mediawiki.special/mediawiki.special.upload.js
@@ -6,12 +6,12 @@
/**
* Add a preview to the upload form
*/
- $( function ( $ ) {
+ $( document ).ready( function () {
/**
* Is the FileAPI available with sufficient functionality?
*/
function hasFileAPI() {
- return typeof window.FileReader !== 'undefined';
+ return window.FileReader !== undefined;
}
/**
@@ -25,7 +25,7 @@
* @return boolean
*/
function fileIsPreviewable( file ) {
- var known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
+ var known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
tooHuge = 10 * 1024 * 1024;
return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
}
@@ -43,23 +43,26 @@
* @param {File} file
*/
function showPreview( file ) {
- var previewSize = 180,
+ var $canvas,
+ ctx,
+ meta,
+ previewSize = 180,
thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
'<div class="thumbinner">' +
'<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
'<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
'</div>' +
'</div>' );
+
thumb.find( '.filename' ).text( file.name ).end()
.find( '.fileinfo' ).text( prettySize( file.size ) ).end();
- var $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>'),
- ctx = $canvas[0].getContext( '2d' );
+ $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>');
+ ctx = $canvas[0].getContext( '2d' );
$( '#mw-htmlform-source' ).parent().prepend( thumb );
- var meta;
- fetchPreview( file, function( dataURL ) {
- var img = new Image(),
+ fetchPreview( file, function ( dataURL ) {
+ var img = new Image(),
rotation = 0;
if ( meta && meta.tiff && meta.tiff.Orientation ) {
@@ -79,7 +82,8 @@
}
img.onload = function () {
- var width, height, x, y, dx, dy, logicalWidth, logicalHeight;
+ var info, width, height, x, y, dx, dy, logicalWidth, logicalHeight;
+
// Fit the image within the previewSizexpreviewSize box
if ( img.width > img.height ) {
width = previewSize;
@@ -129,12 +133,14 @@
thumb.find('.mw-small-spinner').replaceWith($canvas);
// Image size
- var info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) +
+ info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) +
', ' + prettySize( file.size );
+
$( '#mw-upload-thumbnail .fileinfo' ).text( info );
};
img.src = dataURL;
}, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
+ /*jshint camelcase: false, nomen: false */
try {
meta = mw.libs.jpegmeta( data, file.fileName );
meta._binary_data = null;
@@ -171,9 +177,10 @@
// However, our JPEG metadata library wants a string.
// So, this is going to be an ugly conversion.
reader.onload = function() {
- var buffer = new Uint8Array( reader.result ),
+ var i,
+ buffer = new Uint8Array( reader.result ),
string = '';
- for ( var i = 0; i < buffer.byteLength; i++ ) {
+ for ( i = 0; i < buffer.byteLength; i++ ) {
string += String.fromCharCode( buffer[i] );
}
callbackBinary( string );
@@ -196,7 +203,7 @@
} else {
// This ends up decoding the file to base-64 and back again, which
// feels horribly inefficient.
- reader.onload = function() {
+ reader.onload = function () {
callback( reader.result );
};
reader.readAsDataURL( file );
@@ -230,22 +237,29 @@
* Check if the file does not exceed the maximum size
*/
function checkMaxUploadSize( file ) {
+ var maxSize, $error;
+
function getMaxUploadSize( type ) {
var sizes = mw.config.get( 'wgMaxUploadSize' );
+
if ( sizes[type] !== undefined ) {
return sizes[type];
}
return sizes['*'];
}
+
$( '.mw-upload-source-error' ).remove();
- var maxSize = getMaxUploadSize( 'file' );
+ maxSize = getMaxUploadSize( 'file' );
if ( file.size > maxSize ) {
- var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' +
- mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
- $( '#wpUploadFile' ).after( error );
+ $error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' +
+ mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
+
+ $( '#wpUploadFile' ).after( $error );
+
return false;
}
+
return true;
}
@@ -276,23 +290,30 @@
/**
* Disable all upload source fields except the selected one
*/
- $( function ( $ ) {
- var i, row,
- rows = $( '.mw-htmlform-field-UploadSourceField' );
- for ( i = rows.length; i; i-- ) {
- row = rows[i - 1];
- $( 'input[name="wpSourceType"]', row ).change( ( function () {
- var currentRow = row; // Store current row in our own scope
- return function () {
- $( '.mw-upload-source-error' ).remove();
- if ( this.checked ) {
- // Disable all inputs
- $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', true );
- // Re-enable the current one
- $( 'input', currentRow ).prop( 'disabled', false );
- }
- };
- }() ) );
+ $( document ).ready( function () {
+ var i, $row,
+ $rows = $( '.mw-htmlform-field-UploadSourceField' );
+
+ function createHandler( $currentRow ) {
+ /**
+ * @param {jQuery.Event}
+ */
+ return function () {
+ $( '.mw-upload-source-error' ).remove();
+ if ( this.checked ) {
+ // Disable all inputs
+ $rows.find( 'input[name!="wpSourceType"]' ).prop( 'disabled', true );
+ // Re-enable the current one
+ $currentRow.find( 'input' ).prop( 'disabled', false );
+ }
+ };
+ }
+
+ for ( i = $rows.length; i; i-- ) {
+ $row = $rows.eq(i - 1);
+ $row
+ .find( 'input[name="wpSourceType"]' )
+ .change( createHandler( $row ) );
}
} );
diff --git a/resources/mediawiki.special/mediawiki.special.userLogin.signup.js b/resources/mediawiki.special/mediawiki.special.userLogin.signup.js
new file mode 100644
index 00000000..bba42605
--- /dev/null
+++ b/resources/mediawiki.special/mediawiki.special.userLogin.signup.js
@@ -0,0 +1,10 @@
+/**
+ * JavaScript for Special:UserLogin/signup
+ */
+jQuery( document ).ready( function ( $ ) {
+ $( '#wpCreateaccountMail' )
+ .on( 'change', function() {
+ $( '.mw-row-password' ).toggle( !$( this ).attr( 'checked' ) );
+ } )
+ .trigger( 'change' );
+} );
diff --git a/resources/mediawiki/mediawiki.Title.js b/resources/mediawiki/mediawiki.Title.js
index 33cca585..b86a14ba 100644
--- a/resources/mediawiki/mediawiki.Title.js
+++ b/resources/mediawiki/mediawiki.Title.js
@@ -1,6 +1,4 @@
-/**
- * mediaWiki.Title
- *
+/*!
* @author Neil Kandalgaonkar, 2010
* @author Timo Tijhof, 2011
* @since 1.18
@@ -12,13 +10,12 @@
/* Local space */
/**
- * Title
- * @constructor
+ * @class mw.Title
*
- * @param title {String} Title of the page. If no second argument given,
+ * @constructor
+ * @param {string} title Title of the page. If no second argument given,
* this will be searched for a namespace.
- * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is.
- * @return {Title} this
+ * @param {number} [namespace] Namespace id. If given, title will be taken as-is.
*/
function Title( title, namespace ) {
this.ns = 0; // integer namespace id
@@ -35,17 +32,16 @@
}
var
- /**
- * Public methods (defined later)
- */
+ /* Public methods (defined later) */
fn,
/**
* Strip some illegal chars: control chars, colon, less than, greater than,
* brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
* intact, like unicode bidi chars, but it's a good start..
- * @param s {String}
- * @return {String}
+ * @ignore
+ * @param {string} s
+ * @return {string}
*/
clean = function ( s ) {
if ( s !== undefined ) {
@@ -55,8 +51,9 @@ var
/**
* Convert db-key to readable text.
- * @param s {String}
- * @return {String}
+ * @ignore
+ * @param {string} s
+ * @return {string}
*/
text = function ( s ) {
if ( s !== null && s !== undefined ) {
@@ -68,13 +65,15 @@ var
/**
* Sanitize name.
+ * @ignore
*/
fixName = function ( s ) {
return clean( $.trim( s ) );
},
/**
- * Sanitize name.
+ * Sanitize extension.
+ * @ignore
*/
fixExt = function ( s ) {
return clean( s );
@@ -82,6 +81,7 @@ var
/**
* Sanitize namespace id.
+ * @ignore
* @param id {Number} Namespace id.
* @return {Number|Boolean} The id as-is or boolean false if invalid.
*/
@@ -99,8 +99,8 @@ var
/**
* Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
- *
- * @example On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
+ * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
+ * @ignore
* @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
* @return {Number|Boolean} Namespace id or boolean false if unrecognized.
*/
@@ -125,19 +125,20 @@ var
/**
* Helper to extract namespace, name and extension from a string.
*
- * @param title {mw.Title}
- * @param raw {String}
+ * @ignore
+ * @param {mw.Title} title
+ * @param {string} raw
* @return {mw.Title}
*/
setAll = function ( title, s ) {
// In normal browsers the match-array contains null/undefined if there's no match,
// IE returns an empty string.
var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w+))?$/ ),
- ns_match = getNsIdByName( matches[1] );
+ nsMatch = getNsIdByName( matches[1] );
// Namespace must be valid, and title must be a non-empty string.
- if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) {
- title.ns = ns_match;
+ if ( nsMatch && typeof matches[2] === 'string' && matches[2] !== '' ) {
+ title.ns = nsMatch;
title.name = fixName( matches[2] );
if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
title.ext = fixExt( matches[3] );
@@ -153,8 +154,9 @@ var
/**
* Helper to extract name and extension from a string.
*
- * @param title {mw.Title}
- * @param raw {String}
+ * @ignore
+ * @param {mw.Title} title
+ * @param {string} raw
* @return {mw.Title}
*/
setNameAndExtension = function ( title, raw ) {
@@ -179,8 +181,9 @@ var
/**
* Whether this title exists on the wiki.
- * @param title {mixed} prefixed db-key name (string) or instance of Title
- * @return {mixed} Boolean true/false if the information is available. Otherwise null.
+ * @static
+ * @param {Mixed} title prefixed db-key name (string) or instance of Title
+ * @return {Mixed} Boolean true/false if the information is available. Otherwise null.
*/
Title.exists = function ( title ) {
var type = $.type( title ), obj = Title.exist.pages, match;
@@ -198,20 +201,27 @@ var
};
/**
- * @var Title.exist {Object}
+ * @static
+ * @property
*/
Title.exist = {
/**
- * @var Title.exist.pages {Object} Keyed by PrefixedDb title.
+ * @static
+ * @property {Object} exist.pages Keyed by PrefixedDb title.
* Boolean true value indicates page does exist.
*/
pages: {},
/**
- * @example Declare existing titles: Title.exist.set(['User:John_Doe', ...]);
- * @example Declare titles nonexistent: Title.exist.set(['File:Foo_bar.jpg', ...], false);
- * @param titles {String|Array} Title(s) in strict prefixedDb title form.
- * @param state {Boolean} (optional) State of the given titles. Defaults to true.
- * @return {Boolean}
+ * Example to declare existing titles:
+ * Title.exist.set(['User:John_Doe', ...]);
+ * Eample to declare titles nonexistent:
+ * Title.exist.set(['File:Foo_bar.jpg', ...], false);
+ *
+ * @static
+ * @property exist.set
+ * @param {string|Array} titles Title(s) in strict prefixedDb title form.
+ * @param {boolean} [state] State of the given titles. Defaults to true.
+ * @return {boolean}
*/
set: function ( titles, state ) {
titles = $.isArray( titles ) ? titles : [titles];
@@ -231,7 +241,7 @@ var
/**
* Get the namespace number.
- * @return {Number}
+ * @return {number}
*/
getNamespaceId: function (){
return this.ns;
@@ -240,7 +250,7 @@ var
/**
* Get the namespace prefix (in the content-language).
* In NS_MAIN this is '', otherwise namespace name plus ':'
- * @return {String}
+ * @return {string}
*/
getNamespacePrefix: function (){
return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':');
@@ -248,7 +258,7 @@ var
/**
* The name, like "Foo_bar"
- * @return {String}
+ * @return {string}
*/
getName: function () {
if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
@@ -260,7 +270,7 @@ var
/**
* The name, like "Foo bar"
- * @return {String}
+ * @return {string}
*/
getNameText: function () {
return text( this.getName() );
@@ -269,6 +279,7 @@ var
/**
* Get full name in prefixed DB form, like File:Foo_bar.jpg,
* most useful for API calls, anything that must identify the "title".
+ * @return {string}
*/
getPrefixedDb: function () {
return this.getNamespacePrefix() + this.getMain();
@@ -276,7 +287,7 @@ var
/**
* Get full name in text form, like "File:Foo bar.jpg".
- * @return {String}
+ * @return {string}
*/
getPrefixedText: function () {
return text( this.getPrefixedDb() );
@@ -284,7 +295,7 @@ var
/**
* The main title (without namespace), like "Foo_bar.jpg"
- * @return {String}
+ * @return {string}
*/
getMain: function () {
return this.getName() + this.getDotExtension();
@@ -292,7 +303,7 @@ var
/**
* The "text" form, like "Foo bar.jpg"
- * @return {String}
+ * @return {string}
*/
getMainText: function () {
return text( this.getMain() );
@@ -300,7 +311,7 @@ var
/**
* Get the extension (returns null if there was none)
- * @return {String|null} extension
+ * @return {string|null}
*/
getExtension: function () {
return this.ext;
@@ -308,7 +319,7 @@ var
/**
* Convenience method: return string like ".jpg", or "" if no extension
- * @return {String}
+ * @return {string}
*/
getDotExtension: function () {
return this.ext === null ? '' : '.' + this.ext;
@@ -316,7 +327,8 @@ var
/**
* Return the URL to this title
- * @return {String}
+ * @see mw.util#wikiGetlink
+ * @return {string}
*/
getUrl: function () {
return mw.util.wikiGetlink( this.toString() );
@@ -324,7 +336,8 @@ var
/**
* Whether this title exists on the wiki.
- * @return {mixed} Boolean true/false if the information is available. Otherwise null.
+ * @see #static-method-exists
+ * @return {boolean|null} If the information is available. Otherwise null.
*/
exists: function () {
return Title.exists( this );
diff --git a/resources/mediawiki/mediawiki.Uri.js b/resources/mediawiki/mediawiki.Uri.js
index bd12b214..643e5c3e 100644
--- a/resources/mediawiki/mediawiki.Uri.js
+++ b/resources/mediawiki/mediawiki.Uri.js
@@ -61,11 +61,11 @@
/**
* Function that's useful when constructing the URI string -- we frequently encounter the pattern of
* having to add something to the URI as we go, but only if it's present, and to include a character before or after if so.
- * @param {String} to prepend, if value not empty
- * @param {String} value to include, if not empty
- * @param {String} to append, if value not empty
- * @param {Boolean} raw -- if true, do not URI encode
- * @return {String}
+ * @param {string|undefined} pre To prepend.
+ * @param {string} val To include.
+ * @param {string} post To append.
+ * @param {boolean} raw If true, val will not be encoded.
+ * @return {string} Result.
*/
function cat( pre, val, post, raw ) {
if ( val === undefined || val === null || val === '' ) {
@@ -76,8 +76,8 @@
// Regular expressions to parse many common URIs.
var parser = {
- strict: /^(?:([^:\/?#]+):)?(?:\/\/(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)?((?:[^?#\/]*\/)*[^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
- loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/(?:(?:([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?([^:\/?#]*)(?::(\d*))?)?((?:[^?#\/]*\/)*[^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(?:(?:([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/
},
// The order here matches the order of captured matches in the above parser regexes.
@@ -103,14 +103,14 @@
/**
* Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
* @constructor
- * @param {Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone).
+ * @param {Object|string} uri URI string, or an Object with appropriate properties (especially another URI object to clone).
* Object must have non-blank 'protocol', 'host', and 'path' properties.
- * This parameter is optional. If omitted (or set to undefined, null or empty string), then an object will be created
- * for the default uri of this constructor (e.g. document.location for mw.Uri in MediaWiki core).
- * @param {Object|Boolean} Object with options, or (backwards compatibility) a boolean for strictMode
- * - strictMode {Boolean} Trigger strict mode parsing of the url. Default: false
- * - overrideKeys {Boolean} Wether to let duplicate query parameters override eachother (true) or automagically
- * convert to an array (false, default).
+ * This parameter is optional. If omitted (or set to undefined, null or empty string), then an object will be created
+ * for the default uri of this constructor (e.g. document.location for mw.Uri in MediaWiki core).
+ * @param {Object|boolean} Object with options, or (backwards compatibility) a boolean for strictMode
+ * - {boolean} strictMode Trigger strict mode parsing of the url. Default: false
+ * - {boolean} overrideKeys Wether to let duplicate query parameters override eachother (true) or automagically
+ * convert to an array (false, default).
*/
function Uri( uri, options ) {
options = typeof options === 'object' ? options : { strictMode: !!options };
@@ -158,7 +158,7 @@
}
if ( this.path && this.path.charAt( 0 ) !== '/' ) {
// A real relative URL, relative to defaultUri.path. We can't really handle that since we cannot
- // figure out whether the last path compoennt of defaultUri.path is a directory or a file.
+ // figure out whether the last path component of defaultUri.path is a directory or a file.
throw new Error( 'Bad constructor arguments' );
}
if ( !( this.protocol && this.host && this.path ) ) {
@@ -169,8 +169,8 @@
/**
* Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986
* Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a +
- * @param {String} string
- * @return {String} encoded for URI
+ * @param {string} s String to encode.
+ * @return {string} Encoded string for URI.
*/
Uri.encode = function ( s ) {
return encodeURIComponent( s )
@@ -180,9 +180,9 @@
};
/**
- * Standard decodeURIComponent, with '+' to space
- * @param {String} string encoded for URI
- * @return {String} decoded string
+ * Standard decodeURIComponent, with '+' to space.
+ * @param {string} s String encoded for URI.
+ * @return {string} Decoded string.
*/
Uri.decode = function ( s ) {
return decodeURIComponent( s.replace( /\+/g, '%20' ) );
@@ -192,9 +192,9 @@
/**
* Parse a string and set our properties accordingly.
- * @param {String} URI
+ * @param {string} str URI
* @param {Object} options
- * @return {Boolean} success
+ * @return {boolean} Success.
*/
parse: function ( str, options ) {
var q,
@@ -240,7 +240,7 @@
/**
* Returns user and password portion of a URI.
- * @return {String}
+ * @return {string}
*/
getUserInfo: function () {
return cat( '', this.user, cat( ':', this.password, '' ) );
@@ -248,7 +248,7 @@
/**
* Gets host and port portion of a URI.
- * @return {String}
+ * @return {string}
*/
getHostPort: function () {
return this.host + cat( ':', this.port, '' );
@@ -257,7 +257,7 @@
/**
* Returns the userInfo and host and port portion of the URI.
* In most real-world URLs, this is simply the hostname, but it is more general.
- * @return {String}
+ * @return {string}
*/
getAuthority: function () {
return cat( '', this.getUserInfo(), '@' ) + this.getHostPort();
@@ -266,7 +266,7 @@
/**
* Returns the query arguments of the URL, encoded into a string
* Does not preserve the order of arguments passed into the URI. Does handle escaping.
- * @return {String}
+ * @return {string}
*/
getQueryString: function () {
var args = [];
@@ -274,7 +274,13 @@
var k = Uri.encode( key ),
vals = $.isArray( val ) ? val : [ val ];
$.each( vals, function ( i, v ) {
- args.push( k + ( v === null ? '' : '=' + Uri.encode( v ) ) );
+ if ( v === null ) {
+ args.push( k );
+ } else if ( k === 'title' ) {
+ args.push( k + '=' + mw.util.wikiUrlencode( v ) );
+ } else {
+ args.push( k + '=' + Uri.encode( v ) );
+ }
} );
} );
return args.join( '&' );
@@ -282,7 +288,7 @@
/**
* Returns everything after the authority section of the URI
- * @return {String}
+ * @return {string}
*/
getRelativePath: function () {
return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' );
@@ -290,7 +296,7 @@
/**
* Gets the entire URI string. May not be precisely the same as input due to order of query arguments.
- * @return {String} the URI string
+ * @return {string} The URI string.
*/
toString: function () {
return this.protocol + '://' + this.getAuthority() + this.getRelativePath();
diff --git a/resources/mediawiki/mediawiki.debug.css b/resources/mediawiki/mediawiki.debug.css
index 149e1bff..513cb847 100644
--- a/resources/mediawiki/mediawiki.debug.css
+++ b/resources/mediawiki/mediawiki.debug.css
@@ -1,6 +1,5 @@
.mw-debug {
width: 100%;
- text-align: left;
background-color: #eee;
border-top: 1px solid #aaa;
}
diff --git a/resources/mediawiki/mediawiki.debug.js b/resources/mediawiki/mediawiki.debug.js
index 1ad1a623..88af3c65 100644
--- a/resources/mediawiki/mediawiki.debug.js
+++ b/resources/mediawiki/mediawiki.debug.js
@@ -96,7 +96,7 @@
buildHtml: function () {
var $container, $bits, panes, id, gitInfo;
- $container = $( '<div id="mw-debug-toolbar" class="mw-debug"></div>' );
+ $container = $( '<div id="mw-debug-toolbar" class="mw-debug" lang="en" dir="ltr"></div>' );
$bits = $( '<div class="mw-debug-bits"></div>' );
@@ -187,9 +187,7 @@
.text( 'Time: ' + this.data.time.toFixed( 5 ) );
bitDiv( 'memory' )
- .text( 'Memory: ' + this.data.memory )
- .append( $( '<span title="Peak usage"></span>' ).text( ' (' + this.data.memoryPeak + ')' ) );
-
+ .text( 'Memory: ' + this.data.memory + ' (Peak: ' + this.data.memoryPeak + ')' );
$bits.appendTo( $container );
diff --git a/resources/mediawiki/mediawiki.feedback.js b/resources/mediawiki/mediawiki.feedback.js
index 634d02b1..1afe51ef 100644
--- a/resources/mediawiki/mediawiki.feedback.js
+++ b/resources/mediawiki/mediawiki.feedback.js
@@ -1,5 +1,5 @@
/**
- * mediawiki.Feedback
+ * mediawiki.feedback
*
* @author Ryan Kaldari, 2010
* @author Neil Kandalgaonkar, 2010-11
@@ -68,17 +68,28 @@
mw.Feedback.prototype = {
setup: function () {
- var fb = this;
+ var $feedbackPageLink,
+ $bugNoteLink,
+ $bugsListLink,
+ fb = this;
- var $feedbackPageLink = $( '<a>' )
- .attr( { 'href': fb.title.getUrl(), 'target': '_blank' } )
- .css( { 'white-space': 'nowrap' } );
+ $feedbackPageLink = $( '<a>' )
+ .attr( {
+ href: fb.title.getUrl(),
+ target: '_blank'
+ } )
+ .css( {
+ whiteSpace: 'nowrap'
+ } );
- var $bugNoteLink = $( '<a>' ).attr( { 'href': '#' } ).click( function () {
+ $bugNoteLink = $( '<a>' ).attr( { href: '#' } ).click( function () {
fb.displayBugs();
} );
- var $bugsListLink = $( '<a>' ).attr( { 'href': fb.bugsListLink, 'target': '_blank' } );
+ $bugsListLink = $( '<a>' ).attr( {
+ href: fb.bugsListLink,
+ target: '_blank'
+ } );
// TODO: Use a stylesheet instead of these inline styles
this.$dialog =
@@ -108,7 +119,7 @@
),
$( '<div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' ).append(
mw.msg( 'feedback-adding' ),
- $( '<br/>' ),
+ $( '<br>' ),
$( '<span class="feedback-spinner"></span>' )
),
$( '<div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>' ).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 $( '<a />' ).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 <key> if key does not exist.
+ * @return {string} Message as a string in the current form or `<key>` if key does not exist.
*/
toString: function () {
var text;
if ( !this.exists() ) {
// Use <key> 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 <head>.
- * @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 `<head>`.
+ * @return {HTMLElement} Node reference to the created `<style>` tag.
*/
function addStyleTag( text, nextnode ) {
var s = document.createElement( 'style' );
@@ -429,76 +456,107 @@ var mw = ( function ( $, undefined ) {
}
/**
- * Checks if certain cssText is safe to append to
- * a stylesheet.
- *
- * Right now it only makes sure that cssText containing @import
- * rules will end up in a new stylesheet (as those only work when
- * placed at the start of a stylesheet; bug 35562).
- * This could later be extended to take care of other bugs, such as
- * the IE cssRules limit - not the same as the IE styleSheets limit).
+ * Checks whether it is safe to add this css to a stylesheet.
+ *
+ * @private
+ * @param {string} cssText
+ * @return {boolean} False if a new one must be created.
*/
- function canExpandStylesheetWith( $style, cssText ) {
+ function canExpandStylesheetWith( cssText ) {
+ // Makes sure that cssText containing `@import`
+ // rules will end up in a new stylesheet (as those only work when
+ // placed at the start of a stylesheet; bug 35562).
return cssText.indexOf( '@import' ) === -1;
}
- function addEmbeddedCSS( cssText ) {
+ /**
+ * @param {string} [cssText=cssBuffer] If called without cssText,
+ * the internal buffer will be inserted instead.
+ * @param {Function} [callback]
+ */
+ function addEmbeddedCSS( cssText, callback ) {
var $style, styleEl;
- $style = getMarker().prev();
- // Re-use <style> tags if possible, this to try to stay
- // under the IE stylesheet limit (bug 31676).
- // Also verify that the the element before Marker actually is one
- // that came from ResourceLoader, and not a style tag that some
- // other script inserted before our marker, or, more importantly,
- // it may not be a style tag at all (could be <meta> or <script>).
- if (
- $style.data( 'ResourceLoaderDynamicStyleTag' ) === true &&
- canExpandStylesheetWith( $style, cssText )
- ) {
- // There's already a dynamic <style> tag present and
- // canExpandStylesheetWith() gave a green light to append more to it.
- styleEl = $style.get( 0 );
- if ( styleEl.styleSheet ) {
- try {
- styleEl.styleSheet.cssText += cssText; // IE
- } catch ( e ) {
- log( 'addEmbeddedCSS fail\ne.message: ' + e.message, e );
- }
- } else {
- styleEl.appendChild( document.createTextNode( String( cssText ) ) );
+
+ if ( callback ) {
+ cssCallbacks.add( callback );
+ }
+
+ // Yield once before inserting the <style> tag. There are likely
+ // more calls coming up which we can combine this way.
+ // Appending a stylesheet and waiting for the browser to repaint
+ // is fairly expensive, this reduces it (bug 45810)
+ if ( cssText ) {
+ // Be careful not to extend the buffer with css that needs a new stylesheet
+ if ( !cssBuffer || canExpandStylesheetWith( cssText ) ) {
+ // Linebreak for somewhat distinguishable sections
+ // (the rl-cachekey comment separating each)
+ cssBuffer += '\n' + cssText;
+ // TODO: Use requestAnimationFrame in the future which will
+ // perform even better by not injecting styles while the browser
+ // is paiting.
+ setTimeout( function () {
+ // Can't pass addEmbeddedCSS to setTimeout directly because Firefox
+ // (below version 13) has the non-standard behaviour of passing a
+ // numerical "lateness" value as first argument to this callback
+ // http://benalman.com/news/2009/07/the-mysterious-firefox-settime/
+ addEmbeddedCSS();
+ } );
+ return;
}
+
+ // This is a delayed call and we got a buffer still
+ } else if ( cssBuffer ) {
+ cssText = cssBuffer;
+ cssBuffer = '';
} else {
- $( addStyleTag( cssText, getMarker() ) )
- .data( 'ResourceLoaderDynamicStyleTag', true );
+ // This is a delayed call, but buffer is already cleared by
+ // another delayed call.
+ return;
}
- }
- function compare( a, b ) {
- var i;
- if ( a.length !== b.length ) {
- return false;
- }
- for ( i = 0; i < b.length; i += 1 ) {
- if ( $.isArray( a[i] ) ) {
- if ( !compare( a[i], b[i] ) ) {
- return false;
+ // By default, always create a new <style>. Appending text
+ // to a <style> tag means the contents have to be re-parsed (bug 45810).
+ // Except, of course, in IE below 9, in there we default to
+ // re-using and appending to a <style> tag due to the
+ // IE stylesheet limit (bug 31676).
+ if ( 'documentMode' in document && document.documentMode <= 9 ) {
+
+ $style = getMarker().prev();
+ // Verify that the the element before Marker actually is a
+ // <style> tag and one that came from ResourceLoader
+ // (not some other style tag or even a `<meta>` or `<script>`).
+ if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
+ // There's already a dynamic <style> tag present and
+ // canExpandStylesheetWith() gave a green light to append more to it.
+ styleEl = $style.get( 0 );
+ if ( styleEl.styleSheet ) {
+ try {
+ styleEl.styleSheet.cssText += cssText; // IE
+ } catch ( e ) {
+ log( 'addEmbeddedCSS fail\ne.message: ' + e.message, e );
+ }
+ } else {
+ styleEl.appendChild( document.createTextNode( String( cssText ) ) );
}
- }
- if ( a[i] !== b[i] ) {
- return false;
+ cssCallbacks.fire().empty();
+ return;
}
}
- return true;
+
+ $( addStyleTag( cssText, getMarker() ) ).data( 'ResourceLoaderDynamicStyleTag', true );
+
+ cssCallbacks.fire().empty();
}
/**
* Generates an ISO8601 "basic" string from a UNIX timestamp
+ * @private
*/
function formatVersionNumber( timestamp ) {
- var pad = function ( a, b, c ) {
- return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
- },
- d = new Date();
+ var d = new Date();
+ function pad( a, b, c ) {
+ return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
+ }
d.setTime( timestamp * 1000 );
return [
pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
@@ -509,15 +567,16 @@ var mw = ( function ( $, undefined ) {
/**
* Resolves dependencies and detects circular references.
*
- * @param module String Name of the top-level module whose dependencies shall be
+ * @private
+ * @param {string} module Name of the top-level module whose dependencies shall be
* resolved and sorted.
- * @param resolved Array Returns a topological sort of the given module and its
+ * @param {Array} resolved Returns a topological sort of the given module and its
* dependencies, such that later modules depend on earlier modules. The array
* contains the module names. If the array contains already some module names,
* this function appends its result to the pre-existing array.
- * @param unresolved Object [optional] Hash used to track the current dependency
+ * @param {Object} [unresolved] Hash used to track the current dependency
* chain; used to report loops in the dependency graph.
- * @throws Error if any unregistered module or a dependency loop is encountered
+ * @throws {Error} If any unregistered module or a dependency loop is encountered
*/
function sortDependencies( module, resolved, unresolved ) {
var n, deps, len;
@@ -566,9 +625,10 @@ var mw = ( function ( $, undefined ) {
* Gets a list of module names that a module depends on in their proper dependency
* order.
*
- * @param module string module name or array of string module names
- * @return list of dependencies, including 'module'.
- * @throws Error if circular reference is detected
+ * @private
+ * @param {string} module Module name or array of string module names
+ * @return {Array} list of dependencies, including 'module'.
+ * @throws {Error} If circular reference is detected
*/
function resolve( module ) {
var m, resolved;
@@ -597,10 +657,11 @@ var mw = ( function ( $, undefined ) {
* One can also filter for 'unregistered', which will return the
* modules names that don't have a registry entry.
*
- * @param states string or array of strings of module states to filter by
- * @param modules array list of module names to filter (optional, by default the entire
+ * @private
+ * @param {string|string[]} states Module states to filter by
+ * @param {Array} modules List of module names to filter (optional, by default the entire
* registry is used)
- * @return array list of filtered module names
+ * @return {Array} List of filtered module names
*/
function filter( states, modules ) {
var list, module, s, m;
@@ -642,9 +703,9 @@ var mw = ( function ( $, undefined ) {
* Determine whether all dependencies are in state 'ready', which means we may
* execute the module or job now.
*
- * @param dependencies Array dependencies (module names) to be checked.
- *
- * @return Boolean true if all dependencies are in state 'ready', false otherwise
+ * @private
+ * @param {Array} dependencies Dependencies (module names) to be checked.
+ * @return {boolean} True if all dependencies are in state 'ready', false otherwise
*/
function allReady( dependencies ) {
return filter( 'ready', dependencies ).length === dependencies.length;
@@ -656,8 +717,9 @@ var mw = ( function ( $, undefined ) {
* Gets console references in each invocation, so that delayed debugging tools work
* fine. No need for optimization here, which would only result in losing logs.
*
- * @param msg String text for the log entry.
- * @param e Error [optional] to also log.
+ * @private
+ * @param {string} msg text for the log entry.
+ * @param {Error} [e]
*/
function log( msg, e ) {
var console = window.console;
@@ -679,7 +741,8 @@ var mw = ( function ( $, undefined ) {
* state up the dependency tree; otherwise, execute all jobs/modules that now have all their
* dependencies satisfied. On jobs depending on a failed module, run the error callback, if any.
*
- * @param module String name of module that entered one of the states 'ready', 'error', or 'missing'.
+ * @private
+ * @param {string} module Name of module that entered one of the states 'ready', 'error', or 'missing'.
*/
function handlePending( module ) {
var j, job, hasErrors, m, stateChange;
@@ -712,7 +775,7 @@ var mw = ( function ( $, undefined ) {
j -= 1;
try {
if ( hasErrors ) {
- throw new Error ("Module " + module + " failed.");
+ throw new Error( 'Module ' + module + ' failed.');
} else {
if ( $.isFunction( job.ready ) ) {
job.ready();
@@ -747,8 +810,9 @@ var mw = ( function ( $, undefined ) {
* Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
* depending on whether document-ready has occurred yet and whether we are in async mode.
*
- * @param src String: URL to script, will be used as the src attribute in the script tag
- * @param callback Function: Optional callback which will be run when the script is done
+ * @private
+ * @param {string} src URL to script, will be used as the src attribute in the script tag
+ * @param {Function} [callback] Callback which will be run when the script is done
*/
function addScript( src, callback, async ) {
/*jshint evil:true */
@@ -758,16 +822,20 @@ var mw = ( function ( $, undefined ) {
// Using isReady directly instead of storing it locally from
// a $.fn.ready callback (bug 31895).
if ( $.isReady || async ) {
- // jQuery's getScript method is NOT better than doing this the old-fashioned way
- // because jQuery will eval the script's code, and errors will not have sane
- // line numbers.
+ // Can't use jQuery.getScript because that only uses <script> for cross-domain,
+ // it uses XHR and eval for same-domain scripts, which we don't want because it
+ // messes up line numbers.
+ // The below is based on jQuery ([jquery@1.8.2]/src/ajax/script.js)
+
+ // IE-safe way of getting the <head>. document.head isn't supported
+ // in old IE, and doesn't work when in the <head>.
+ head = document.getElementsByTagName( 'head' )[0] || document.body;
+
script = document.createElement( 'script' );
- script.setAttribute( 'src', src );
- script.setAttribute( 'type', 'text/javascript' );
+ script.async = true;
+ script.src = src;
if ( $.isFunction( callback ) ) {
- // Attach handlers for all browsers (based on jQuery.ajax)
script.onload = script.onreadystatechange = function () {
-
if (
!done
&& (
@@ -775,24 +843,20 @@ var mw = ( function ( $, undefined ) {
|| /loaded|complete/.test( script.readyState )
)
) {
-
done = true;
- callback();
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
- // Handle memory leak in IE. This seems to fail in
- // IE7 sometimes (Permission Denied error when
- // accessing script.parentNode) so wrap it in
- // a try catch.
- try {
- script.onload = script.onreadystatechange = null;
- if ( script.parentNode ) {
- script.parentNode.removeChild( script );
- }
-
- // Dereference the script
- script = undefined;
- } catch ( e ) { }
+ // Remove the script
+ if ( script.parentNode ) {
+ script.parentNode.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ callback();
}
};
}
@@ -800,20 +864,17 @@ var mw = ( function ( $, undefined ) {
if ( window.opera ) {
// Appending to the <head> blocks rendering completely in Opera,
// so append to the <body> after document ready. This means the
- // scripts only start loading after the document has been rendered,
+ // scripts only start loading after the document has been rendered,
// but so be it. Opera users don't deserve faster web pages if their
- // browser makes it impossible
- $( function () { document.body.appendChild( script ); } );
+ // browser makes it impossible.
+ $( function () {
+ document.body.appendChild( script );
+ } );
} else {
- // IE-safe way of getting the <head> . document.documentElement.head doesn't
- // work in scripts that run in the <head>
- head = document.getElementsByTagName( 'head' )[0];
- ( document.body || head ).appendChild( script );
+ head.appendChild( script );
}
} else {
- document.write( mw.html.element(
- 'script', { 'type': 'text/javascript', 'src': src }, ''
- ) );
+ document.write( mw.html.element( 'script', { 'src': src }, '' ) );
if ( $.isFunction( callback ) ) {
// Document.write is synchronous, so this is called when it's done
// FIXME: that's a lie. doc.write isn't actually synchronous
@@ -825,10 +886,12 @@ var mw = ( function ( $, undefined ) {
/**
* Executes a loaded module, making it ready to use
*
- * @param module string module name to execute
+ * @private
+ * @param {string} module Module name to execute
*/
function execute( module ) {
- var key, value, media, i, urls, script, markModuleReady, nestedAddScript;
+ var key, value, media, i, urls, cssHandle, checkCssHandles,
+ cssHandlesRegistered = false;
if ( registry[module] === undefined ) {
throw new Error( 'Module has not been registered yet: ' + module );
@@ -837,12 +900,13 @@ var mw = ( function ( $, undefined ) {
} else if ( registry[module].state === 'loading' ) {
throw new Error( 'Module has not completed loading yet: ' + module );
} else if ( registry[module].state === 'ready' ) {
- throw new Error( 'Module has already been loaded: ' + module );
+ throw new Error( 'Module has already been executed: ' + module );
}
/**
* Define loop-function here for efficiency
* and to avoid re-using badly scoped variables.
+ * @ignore
*/
function addLink( media, url ) {
var el = document.createElement( 'link' );
@@ -854,6 +918,80 @@ var mw = ( function ( $, undefined ) {
el.href = url;
}
+ function runScript() {
+ var script, markModuleReady, nestedAddScript;
+ try {
+ script = registry[module].script;
+ markModuleReady = function () {
+ registry[module].state = 'ready';
+ handlePending( module );
+ };
+ nestedAddScript = function ( arr, callback, async, i ) {
+ // Recursively call addScript() in its own callback
+ // for each element of arr.
+ if ( i >= arr.length ) {
+ // We're at the end of the array
+ callback();
+ return;
+ }
+
+ addScript( arr[i], function () {
+ nestedAddScript( arr, callback, async, i + 1 );
+ }, async );
+ };
+
+ if ( $.isArray( script ) ) {
+ nestedAddScript( script, markModuleReady, registry[module].async, 0 );
+ } else if ( $.isFunction( script ) ) {
+ registry[module].state = 'ready';
+ script( $ );
+ handlePending( module );
+ }
+ } catch ( e ) {
+ // This needs to NOT use mw.log because these errors are common in production mode
+ // and not in debug mode, such as when a symbol that should be global isn't exported
+ log( 'Exception thrown by ' + module + ': ' + e.message, e );
+ registry[module].state = 'error';
+ handlePending( module );
+ }
+ }
+
+ // This used to be inside runScript, but since that is now fired asychronously
+ // (after CSS is loaded) we need to set it here right away. It is crucial that
+ // when execute() is called this is set synchronously, otherwise modules will get
+ // executed multiple times as the registry will state that it isn't loading yet.
+ registry[module].state = 'loading';
+
+ // Add localizations to message system
+ if ( $.isPlainObject( registry[module].messages ) ) {
+ mw.messages.set( registry[module].messages );
+ }
+
+ // Make sure we don't run the scripts until all (potentially asynchronous)
+ // stylesheet insertions have completed.
+ ( function () {
+ var pending = 0;
+ checkCssHandles = function () {
+ // cssHandlesRegistered ensures we don't take off too soon, e.g. when
+ // one of the cssHandles is fired while we're still creating more handles.
+ if ( cssHandlesRegistered && pending === 0 && runScript ) {
+ runScript();
+ runScript = undefined; // Revoke
+ }
+ };
+ cssHandle = function () {
+ var check = checkCssHandles;
+ pending++;
+ return function () {
+ if (check) {
+ pending--;
+ check();
+ check = undefined; // Revoke
+ }
+ };
+ };
+ }() );
+
// Process styles (see also mw.loader.implement)
// * back-compat: { <media>: css }
// * back-compat: { <media>: [url, ..] }
@@ -872,7 +1010,7 @@ var mw = ( function ( $, undefined ) {
// Strings are pre-wrapped in "@media". The media-type was just ""
// (because it had to be set to something).
// This is one of the reasons why this format is no longer used.
- addEmbeddedCSS( value );
+ addEmbeddedCSS( value, cssHandle() );
} else {
// back-compat: { <media>: [url, ..] }
media = key;
@@ -889,7 +1027,7 @@ var mw = ( function ( $, undefined ) {
addLink( media, value[i] );
} else if ( key === 'css' ) {
// { "css": [css, ..] }
- addEmbeddedCSS( value[i] );
+ addEmbeddedCSS( value[i], cssHandle() );
}
}
// Not an array, but a regular object
@@ -906,61 +1044,24 @@ var mw = ( function ( $, undefined ) {
}
}
- // Add localizations to message system
- if ( $.isPlainObject( registry[module].messages ) ) {
- mw.messages.set( registry[module].messages );
- }
-
- // Execute script
- try {
- script = registry[module].script;
- markModuleReady = function () {
- registry[module].state = 'ready';
- handlePending( module );
- };
- nestedAddScript = function ( arr, callback, async, i ) {
- // Recursively call addScript() in its own callback
- // for each element of arr.
- if ( i >= arr.length ) {
- // We're at the end of the array
- callback();
- return;
- }
-
- addScript( arr[i], function () {
- nestedAddScript( arr, callback, async, i + 1 );
- }, async );
- };
-
- if ( $.isArray( script ) ) {
- registry[module].state = 'loading';
- nestedAddScript( script, markModuleReady, registry[module].async, 0 );
- } else if ( $.isFunction( script ) ) {
- registry[module].state = 'ready';
- script( $ );
- handlePending( module );
- }
- } catch ( e ) {
- // This needs to NOT use mw.log because these errors are common in production mode
- // and not in debug mode, such as when a symbol that should be global isn't exported
- log( 'Exception thrown by ' + module + ': ' + e.message, e );
- registry[module].state = 'error';
- handlePending( module );
- }
+ // Kick off.
+ cssHandlesRegistered = true;
+ checkCssHandles();
}
/**
* Adds a dependencies to the queue with optional callbacks to be run
* when the dependencies are ready or fail
*
- * @param dependencies string module name or array of string module names
- * @param ready function callback to execute when all dependencies are ready
- * @param error function callback to execute when any dependency fails
- * @param async (optional) If true, load modules asynchronously even if
- * document ready has not yet occurred
+ * @private
+ * @param {string|string[]} dependencies Module name or array of string module names
+ * @param {Function} [ready] Callback to execute when all dependencies are ready
+ * @param {Function} [error] Callback to execute when any dependency fails
+ * @param {boolean} [async] If true, load modules asynchronously even if
+ * document ready has not yet occurred.
*/
function request( dependencies, ready, error, async ) {
- var regItemDeps, regItemDepLen, n;
+ var n;
// Allow calling by single module name
if ( typeof dependencies === 'string' ) {
@@ -1012,6 +1113,7 @@ var mw = ( function ( $, undefined ) {
/**
* Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] }
* to a query string of the form foo.bar,baz|bar.baz,quux
+ * @private
*/
function buildModulesString( moduleMap ) {
var arr = [], p, prefix;
@@ -1025,14 +1127,15 @@ var mw = ( function ( $, undefined ) {
/**
* Asynchronously append a script tag to the end of the body
* that invokes load.php
- * @param moduleMap {Object}: Module map, see buildModulesString()
- * @param currReqBase {Object}: Object with other parameters (other than 'modules') to use in the request
- * @param sourceLoadScript {String}: URL of load.php
- * @param async {Boolean}: If true, use an asynchrounous request even if document ready has not yet occurred
+ * @private
+ * @param {Object} moduleMap Module map, see #buildModulesString
+ * @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request
+ * @param {string} sourceLoadScript URL of load.php
+ * @param {boolean} async If true, use an asynchrounous request even if document ready has not yet occurred
*/
function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
var request = $.extend(
- { 'modules': buildModulesString( moduleMap ) },
+ { modules: buildModulesString( moduleMap ) },
currReqBase
);
request = sortQuery( request );
@@ -1127,9 +1230,9 @@ var mw = ( function ( $, undefined ) {
}
}
- currReqBase = $.extend( { 'version': formatVersionNumber( maxVersion ) }, reqBase );
+ currReqBase = $.extend( { version: formatVersionNumber( maxVersion ) }, reqBase );
// For user modules append a user name to the request.
- if ( group === "user" && mw.config.get( 'wgUserName' ) !== null ) {
+ if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
currReqBase.user = mw.config.get( 'wgUserName' );
}
currReqBaseLength = $.param( currReqBase ).length;
@@ -1183,10 +1286,10 @@ var mw = ( function ( $, undefined ) {
/**
* Register a source.
*
- * @param id {String}: Short lowercase a-Z string representing a source, only used internally.
- * @param props {Object}: Object containing only the loadScript property which is a url to
- * the load.php location of the source.
- * @return {Boolean}
+ * @param {string} id Short lowercase a-Z string representing a source, only used internally.
+ * @param {Object} props Object containing only the loadScript property which is a url to
+ * the load.php location of the source.
+ * @return {boolean}
*/
addSource: function ( id, props ) {
var source;
@@ -1242,15 +1345,15 @@ var mw = ( function ( $, undefined ) {
}
// List the module as registered
registry[module] = {
- 'version': version !== undefined ? parseInt( version, 10 ) : 0,
- 'dependencies': [],
- 'group': typeof group === 'string' ? group : null,
- 'source': typeof source === 'string' ? source: 'local',
- 'state': 'registered'
+ version: version !== undefined ? parseInt( version, 10 ) : 0,
+ dependencies: [],
+ group: typeof group === 'string' ? group : null,
+ source: typeof source === 'string' ? source: 'local',
+ state: 'registered'
};
if ( typeof dependencies === 'string' ) {
// Allow dependencies to be given as a single module name
- registry[module].dependencies = [dependencies];
+ registry[module].dependencies = [ dependencies ];
} else if ( typeof dependencies === 'object' || $.isFunction( dependencies ) ) {
// Allow dependencies to be given as an array of module names
// or a function which returns an array
@@ -1265,20 +1368,20 @@ var mw = ( function ( $, undefined ) {
*
* All arguments are required.
*
- * @param {String} module Name of module
+ * @param {string} module Name of module
* @param {Function|Array} script Function with module code or Array of URLs to
- * be used as the src attribute of a new <script> tag.
+ * be used as the src attribute of a new `<script>` tag.
* @param {Object} style Should follow one of the following patterns:
- * { "css": [css, ..] }
- * { "url": { <media>: [url, ..] } }
- * And for backwards compatibility (needs to be supported forever due to caching):
- * { <media>: css }
- * { <media>: [url, ..] }
+ * { "css": [css, ..] }
+ * { "url": { <media>: [url, ..] } }
+ * And for backwards compatibility (needs to be supported forever due to caching):
+ * { <media>: css }
+ * { <media>: [url, ..] }
*
- * The reason css strings are not concatenated anymore is bug 31676. We now check
- * whether it's safe to extend the stylesheet (see canExpandStylesheetWith).
+ * The reason css strings are not concatenated anymore is bug 31676. We now check
+ * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
*
- * @param {Object} msgs List of key/value pairs to be passed through mw.messages.set
+ * @param {Object} msgs List of key/value pairs to be added to {@link mw#messages}.
*/
implement: function ( module, script, style, msgs ) {
// Validate input
@@ -1331,7 +1434,7 @@ var mw = ( function ( $, undefined ) {
}
// Allow calling with a single dependency as a string
if ( tod === 'string' ) {
- dependencies = [dependencies];
+ dependencies = [ dependencies ];
}
// Resolve entire dependency map
dependencies = resolve( dependencies );
@@ -1366,7 +1469,7 @@ var mw = ( function ( $, undefined ) {
* be assumed if loading a URL, and false will be assumed otherwise.
*/
load: function ( modules, type, async ) {
- var filtered, m, module;
+ var filtered, m, module, l;
// Validate input
if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
@@ -1381,11 +1484,13 @@ var mw = ( function ( $, undefined ) {
async = true;
}
if ( type === 'text/css' ) {
- $( 'head' ).append( $( '<link>', {
- rel: 'stylesheet',
- type: 'text/css',
- href: modules
- } ) );
+ // IE7-8 throws security warnings when inserting a <link> tag
+ // with a protocol-relative URL set though attributes (instead of
+ // properties) - when on HTTPS. See also bug #.
+ l = document.createElement( 'link' );
+ l.rel = 'stylesheet';
+ l.href = modules;
+ $( 'head' ).append( l );
return;
}
if ( type === 'text/javascript' || type === undefined ) {
@@ -1396,7 +1501,7 @@ var mw = ( function ( $, undefined ) {
throw new Error( 'invalid type for external url, must be text/css or text/javascript. not ' + type );
}
// Called with single module
- modules = [modules];
+ modules = [ modules ];
}
// Filter out undefined modules, otherwise resolve() will throw
@@ -1427,7 +1532,7 @@ var mw = ( function ( $, undefined ) {
return;
}
// Since some modules are not yet ready, queue up a request.
- request( filtered, null, null, async );
+ request( filtered, undefined, undefined, async );
},
/**
@@ -1448,7 +1553,7 @@ var mw = ( function ( $, undefined ) {
if ( registry[module] === undefined ) {
mw.loader.register( module );
}
- if ( $.inArray(state, ['ready', 'error', 'missing']) !== -1
+ if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1
&& registry[module].state !== state ) {
// Make sure pending modules depending on this one get executed if their
// dependencies are now fulfilled!
@@ -1510,11 +1615,15 @@ var mw = ( function ( $, undefined ) {
};
}() ),
- /** HTML construction helper functions */
+ /**
+ * HTML construction helper functions
+ * @class mw.html
+ * @singleton
+ */
html: ( function () {
function escapeCallback( s ) {
switch ( s ) {
- case "'":
+ case '\'':
return '&#039;';
case '"':
return '&quot;';
@@ -1530,7 +1639,7 @@ var mw = ( function ( $, undefined ) {
return {
/**
* Escape a string for HTML. Converts special characters to HTML entities.
- * @param s The string to escape
+ * @param {string} s The string to escape
*/
escape: function ( s ) {
return s.replace( /['"<>&]/g, escapeCallback );
@@ -1538,7 +1647,7 @@ var mw = ( function ( $, undefined ) {
/**
* Wrapper object for raw HTML passed to mw.html.element().
- * @constructor
+ * @class mw.html.Raw
*/
Raw: function ( value ) {
this.value = value;
@@ -1546,7 +1655,7 @@ var mw = ( function ( $, undefined ) {
/**
* Wrapper object for CDATA element contents passed to mw.html.element()
- * @constructor
+ * @class mw.html.Cdata
*/
Cdata: function ( value ) {
this.value = value;
diff --git a/resources/mediawiki/mediawiki.log.js b/resources/mediawiki/mediawiki.log.js
index 4ea1a881..ee08b12b 100644
--- a/resources/mediawiki/mediawiki.log.js
+++ b/resources/mediawiki/mediawiki.log.js
@@ -41,7 +41,7 @@
':' + ( d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds() ) +
'.' + ( d.getMilliseconds() < 10 ? '00' + d.getMilliseconds() : ( d.getMilliseconds() < 100 ? '0' + d.getMilliseconds() : d.getMilliseconds() ) ),
$log = $( '#mw-log-console' );
-
+
if ( !$log.length ) {
$log = $( '<div id="mw-log-console"></div>' ).css( {
overflow: 'auto',
diff --git a/resources/mediawiki/mediawiki.notification.js b/resources/mediawiki/mediawiki.notification.js
index 58a3ab6a..fd34e7ee 100644
--- a/resources/mediawiki/mediawiki.notification.js
+++ b/resources/mediawiki/mediawiki.notification.js
@@ -1,24 +1,24 @@
-/**
- * Implements mediaWiki.notification library
- */
( function ( mw, $ ) {
'use strict';
- var isPageReady = false,
- isInitialized = false,
+ var notification,
+ isPageReady = false,
preReadyNotifQueue = [],
- /**
- * @var {jQuery}
- * The #mw-notification-area div that all notifications are contained inside.
- */
+ // The #mw-notification-area div that all notifications are contained inside.
$area = null;
/**
* Creates a Notification object for 1 message.
- * Does not insert anything into the document (see .start()).
+ * Does not insert anything into the document (see #start).
+ *
+ * The "_" in the name is to avoid a bug (http://github.com/senchalabs/jsduck/issues/304)
+ * It is not part of the actual class name.
+ *
+ * @class mw.Notification_
+ * @alternateClassName mw.Notification
+ * @private
*
* @constructor
- * @see mw.notification.notify
*/
function Notification( message, options ) {
var $notification, $notificationTitle, $notificationContent;
@@ -88,7 +88,9 @@
// Other notification elements matching the same tag
$tagMatches,
outerHeight,
- placeholderHeight;
+ placeholderHeight,
+ autohideCount,
+ notif;
if ( this.isOpen ) {
return;
@@ -164,10 +166,11 @@
}
} );
+ notif = this;
+
// Create a clear placeholder we can use to make the notifications around the notification that is being
// replaced expand or contract gracefully to fit the height of the new notification.
- var self = this;
- self.$replacementPlaceholder = $( '<div>' )
+ notif.$replacementPlaceholder = $( '<div>' )
// Set the height to the space the previous notification or placeholder took
.css( 'height', outerHeight )
// Make sure that this placeholder is at the very end of this tagged notification group
@@ -181,7 +184,7 @@
// Reset the notification position after we've finished the space animation
// However do not do it if the placeholder was removed because another tagged
// notification went and closed this one.
- if ( self.$replacementPlaceholder ) {
+ if ( notif.$replacementPlaceholder ) {
$notification.css( 'position', '' );
}
// Finally, remove the placeholder from the DOM
@@ -206,7 +209,7 @@
// By default a notification is paused.
// If this notification is within the first {autoHideLimit} notifications then
// start the auto-hide timer as soon as it's created.
- var autohideCount = $area.find( '.mw-notification-autohide' ).length;
+ autohideCount = $area.find( '.mw-notification-autohide' ).length;
if ( autohideCount <= notification.autoHideLimit ) {
this.resume();
}
@@ -253,6 +256,7 @@
*
* @param {Object} options An object containing options for the closing of the notification.
* These are typically only used internally.
+ *
* - speed: Use a close speed different than the default 'slow'.
* - placeholder: Set to false to disable the placeholder transition.
*/
@@ -326,7 +330,7 @@
/**
* Helper function, take a list of notification divs and call
- * a function on the Notification instance attached to them
+ * a function on the Notification instance attached to them.
*
* @param {jQuery} $notifications A jQuery object containing notification divs
* @param {string} fn The name of the function to call on the Notification instance
@@ -341,40 +345,43 @@
}
/**
- * Initialisation
- * (don't call before document ready)
+ * Initialisation.
+ * Must only be called once, and not before the document is ready.
+ * @ignore
*/
function init() {
- if ( !isInitialized ) {
- isInitialized = true;
- $area = $( '<div id="mw-notification-area"></div>' )
- // Pause auto-hide timers when the mouse is in the notification area.
- .on( {
- mouseenter: notification.pause,
- mouseleave: notification.resume
- } )
- // When clicking on a notification close it.
- .on( 'click', '.mw-notification', function () {
- var notif = $( this ).data( 'mw.notification' );
- if ( notif ) {
- notif.close();
- }
- } )
- // Stop click events from <a> tags from propogating to prevent clicking.
- // on links from hiding a notification.
- .on( 'click', 'a', function ( e ) {
- e.stopPropagation();
- } );
-
- // Prepend the notification area to the content area and save it's object.
- mw.util.$content.prepend( $area );
- }
+ $area = $( '<div id="mw-notification-area"></div>' )
+ // Pause auto-hide timers when the mouse is in the notification area.
+ .on( {
+ mouseenter: notification.pause,
+ mouseleave: notification.resume
+ } )
+ // When clicking on a notification close it.
+ .on( 'click', '.mw-notification', function () {
+ var notif = $( this ).data( 'mw.notification' );
+ if ( notif ) {
+ notif.close();
+ }
+ } )
+ // Stop click events from <a> tags from propogating to prevent clicking.
+ // on links from hiding a notification.
+ .on( 'click', 'a', function ( e ) {
+ e.stopPropagation();
+ } );
+
+ // Prepend the notification area to the content area and save it's object.
+ mw.util.$content.prepend( $area );
}
- var notification = {
+ /**
+ * @class mw.notification
+ * @singleton
+ */
+ notification = {
/**
* Pause auto-hide timers for all notifications.
* Notifications will not auto-hide until resume is called.
+ * @see mw.Notification#pause
*/
pause: function () {
callEachNotification(
@@ -385,13 +392,13 @@
/**
* Resume any paused auto-hide timers from the beginning.
- * Only the first {autoHideLimit} timers will be resumed.
+ * Only the first #autoHideLimit timers will be resumed.
*/
resume: function () {
callEachNotification(
- // Only call resume on the first {autoHideLimit} notifications.
- // Exclude noautohide notifications to avoid bugs where {autoHideLimit}
- // { autoHide: false } notifications are at the start preventing any
+ // Only call resume on the first #autoHideLimit notifications.
+ // Exclude noautohide notifications to avoid bugs where #autoHideLimit
+ // `{ autoHide: false }` notifications are at the start preventing any
// auto-hide notifications from being autohidden.
$area.children( '.mw-notification-autohide' ).slice( 0, notification.autoHideLimit ),
'resume'
@@ -401,10 +408,9 @@
/**
* Display a notification message to the user.
*
- * @param {mixed} message The DOM-element, jQuery object, mw.Message instance,
- * or plaintext string to be used as the message.
+ * @param {HTMLElement|jQuery|mw.Message|string} message
* @param {Object} options The options to use for the notification.
- * See mw.notification.defaults for details.
+ * See #defaults for details.
*/
notify: function ( message, options ) {
var notif;
@@ -420,22 +426,23 @@
},
/**
- * @var {Object}
- * The defaults for mw.notification.notify's options parameter
- * autoHide:
- * A boolean indicating whether the notifification should automatically
- * be hidden after shown. Or if it should persist.
+ * @property {Object}
+ * The defaults for #notify options parameter.
+ *
+ * - autoHide:
+ * A boolean indicating whether the notifification should automatically
+ * be hidden after shown. Or if it should persist.
*
- * tag:
- * An optional string. When a notification is tagged only one message
- * with that tag will be displayed. Trying to display a new notification
- * with the same tag as one already being displayed will cause the other
- * notification to be closed and this new notification to open up inside
- * the same place as the previous notification.
+ * - tag:
+ * An optional string. When a notification is tagged only one message
+ * with that tag will be displayed. Trying to display a new notification
+ * with the same tag as one already being displayed will cause the other
+ * notification to be closed and this new notification to open up inside
+ * the same place as the previous notification.
*
- * title:
- * An optional title for the notification. Will be displayed above the
- * content. Usually in bold.
+ * - title:
+ * An optional title for the notification. Will be displayed above the
+ * content. Usually in bold.
*/
defaults: {
autoHide: true,
@@ -444,20 +451,20 @@
},
/**
- * @var {number}
+ * @property {number}
* Number of seconds to wait before auto-hiding notifications.
*/
autoHideSeconds: 5,
/**
- * @var {number}
+ * @property {number}
* Maximum number of notifications to count down auto-hide timers for.
- * Only the first {autoHideLimit} notifications being displayed will
+ * Only the first #autoHideLimit notifications being displayed will
* auto-hide. Any notifications further down in the list will only start
* counting down to auto-hide after the first few messages have closed.
*
* This basically represents the number of notifications the user should
- * be able to process in {autoHideSeconds} time.
+ * be able to process in #autoHideSeconds time.
*/
autoHideLimit: 3
};
diff --git a/resources/mediawiki/mediawiki.notify.js b/resources/mediawiki/mediawiki.notify.js
index 3bf2a896..83d95b61 100644
--- a/resources/mediawiki/mediawiki.notify.js
+++ b/resources/mediawiki/mediawiki.notify.js
@@ -1,11 +1,13 @@
/**
- * Implements mediaWiki.notify function
+ * @class mw.plugin.notify
*/
( function ( mw ) {
'use strict';
/**
- * @see mw.notification.notify
+ * @see mw.notification#notify
+ * @param message
+ * @param options
*/
mw.notify = function ( message, options ) {
// Don't bother loading the whole notification system if we never use it.
@@ -17,4 +19,9 @@
} );
};
-}( mediaWiki ) ); \ No newline at end of file
+ /**
+ * @class mw
+ * @mixins mw.plugin.notify
+ */
+
+}( mediaWiki ) );
diff --git a/resources/mediawiki/mediawiki.searchSuggest.css b/resources/mediawiki/mediawiki.searchSuggest.css
new file mode 100644
index 00000000..0fb862b9
--- /dev/null
+++ b/resources/mediawiki/mediawiki.searchSuggest.css
@@ -0,0 +1,16 @@
+/* Make sure the links are not underlined or colored, ever. */
+/* There is already a :focus / :hover indication on the <div>. */
+.suggestions a.mw-searchSuggest-link,
+.suggestions a.mw-searchSuggest-link:hover,
+.suggestions a.mw-searchSuggest-link:active,
+.suggestions a.mw-searchSuggest-link:focus {
+ text-decoration: none;
+ color: black;
+}
+
+.suggestions-result-current a.mw-searchSuggest-link,
+.suggestions-result-current a.mw-searchSuggest-link:hover,
+.suggestions-result-current a.mw-searchSuggest-link:active,
+.suggestions-result-current a.mw-searchSuggest-link:focus {
+ color: white;
+}
diff --git a/resources/mediawiki/mediawiki.searchSuggest.js b/resources/mediawiki/mediawiki.searchSuggest.js
index 99a55576..2bc7cea9 100644
--- a/resources/mediawiki/mediawiki.searchSuggest.js
+++ b/resources/mediawiki/mediawiki.searchSuggest.js
@@ -3,7 +3,7 @@
*/
( function ( mw, $ ) {
$( document ).ready( function ( $ ) {
- var map, searchboxesSelectors,
+ var map, resultRenderCache, searchboxesSelectors,
// Region where the suggestions box will appear directly below
// (using the same width). Can be a container element or the input
// itself, depending on what suits best in the environment.
@@ -41,6 +41,91 @@
return;
}
+ // Compute form data for search suggestions functionality.
+ function computeResultRenderCache( context ) {
+ var $form, formAction, baseHref, linkParams;
+
+ // Compute common parameters for links' hrefs
+ $form = context.config.$region.closest( 'form' );
+
+ formAction = $form.attr( 'action' );
+ baseHref = formAction + ( formAction.match(/\?/) ? '&' : '?' );
+
+ linkParams = {};
+ $.each( $form.serializeArray(), function ( idx, obj ) {
+ linkParams[ obj.name ] = obj.value;
+ } );
+
+ return {
+ textParam: context.data.$textbox.attr( 'name' ),
+ linkParams: linkParams,
+ baseHref: baseHref
+ };
+ }
+
+ // The function used to render the suggestions.
+ function renderFunction( text, context ) {
+ if ( !resultRenderCache ) {
+ resultRenderCache = computeResultRenderCache( context );
+ }
+
+ // linkParams object is modified and reused
+ resultRenderCache.linkParams[ resultRenderCache.textParam ] = text;
+
+ // this is the container <div>, jQueryfied
+ this
+ .append(
+ // the <span> is needed for $.autoEllipsis to work
+ $( '<span>' )
+ .css( 'whiteSpace', 'nowrap' )
+ .text( text )
+ )
+ .wrap(
+ $( '<a>' )
+ .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) )
+ .addClass( 'mw-searchSuggest-link' )
+ );
+ }
+
+ function specialRenderFunction( query, context ) {
+ var $el = this;
+
+ if ( !resultRenderCache ) {
+ resultRenderCache = computeResultRenderCache( context );
+ }
+
+ // linkParams object is modified and reused
+ resultRenderCache.linkParams[ resultRenderCache.textParam ] = query;
+
+ if ( $el.children().length === 0 ) {
+ $el
+ .append(
+ $( '<div>' )
+ .addClass( 'special-label' )
+ .text( mw.msg( 'searchsuggest-containing' ) ),
+ $( '<div>' )
+ .addClass( 'special-query' )
+ .text( query )
+ .autoEllipsis()
+ )
+ .show();
+ } else {
+ $el.find( '.special-query' )
+ .text( query )
+ .autoEllipsis();
+ }
+
+ if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
+ $el.parent().attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' );
+ } else {
+ $el.wrap(
+ $( '<a>' )
+ .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' )
+ .addClass( 'mw-searchSuggest-link' )
+ );
+ }
+ }
+
// General suggestions functionality for all search boxes
searchboxesSelectors = [
// Primary searchbox on every page in standard skins
@@ -89,6 +174,7 @@
}
},
result: {
+ render: renderFunction,
select: function ( $input ) {
$input.closest( 'form' ).submit();
}
@@ -118,31 +204,13 @@
// Special suggestions functionality for skin-provided search box
$searchInput.suggestions( {
result: {
+ render: renderFunction,
select: function ( $input ) {
$input.closest( 'form' ).submit();
}
},
special: {
- render: function ( query ) {
- var $el = this;
- if ( $el.children().length === 0 ) {
- $el
- .append(
- $( '<div>' )
- .addClass( 'special-label' )
- .text( mw.msg( 'searchsuggest-containing' ) ),
- $( '<div>' )
- .addClass( 'special-query' )
- .text( query )
- .autoEllipsis()
- )
- .show();
- } else {
- $el.find( '.special-query' )
- .text( query )
- .autoEllipsis();
- }
- },
+ render: specialRenderFunction,
select: function ( $input ) {
$input.closest( 'form' ).append(
$( '<input type="hidden" name="fulltext" value="1"/>' )
diff --git a/resources/mediawiki/mediawiki.user.js b/resources/mediawiki/mediawiki.user.js
index e64d2e84..e0329597 100644
--- a/resources/mediawiki/mediawiki.user.js
+++ b/resources/mediawiki/mediawiki.user.js
@@ -61,7 +61,7 @@
*
* @return String: Random set of 32 alpha-numeric characters
*/
- function generateId() {
+ this.generateRandomSessionId = function () {
var i, r,
id = '',
seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
@@ -70,7 +70,7 @@
id += seed.substring( r, r + 1 );
}
return id;
- }
+ };
/**
* Gets the current user's name.
@@ -89,6 +89,25 @@
};
/**
+ * Get date user registered, if available.
+ *
+ * @return {Date|false|null} date user registered, or false for anonymous users, or
+ * null when data is not available
+ */
+ this.getRegistration = function () {
+ var registration = mw.config.get( 'wgUserRegistration' );
+ if ( this.isAnon() ) {
+ return false;
+ } else if ( registration === null ) {
+ // Information may not be available if they signed up before
+ // MW began storing this.
+ return null;
+ } else {
+ return new Date( registration );
+ }
+ };
+
+ /**
* Checks if the current user is anonymous.
*
* @return Boolean
@@ -115,7 +134,7 @@
this.sessionId = function () {
var sessionId = $.cookie( 'mediaWiki.user.sessionId' );
if ( typeof sessionId === 'undefined' || sessionId === null ) {
- sessionId = generateId();
+ sessionId = user.generateRandomSessionId();
$.cookie( 'mediaWiki.user.sessionId', sessionId, { 'expires': null, 'path': '/' } );
}
return sessionId;
diff --git a/resources/mediawiki/mediawiki.util.js b/resources/mediawiki/mediawiki.util.js
index 29284384..5211b0d0 100644
--- a/resources/mediawiki/mediawiki.util.js
+++ b/resources/mediawiki/mediawiki.util.js
@@ -1,10 +1,11 @@
-/**
- * Implements mediaWiki.util library
- */
( function ( mw, $ ) {
'use strict';
- // Local cache and alias
+ /**
+ * Utility library
+ * @class mw.util
+ * @singleton
+ */
var util = {
/**
@@ -28,13 +29,10 @@
profile.platform === 'mac'
// Chrome on Mac
? 'ctrl-option-'
- : profile.platform === 'win'
- // Chrome on Windows
- // (both alt- and alt-shift work, but alt-f triggers Chrome wrench menu
- // which alt-shift-f does not)
- ? 'alt-shift-'
- // Chrome on other (Ubuntu?)
- : 'alt-'
+ // Chrome on Windows or Linux
+ // (both alt- and alt-shift work, but alt with E, D, F etc does not
+ // work since they are browser shortcuts)
+ : 'alt-shift-'
);
// Non-Windows Safari with webkit_version > 526
@@ -62,7 +60,8 @@
/* Fill $content var */
util.$content = ( function () {
- var $content, selectors = [
+ var i, l, $content, selectors;
+ selectors = [
// The preferred standard for setting $content (class="mw-body")
// You may also use (class="mw-body mw-body-primary") if you use
// mw-body in multiple locations.
@@ -94,7 +93,7 @@
// not inserted bodytext yet. But in any case <body> should always exist
'body'
];
- for ( var i = 0, l = selectors.length; i < l; i++ ) {
+ for ( i = 0, l = selectors.length; i < l; i++ ) {
$content = $( selectors[i] ).first();
if ( $content.length ) {
return $content;
@@ -136,7 +135,7 @@
/**
* Encode the string like PHP's rawurlencode
*
- * @param str string String to be encoded
+ * @param {string} str String to be encoded.
*/
rawurlencode: function ( str ) {
str = String( str );
@@ -150,7 +149,7 @@
* We want / and : to be included as literal characters in our title URLs
* as they otherwise fatally break the title
*
- * @param str string String to be encoded
+ * @param {string} str String to be encoded.
*/
wikiUrlencode: function ( str ) {
return util.rawurlencode( str )
@@ -158,10 +157,10 @@
},
/**
- * Get the link to a page name (relative to wgServer)
+ * Get the link to a page name (relative to `wgServer`),
*
- * @param str String: Page name to get the link for.
- * @return String: Location for a page with name of 'str' or boolean false on error.
+ * @param {string} str Page name to get the link for.
+ * @return {string} Location for a page with name of `str` or boolean false on error.
*/
wikiGetlink: function ( str ) {
return mw.config.get( 'wgArticlePath' ).replace( '$1',
@@ -170,7 +169,7 @@
/**
* Get address to a script in the wiki root.
- * For index.php use mw.config.get( 'wgScript' )
+ * For index.php use `mw.config.get( 'wgScript' )`.
*
* @since 1.18
* @param str string Name of script (eg. 'api'), defaults to 'index'
@@ -190,20 +189,18 @@
/**
* Append a new style block to the head and return the CSSStyleSheet object.
- * Use .ownerNode to access the <style> element, or use mw.loader.addStyleTag.
+ * Use .ownerNode to access the `<style>` element, or use mw.loader#addStyleTag.
* This function returns the styleSheet object for convience (due to cross-browsers
* difference as to where it is located).
- * @example
- * <code>
- * var sheet = mw.util.addCSS('.foobar { display: none; }');
- * $(foo).click(function () {
- * // Toggle the sheet on and off
- * sheet.disabled = !sheet.disabled;
- * });
- * </code>
*
- * @param text string CSS to be appended
- * @return CSSStyleSheet (use .ownerNode to get to the <style> element)
+ * var sheet = mw.util.addCSS('.foobar { display: none; }');
+ * $(foo).click(function () {
+ * // Toggle the sheet on and off
+ * sheet.disabled = !sheet.disabled;
+ * });
+ *
+ * @param {string} text CSS to be appended
+ * @return {CSSStyleSheet} Use .ownerNode to get to the `<style>` element.
*/
addCSS: function ( text ) {
var s = mw.loader.addStyleTag( text );
@@ -213,10 +210,10 @@
/**
* Hide/show the table of contents element
*
- * @param $toggleLink jQuery A jQuery object of the toggle link.
- * @param callback function Function to be called after the toggle is
- * completed (including the animation) (optional)
- * @return mixed Boolean visibility of the toc (true if it's visible)
+ * @param {jQuery} $toggleLink A jQuery object of the toggle link.
+ * @param {Function} [callback] Function to be called after the toggle is
+ * completed (including the animation).
+ * @return {Mixed} Boolean visibility of the toc (true if it's visible)
* or Null if there was no table of contents.
*/
toggleToc: function ( $toggleLink, callback ) {
@@ -253,12 +250,14 @@
* Grab the URL parameter value for the given parameter.
* Returns null if not found.
*
- * @param param string The parameter name.
- * @param url string URL to search through (optional)
- * @return mixed Parameter value or null.
+ * @param {string} param The parameter name.
+ * @param {string} [url] URL to search through.
+ * @return {Mixed} Parameter value or null.
*/
getParamValue: function ( param, url ) {
- url = url || document.location.href;
+ if ( url === undefined ) {
+ url = document.location.href;
+ }
// Get last match, stop at hash
var re = new RegExp( '^[^#]*[&?]' + $.escapeRE( param ) + '=([^&#]*)' ),
m = re.exec( url );
@@ -271,14 +270,14 @@
},
/**
- * @var string
+ * @property {string}
* Access key prefix. Will be re-defined based on browser/operating system
- * detection in mw.util.init().
+ * detection in mw.util#init.
*/
tooltipAccessKeyPrefix: 'alt-',
/**
- * @var RegExp
+ * @property {RegExp}
* Regex to match accesskey tooltips.
*/
tooltipAccessKeyRegexp: /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/,
@@ -289,8 +288,7 @@
* otherwise, all the nodes that will probably have accesskeys by
* default are updated.
*
- * @param $nodes {Array|jQuery} [optional] A jQuery object, or array
- * of elements to update.
+ * @param {Array|jQuery} [$nodes] A jQuery object, or array of nodes to update.
*/
updateTooltipAccessKeys: function ( $nodes ) {
if ( !$nodes ) {
@@ -312,9 +310,9 @@
},
/*
- * @var jQuery
- * A jQuery object that refers to the content area element
- * Populated by init().
+ * @property {jQuery}
+ * A jQuery object that refers to the content area element.
+ * Populated by #init.
*/
$content: null,
@@ -329,28 +327,28 @@
*
* By default the new link will be added to the end of the list. To
* add the link before a given existing item, pass the DOM node
- * (document.getElementById( 'foobar' )) or the jQuery-selector
- * ( '#foobar' ) of that item.
+ * (e.g. `document.getElementById( 'foobar' )`) or a jQuery-selector
+ * (e.g. `'#foobar'`) for that item.
*
- * @example mw.util.addPortletLink(
- * 'p-tb', 'http://mediawiki.org/',
- * 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', '#t-print'
- * )
+ * mw.util.addPortletLink(
+ * 'p-tb', 'http://mediawiki.org/',
+ * 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', '#t-print'
+ * );
*
- * @param portlet string ID of the target portlet ( 'p-cactions' or 'p-personal' etc.)
- * @param href string Link URL
- * @param text string Link text
- * @param id string ID of the new item, should be unique and preferably have
- * the appropriate prefix ( 'ca-', 'pt-', 'n-' or 't-' )
- * @param tooltip string Text to show when hovering over the link, without accesskey suffix
- * @param accesskey string Access key to activate this link (one character, try
- * to avoid conflicts. Use $( '[accesskey=x]' ).get() in the console to
- * see if 'x' is already used.
- * @param nextnode mixed DOM Node or jQuery-selector string of the item that the new
- * item should be added before, should be another item in the same
- * list, it will be ignored otherwise
+ * @param {string} portlet ID of the target portlet ( 'p-cactions' or 'p-personal' etc.)
+ * @param {string} href Link URL
+ * @param {string} text Link text
+ * @param {string} [id] ID of the new item, should be unique and preferably have
+ * the appropriate prefix ( 'ca-', 'pt-', 'n-' or 't-' )
+ * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix
+ * @param {string} [accesskey] Access key to activate this link (one character, try
+ * to avoid conflicts. Use `$( '[accesskey=x]' ).get()` in the console to
+ * see if 'x' is already used.
+ * @param {HTMLElement|jQuery|string} [nextnode] Element or jQuery-selector string to the item that
+ * the new item should be added before, should be another item in the same
+ * list, it will be ignored otherwise
*
- * @return mixed The DOM Node of the added item (a ListItem or Anchor element,
+ * @return {HTMLElement|null} The added element (a ListItem or Anchor element,
* depending on the skin) or null if no element was added to the document.
*/
addPortletLink: function ( portlet, href, text, id, tooltip, accesskey, nextnode ) {
@@ -370,7 +368,6 @@
// just add it to the bottom of their 'sidebar' element as a fallback
switch ( mw.config.get( 'skin' ) ) {
case 'standard':
- case 'cologneblue':
$( '#quickbar' ).append( $link.after( '<br/>' ) );
return $link[0];
case 'nostalgia':
@@ -440,7 +437,7 @@
// If the jQuery selector isn't found within the <ul>,
// or if nextnode was invalid or not passed at all,
- // then just append it at the end of the <ul> (this is the default behaviour)
+ // then just append it at the end of the <ul> (this is the default behavior)
} else {
$ul.append( $item );
}
@@ -455,9 +452,9 @@
* something, replacing any previous message.
* Calling with no arguments, with an empty string or null will hide the message
*
- * @param message {mixed} The DOM-element, jQuery object or HTML-string to be put inside the message box.
+ * @param {Mixed} message The DOM-element, jQuery object or HTML-string to be put inside the message box.
* to allow CSS/JS to hide different boxes. null = no class used.
- * @depreceated Use mw.notify
+ * @deprecated Use mw#notify
*/
jsMessage: function ( message ) {
if ( !arguments.length || message === '' || message === null ) {
@@ -475,87 +472,80 @@
* according to HTML5 specification. Please note the specification
* does not validate a domain with one character.
*
- * @todo FIXME: should be moved to or replaced by a JavaScript validation module.
+ * FIXME: should be moved to or replaced by a validation module.
*
- * @param mailtxt string E-mail address to be validated.
- * @return mixed Null if mailtxt was an empty string, otherwise true/false
- * is determined by validation.
+ * @param {string} mailtxt E-mail address to be validated.
+ * @return {boolean|null} Null if `mailtxt` was an empty string, otherwise true/false
+ * as determined by validation.
*/
validateEmail: function ( mailtxt ) {
- var rfc5322_atext, rfc1034_ldh_str, HTML5_email_regexp;
+ var rfc5322Atext, rfc1034LdhStr, html5EmailRegexp;
if ( mailtxt === '' ) {
return null;
}
- /**
- * HTML5 defines a string as valid e-mail address if it matches
- * the ABNF:
- * 1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
- * With:
- * - atext : defined in RFC 5322 section 3.2.3
- * - ldh-str : defined in RFC 1034 section 3.5
- *
- * (see STD 68 / RFC 5234 http://tools.ietf.org/html/std68):
- */
-
- /**
- * First, define the RFC 5322 'atext' which is pretty easy:
- * atext = ALPHA / DIGIT / ; Printable US-ASCII
- "!" / "#" / ; characters not including
- "$" / "%" / ; specials. Used for atoms.
- "&" / "'" /
- "*" / "+" /
- "-" / "/" /
- "=" / "?" /
- "^" / "_" /
- "`" / "{" /
- "|" / "}" /
- "~"
- */
- rfc5322_atext = "a-z0-9!#$%&'*+\\-/=?^_`{|}~";
-
- /**
- * Next define the RFC 1034 'ldh-str'
- * <domain> ::= <subdomain> | " "
- * <subdomain> ::= <label> | <subdomain> "." <label>
- * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
- * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
- * <let-dig-hyp> ::= <let-dig> | "-"
- * <let-dig> ::= <letter> | <digit>
- */
- rfc1034_ldh_str = "a-z0-9\\-";
-
- HTML5_email_regexp = new RegExp(
+ // HTML5 defines a string as valid e-mail address if it matches
+ // the ABNF:
+ // 1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
+ // With:
+ // - atext : defined in RFC 5322 section 3.2.3
+ // - ldh-str : defined in RFC 1034 section 3.5
+ //
+ // (see STD 68 / RFC 5234 http://tools.ietf.org/html/std68)
+ // First, define the RFC 5322 'atext' which is pretty easy:
+ // atext = ALPHA / DIGIT / ; Printable US-ASCII
+ // "!" / "#" / ; characters not including
+ // "$" / "%" / ; specials. Used for atoms.
+ // "&" / "'" /
+ // "*" / "+" /
+ // "-" / "/" /
+ // "=" / "?" /
+ // "^" / "_" /
+ // "`" / "{" /
+ // "|" / "}" /
+ // "~"
+ rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~';
+
+ // Next define the RFC 1034 'ldh-str'
+ // <domain> ::= <subdomain> | " "
+ // <subdomain> ::= <label> | <subdomain> "." <label>
+ // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+ // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+ // <let-dig-hyp> ::= <let-dig> | "-"
+ // <let-dig> ::= <letter> | <digit>
+ rfc1034LdhStr = 'a-z0-9\\-';
+
+ html5EmailRegexp = new RegExp(
// start of string
'^'
+
// User part which is liberal :p
- '[' + rfc5322_atext + '\\.]+'
+ '[' + rfc5322Atext + '\\.]+'
+
// 'at'
'@'
+
// Domain first part
- '[' + rfc1034_ldh_str + ']+'
+ '[' + rfc1034LdhStr + ']+'
+
// Optional second part and following are separated by a dot
- '(?:\\.[' + rfc1034_ldh_str + ']+)*'
+ '(?:\\.[' + rfc1034LdhStr + ']+)*'
+
// End of string
'$',
// RegExp is case insensitive
'i'
);
- return (null !== mailtxt.match( HTML5_email_regexp ) );
+ return (null !== mailtxt.match( html5EmailRegexp ) );
},
/**
* Note: borrows from IP::isIPv4
*
- * @param address string
- * @param allowBlock boolean
- * @return boolean
+ * @param {string} address
+ * @param {boolean} allowBlock
+ * @return {boolean}
*/
isIPv4Address: function ( address, allowBlock ) {
if ( typeof address !== 'string' ) {
@@ -572,9 +562,9 @@
/**
* Note: borrows from IP::isIPv6
*
- * @param address string
- * @param allowBlock boolean
- * @return boolean
+ * @param {string} address
+ * @param {boolean} allowBlock
+ * @return {boolean}
*/
isIPv6Address: function ( address, allowBlock ) {
if ( typeof address !== 'string' ) {
diff --git a/resources/startup.js b/resources/startup.js
index 7951af06..deff7e6e 100644
--- a/resources/startup.js
+++ b/resources/startup.js
@@ -3,19 +3,22 @@
* continue loading the jquery and mediawiki modules. This code should work on
* even the most ancient of browsers, so be very careful when editing.
*/
+
/**
* Returns false when run in a black-listed browser
*
* This function will be deleted after it's used, so do not expand it to be
- * generally useful beyond startup
+ * generally useful beyond startup.
*
- * jQuery has minimum requirements of:
- * * Internet Explorer 6.0+
- * * Firefox 3.6+
- * * Safari 5.0+
- * * Opera 11+
- * * Chrome
+ * MediaWiki & jQuery compatibility:
+ * - Internet Explorer 6.0+
+ * - Firefox 10+
+ * - Safari 5.0+
+ * - Opera 11+
+ * - Chrome
*/
+
+/*jshint unused: false */
function isCompatible() {
// IE < 6.0
if ( navigator.appVersion.indexOf( 'MSIE' ) !== -1
@@ -23,11 +26,9 @@ function isCompatible() {
{
return false;
}
- // @todo FIXME: Firefox < 3.6
- // @todo FIXME: Safari < 5.0
- // @todo FIXME: Opera < 11
return true;
}
+
/**
- * The startUp() function will be generated and added here (at the bottom)
+ * The startUp() function will be auto-generated and added below.
*/