diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2013-12-08 09:55:49 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2013-12-08 09:55:49 +0100 |
commit | 4ac9fa081a7c045f6a9f1cfc529d82423f485b2e (patch) | |
tree | af68743f2f4a47d13f2b0eb05f5c4aaf86d8ea37 /resources | |
parent | af4da56f1ad4d3ef7b06557bae365da2ea27a897 (diff) |
Update to MediaWiki 1.22.0
Diffstat (limited to 'resources')
166 files changed, 6697 insertions, 2345 deletions
diff --git a/resources/Resources.php b/resources/Resources.php index 6205bb91..06120008 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -20,7 +20,7 @@ * @file */ -if( !defined( 'MEDIAWIKI' ) ) { +if ( !defined( 'MEDIAWIKI' ) ) { die( 'Not an entry point.' ); } @@ -60,14 +60,11 @@ return array( * * See Vector for an example. */ - - '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' ) ), + 'styles' => array( + 'cologneblue/screen.css' => array( 'media' => 'screen' ), + 'cologneblue/print.css' => array( 'media' => 'print' ), + ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), @@ -102,37 +99,53 @@ return array( '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' ) ), + 'skins.vector' => array( + // Used in the web installer. Test it after modifying this definition! + 'styles' => array( + 'common/commonElements.css' => array( 'media' => 'screen' ), + 'common/commonContent.css' => array( 'media' => 'screen' ), + 'common/commonInterface.css' => array( 'media' => 'screen' ), + 'vector/styles.less', + ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), - 'skins.vector' => array( - // Keep in sync with WebInstallerOutput::getCSS() + 'skins.vector.beta' => array( + // Keep in sync with skins.vector '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)' ), + 'vector/styles-beta.less', ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), 'skins.vector.js' => array( - 'scripts' => 'vector/vector.js', + 'scripts' => array( + 'vector/collapsibleTabs.js', + 'vector/vector.js', + ), + 'position' => 'top', + 'dependencies' => 'jquery.delayedBind', + 'remoteBasePath' => $GLOBALS['wgStylePath'], + 'localBasePath' => $GLOBALS['wgStyleDirectory'], + ), + 'skins.vector.collapsibleNav' => array( + 'scripts' => array( + 'vector/collapsibleNav.js', + ), + 'messages' => array( + 'vector-collapsiblenav-more', + ), + 'dependencies' => array( + 'jquery.client', + 'jquery.cookie', + 'jquery.tabIndex', + ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'position' => 'bottom', ), /* jQuery */ @@ -166,6 +179,7 @@ return array( ), 'jquery.byteLength' => array( 'scripts' => 'resources/jquery/jquery.byteLength.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.byteLimit' => array( 'scripts' => 'resources/jquery/jquery.byteLimit.js', @@ -173,6 +187,11 @@ return array( ), 'jquery.checkboxShiftClick' => array( 'scripts' => 'resources/jquery/jquery.checkboxShiftClick.js', + 'targets' => array( 'desktop', 'mobile' ), + ), + 'jquery.chosen' => array( + 'scripts' => 'resources/jquery.chosen/chosen.jquery.js', + 'styles' => 'resources/jquery.chosen/chosen.css', ), 'jquery.client' => array( 'scripts' => 'resources/jquery/jquery.client.js', @@ -210,6 +229,7 @@ return array( ), 'jquery.getAttrs' => array( 'scripts' => 'resources/jquery/jquery.getAttrs.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.hidpi' => array( 'scripts' => 'resources/jquery/jquery.hidpi.js', @@ -233,12 +253,14 @@ return array( 'scripts' => 'resources/jquery/jquery.makeCollapsible.js', 'styles' => 'resources/jquery/jquery.makeCollapsible.css', 'messages' => array( 'collapsible-expand', 'collapsible-collapse' ), + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.mockjax' => array( 'scripts' => 'resources/jquery/jquery.mockjax.js', ), 'jquery.mw-jump' => array( 'scripts' => 'resources/jquery/jquery.mw-jump.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.mwExtension' => array( 'scripts' => 'resources/jquery/jquery.mwExtension.js', @@ -246,15 +268,18 @@ return array( ), 'jquery.placeholder' => array( 'scripts' => 'resources/jquery/jquery.placeholder.js', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.qunit' => array( 'scripts' => 'resources/jquery/jquery.qunit.js', 'styles' => 'resources/jquery/jquery.qunit.css', 'position' => 'top', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.qunit.completenessTest' => array( 'scripts' => 'resources/jquery/jquery.qunit.completenessTest.js', 'dependencies' => 'jquery.qunit', + 'targets' => array( 'desktop', 'mobile' ), ), 'jquery.spinner' => array( 'scripts' => 'resources/jquery/jquery.spinner.js', @@ -276,7 +301,10 @@ return array( 'scripts' => 'resources/jquery/jquery.tablesorter.js', 'styles' => 'resources/jquery/jquery.tablesorter.css', 'messages' => array( 'sort-descending', 'sort-ascending' ), - 'dependencies' => 'jquery.mwExtension', + 'dependencies' => array( + 'jquery.mwExtension', + 'mediawiki.language.months', + ), ), 'jquery.textSelection' => array( 'scripts' => 'resources/jquery/jquery.textSelection.js', @@ -605,6 +633,12 @@ return array( 'mediawiki.Title', ), ), + 'mediawiki.api.login' => array( + 'scripts' => 'resources/mediawiki.api/mediawiki.api.login.js', + 'dependencies' => array( + 'mediawiki.api', + ), + ), 'mediawiki.api.parse' => array( 'scripts' => 'resources/mediawiki.api/mediawiki.api.parse.js', 'dependencies' => 'mediawiki.api', @@ -616,6 +650,9 @@ return array( 'user.tokens', ), ), + 'mediawiki.icon' => array( + 'styles' => 'resources/mediawiki/mediawiki.icon.css', + ), 'mediawiki.debug' => array( 'scripts' => 'resources/mediawiki/mediawiki.debug.js', 'styles' => 'resources/mediawiki/mediawiki.debug.css', @@ -629,6 +666,14 @@ return array( // must be loaded on the bottom 'position' => 'bottom', ), + 'mediawiki.inspect' => array( + 'scripts' => 'resources/mediawiki/mediawiki.inspect.js', + 'dependencies' => array( + 'jquery.byteLength', + 'jquery.json', + ), + 'targets' => array( 'desktop', 'mobile' ), + ), 'mediawiki.feedback' => array( 'scripts' => 'resources/mediawiki/mediawiki.feedback.js', 'styles' => 'resources/mediawiki/mediawiki.feedback.css', @@ -663,6 +708,7 @@ return array( ), 'mediawiki.htmlform' => array( 'scripts' => 'resources/mediawiki/mediawiki.htmlform.js', + 'messages' => array( 'htmlform-chosen-placeholder' ), ), 'mediawiki.notification' => array( 'styles' => 'resources/mediawiki/mediawiki.notification.css', @@ -687,11 +733,15 @@ return array( 'jquery.client', 'jquery.placeholder', 'jquery.suggestions', + 'mediawiki.api', ), ), 'mediawiki.Title' => array( 'scripts' => 'resources/mediawiki/mediawiki.Title.js', - 'dependencies' => 'mediawiki.util', + 'dependencies' => array( + 'jquery.byteLength', + 'mediawiki.util', + ), ), 'mediawiki.Uri' => array( 'scripts' => 'resources/mediawiki/mediawiki.Uri.js', @@ -723,16 +773,30 @@ return array( 'mediawiki.action.edit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.js', 'dependencies' => array( + 'mediawiki.action.edit.styles', 'jquery.textSelection', 'jquery.byteLimit', ), 'position' => 'top', ), + 'mediawiki.action.edit.styles' => array( + 'styles' => 'resources/mediawiki.action/mediawiki.action.edit.styles.css', + 'position' => 'top', + ), + 'mediawiki.action.edit.collapsibleFooter' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js', + 'styles' => 'resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css', + 'dependencies' => array( + 'jquery.makeCollapsible', + 'mediawiki.icon', + ), + ), 'mediawiki.action.edit.preview' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.preview.js', 'dependencies' => array( 'jquery.form', 'jquery.spinner', + 'mediawiki.action.history.diff', ), ), 'mediawiki.action.history' => array( @@ -759,11 +823,24 @@ return array( ), 'mediawiki.action.view.postEdit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.postEdit.js', - 'dependencies' => 'jquery.cookie' + 'styles' => 'resources/mediawiki.action/mediawiki.action.view.postEdit.css', + 'dependencies' => array( + 'jquery.cookie', + 'mediawiki.jqueryMsg' + ), + 'messages' => array( + 'postedit-confirmation', + ), ), 'mediawiki.action.view.rightClickEdit' => array( 'scripts' => 'resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js', ), + 'mediawiki.action.edit.editWarning' => array( + 'scripts' => 'resources/mediawiki.action/mediawiki.action.edit.editWarning.js', + 'messages' => array( + 'editwarning-warning', + ), + ), // Alias for backwards compatibility 'mediawiki.action.watch.ajax' => array( 'dependencies' => 'mediawiki.page.watch.ajax' @@ -825,6 +902,16 @@ return array( 'targets' => array( 'desktop', 'mobile' ), ), + 'mediawiki.language.months' => array( + 'scripts' => 'resources/mediawiki.language/mediawiki.language.months.js', + 'dependencies' => 'mediawiki.language', + 'messages' => array_merge( + Language::$mMonthMsgs, + Language::$mMonthGenMsgs, + Language::$mMonthAbbrevMsgs + ) + ), + /* MediaWiki Libs */ 'mediawiki.libs.jpegmeta' => array( @@ -833,6 +920,9 @@ return array( /* MediaWiki Page */ + 'mediawiki.page.gallery' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.gallery.js', + ), 'mediawiki.page.ready' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.ready.js', 'dependencies' => array( @@ -842,6 +932,7 @@ return array( 'jquery.mw-jump', 'mediawiki.util', ), + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.page.startup' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.startup.js', @@ -850,6 +941,7 @@ return array( 'mediawiki.util', ), 'position' => 'top', + 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.page.patrol.ajax' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.patrol.ajax.js', @@ -887,6 +979,10 @@ return array( 'watcherrortext', ), ), + 'mediawiki.page.image.pagination' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.image.pagination.js', + 'dependencies' => array( 'jquery.spinner' ) + ), /* MediaWiki Special pages */ @@ -913,15 +1009,21 @@ return array( ), 'mediawiki.special.changeslist' => array( 'styles' => 'resources/mediawiki.special/mediawiki.special.changeslist.css', - 'dependencies' => array( 'jquery.makeCollapsible' ), + ), + 'mediawiki.special.changeslist.enhanced' => array( + 'styles' => 'resources/mediawiki.special/mediawiki.special.changeslist.enhanced.css', ), 'mediawiki.special.movePage' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.movePage.js', 'dependencies' => 'jquery.byteLimit', ), + 'mediawiki.special.pagesWithProp' => array( + 'styles' => 'resources/mediawiki.special/mediawiki.special.pagesWithProp.css', + ), 'mediawiki.special.preferences' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.preferences.js', - 'styles' => 'resources/mediawiki.special/mediawiki.special.preferences.css', + 'styles' => 'resources/mediawiki.special/mediawiki.special.preferences.css', + 'position' => 'top', ), 'mediawiki.special.recentchanges' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.recentchanges.js', @@ -941,7 +1043,7 @@ return array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.undelete.js', ), 'mediawiki.special.upload' => array( - // @TODO: merge in remainder of mediawiki.legacy.upload + // @todo merge in remainder of mediawiki.legacy.upload 'scripts' => 'resources/mediawiki.special/mediawiki.special.upload.js', 'messages' => array( 'widthheight', @@ -951,10 +1053,33 @@ return array( 'size-gigabytes', 'largefileserver', ), - 'dependencies' => array( 'mediawiki.libs.jpegmeta', 'mediawiki.util' ), + 'dependencies' => array( + 'mediawiki.libs.jpegmeta', + 'mediawiki.util', + ), + ), + 'mediawiki.special.userlogin' => array( + 'styles' => array( + 'resources/mediawiki.special/mediawiki.special.vforms.css', + 'resources/mediawiki.special/mediawiki.special.userLogin.css', + ), + 'position' => 'top', + ), + 'mediawiki.special.createaccount' => array( + 'styles' => array( + 'resources/mediawiki.special/mediawiki.special.vforms.css', + 'resources/mediawiki.special/mediawiki.special.createAccount.css', + ), ), - 'mediawiki.special.userlogin.signup' => array( - 'scripts' => 'resources/mediawiki.special/mediawiki.special.userLogin.signup.js', + 'mediawiki.special.createaccount.js' => array( + 'scripts' => 'resources/mediawiki.special/mediawiki.special.createAccount.js', + 'messages' => array( + 'createacct-captcha', + 'createacct-emailrequired', + 'createacct-imgcaptcha-ph' + ), + 'dependencies' => 'mediawiki.jqueryMsg', + 'position' => 'top', ), 'mediawiki.special.javaScriptTest' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js', @@ -964,6 +1089,7 @@ return array( ) ), 'dependencies' => array( 'jquery.qunit' ), 'position' => 'top', + 'targets' => array( 'desktop', 'mobile' ), ), /* MediaWiki Tests */ @@ -978,6 +1104,7 @@ return array( 'mediawiki.page.ready', ), 'position' => 'top', + 'targets' => array( 'desktop', 'mobile' ), ), /* MediaWiki Legacy */ @@ -998,14 +1125,9 @@ return array( 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), 'mediawiki.legacy.config' => array( + // Used in the web installer. Test it after modifying this definition! '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', + 'styles' => array( 'common/config.css' ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], 'dependencies' => 'mediawiki.legacy.wikibits', @@ -1015,12 +1137,12 @@ return array( 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], 'dependencies' => array( - 'mediawiki.legacy.wikibits', 'jquery.byteLimit', ), 'position' => 'top', ), 'mediawiki.legacy.shared' => array( + // Used in the web installer. Test it after modifying this definition! 'styles' => array( 'common/shared.css' => array( 'media' => 'screen' ) ), 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], @@ -1035,7 +1157,9 @@ return array( 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], 'dependencies' => array( - 'mediawiki.legacy.wikibits', + 'jquery.spinner', + 'mediawiki.api', + 'mediawiki.Title', 'mediawiki.util', ), ), @@ -1048,9 +1172,11 @@ return array( ), 'position' => 'top', ), - 'mediawiki.legacy.wikiprintable' => array( - 'styles' => array( 'common/wikiprintable.css' => array( 'media' => 'print' ) ), - 'remoteBasePath' => $GLOBALS['wgStylePath'], - 'localBasePath' => $GLOBALS['wgStyleDirectory'], + 'mediawiki.ui' => array( + 'skinStyles' => array( + 'default' => 'resources/mediawiki.ui/mediawiki.ui.default.css', + 'vector' => 'resources/mediawiki.ui/mediawiki.ui.vector.css', + ), + 'position' => 'top', ), ); diff --git a/resources/Resources.php.orig b/resources/Resources.php.orig deleted file mode 100644 index 0a70c5a2..00000000 --- a/resources/Resources.php.orig +++ /dev/null @@ -1,968 +0,0 @@ -<?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.chosen/LICENSE b/resources/jquery.chosen/LICENSE new file mode 100644 index 00000000..0675dc52 --- /dev/null +++ b/resources/jquery.chosen/LICENSE @@ -0,0 +1,24 @@ +# Chosen, a Select Box Enhancer for jQuery and Protoype +## by Patrick Filler for [Harvest](http://getharvest.com) + +Available for use under the [MIT License](http://en.wikipedia.org/wiki/MIT_License) + +Copyright (c) 2011-2013 by Harvest + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/resources/jquery.chosen/chosen-sprite.png b/resources/jquery.chosen/chosen-sprite.png Binary files differnew file mode 100644 index 00000000..3611ae4a --- /dev/null +++ b/resources/jquery.chosen/chosen-sprite.png diff --git a/resources/jquery.chosen/chosen-sprite@2x.png b/resources/jquery.chosen/chosen-sprite@2x.png Binary files differnew file mode 100644 index 00000000..bd61d963 --- /dev/null +++ b/resources/jquery.chosen/chosen-sprite@2x.png diff --git a/resources/jquery.chosen/chosen.css b/resources/jquery.chosen/chosen.css new file mode 100644 index 00000000..17793ed7 --- /dev/null +++ b/resources/jquery.chosen/chosen.css @@ -0,0 +1,440 @@ +/* @group Base */ +.chzn-container { + font-size: 13px; + position: relative; + display: inline-block; + vertical-align: middle; + zoom: 1; + *display: inline; +} +.chzn-container .chzn-drop { + background: #fff; + border: 1px solid #aaa; + border-top: 0; + position: absolute; + top: 100%; + left: -9999px; + -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15); + -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15); + box-shadow : 0 4px 5px rgba(0,0,0,.15); + z-index: 1010; + width: 100%; + -moz-box-sizing : border-box; + -ms-box-sizing : border-box; + -webkit-box-sizing: border-box; + -khtml-box-sizing : border-box; + box-sizing : border-box; +} + +.chzn-container.chzn-with-drop .chzn-drop { + left: 0; +} + +/* @end */ + +/* @group Single Chosen */ +.chzn-container-single .chzn-single { + background-color: #ffffff; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4)); + background-image: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: linear-gradient(#ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + -webkit-border-radius: 5px; + -moz-border-radius : 5px; + border-radius : 5px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + border: 1px solid #aaaaaa; + -webkit-box-shadow: 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + -moz-box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + display: block; + overflow: hidden; + white-space: nowrap; + position: relative; + height: 23px; + line-height: 24px; + padding: 0 0 0 8px; + color: #444444; + text-decoration: none; +} +.chzn-container-single .chzn-default { + color: #999; +} +.chzn-container-single .chzn-single span { + margin-right: 26px; + display: block; + overflow: hidden; + white-space: nowrap; + -o-text-overflow: ellipsis; + -ms-text-overflow: ellipsis; + text-overflow: ellipsis; +} +.chzn-container-single .chzn-single abbr { + display: block; + position: absolute; + right: 26px; + top: 6px; + width: 12px; + height: 12px; + font-size: 1px; + background: url('chosen-sprite.png') -42px 1px no-repeat; +} +.chzn-container-single .chzn-single abbr:hover { + background-position: -42px -10px; +} +.chzn-container-single.chzn-disabled .chzn-single abbr:hover { + background-position: -42px -10px; +} +.chzn-container-single .chzn-single div { + position: absolute; + right: 0; + top: 0; + display: block; + height: 100%; + width: 18px; +} +.chzn-container-single .chzn-single div b { + background: url('chosen-sprite.png') no-repeat 0px 2px; + display: block; + width: 100%; + height: 100%; +} +.chzn-container-single .chzn-search { + padding: 3px 4px; + position: relative; + margin: 0; + white-space: nowrap; + z-index: 1010; +} +.chzn-container-single .chzn-search input { + background: #fff url('chosen-sprite.png') no-repeat 100% -20px; + background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat 100% -20px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat 100% -20px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat 100% -20px, linear-gradient(#eeeeee 1%, #ffffff 15%); + margin: 1px 0; + padding: 4px 20px 4px 5px; + outline: 0; + border: 1px solid #aaa; + font-family: sans-serif; + font-size: 1em; + width: 100%; + -moz-box-sizing : border-box; + -ms-box-sizing : border-box; + -webkit-box-sizing: border-box; + -khtml-box-sizing : border-box; + box-sizing : border-box; +} +.chzn-container-single .chzn-drop { + margin-top: -1px; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius : 0 0 4px 4px; + border-radius : 0 0 4px 4px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; +} +.chzn-container-single-nosearch .chzn-search { + position: absolute; + left: -9999px; +} +/* @end */ + +/* @group Multi Chosen */ +.chzn-container-multi .chzn-choices { + background-color: #fff; + background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: linear-gradient(#eeeeee 1%, #ffffff 15%); + border: 1px solid #aaa; + margin: 0; + padding: 0; + cursor: text; + overflow: hidden; + height: auto !important; + height: 1%; + position: relative; + width: 100%; + -moz-box-sizing : border-box; + -ms-box-sizing : border-box; + -webkit-box-sizing: border-box; + -khtml-box-sizing : border-box; + box-sizing : border-box; +} +.chzn-container-multi .chzn-choices li { + float: left; + list-style: none; +} +.chzn-container-multi .chzn-choices .search-field { + white-space: nowrap; + margin: 0; + padding: 0; +} +.chzn-container-multi .chzn-choices .search-field input { + color: #666; + background: transparent !important; + border: 0 !important; + font-family: sans-serif; + font-size: 100%; + height: 15px; + padding: 5px; + margin: 1px 0; + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow : none; + box-shadow : none; +} +.chzn-container-multi .chzn-choices .search-field .default { + color: #999; +} +.chzn-container-multi .chzn-choices .search-choice { + -webkit-border-radius: 3px; + -moz-border-radius : 3px; + border-radius : 3px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + color: #333; + border: 1px solid #aaaaaa; + line-height: 13px; + padding: 3px 20px 3px 5px; + margin: 3px 0 3px 5px; + position: relative; + cursor: default; +} +.chzn-container-multi .chzn-choices .search-choice.search-choice-disabled { + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + color: #666; + border: 1px solid #cccccc; + padding-right: 5px; +} +.chzn-container-multi .chzn-choices .search-choice-focus { + background: #d4d4d4; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close { + display: block; + position: absolute; + right: 3px; + top: 4px; + width: 12px; + height: 12px; + font-size: 1px; + background: url('chosen-sprite.png') -42px 1px no-repeat; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover { + background-position: -42px -10px; +} +.chzn-container-multi .chzn-choices .search-choice-focus .search-choice-close { + background-position: -42px -10px; +} +/* @end */ + +/* @group Results */ +.chzn-container .chzn-results { + margin: 0 4px 4px 0; + max-height: 240px; + padding: 0 0 0 4px; + position: relative; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.chzn-container-multi .chzn-results { + margin: 0; + padding: 0; +} +.chzn-container .chzn-results li { + display: none; + line-height: 15px; + padding: 5px 6px; + margin: 0; + list-style: none; +} +.chzn-container .chzn-results .active-result { + cursor: pointer; + display: list-item; +} +.chzn-container .chzn-results .highlighted { + background-color: #3875d7; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3875d7', endColorstr='#2a62bc', GradientType=0 ); + background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc)); + background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -moz-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -o-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: linear-gradient(#3875d7 20%, #2a62bc 90%); + color: #fff; +} +.chzn-container .chzn-results li em { + background: #feffde; + font-style: normal; +} +.chzn-container .chzn-results .highlighted em { + background: transparent; +} +.chzn-container .chzn-results .no-results { + background: #f4f4f4; + display: list-item; +} +.chzn-container .chzn-results .group-result { + cursor: default; + color: #999; + font-weight: bold; +} +.chzn-container .chzn-results .group-option { + padding-left: 15px; +} +.chzn-container-multi .chzn-drop .result-selected { + display: none; +} +.chzn-container .chzn-results-scroll { + background: white; + margin: 0 4px; + position: absolute; + text-align: center; + width: 321px; /* This should by dynamic with js */ + z-index: 1; +} +.chzn-container .chzn-results-scroll span { + display: inline-block; + height: 17px; + text-indent: -5000px; + width: 9px; +} +.chzn-container .chzn-results-scroll-down { + bottom: 0; +} +.chzn-container .chzn-results-scroll-down span { + background: url('chosen-sprite.png') no-repeat -4px -3px; +} +.chzn-container .chzn-results-scroll-up span { + background: url('chosen-sprite.png') no-repeat -22px -3px; +} +/* @end */ + +/* @group Active */ +.chzn-container-active .chzn-single { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active.chzn-with-drop .chzn-single { + border: 1px solid #aaa; + -webkit-box-shadow: 0 1px 0 #fff inset; + -moz-box-shadow : 0 1px 0 #fff inset; + box-shadow : 0 1px 0 #fff inset; + background-color: #eee; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 ); + background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -moz-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -o-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: linear-gradient(#eeeeee 20%, #ffffff 80%); + -webkit-border-bottom-left-radius : 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomleft : 0; + -moz-border-radius-bottomright: 0; + border-bottom-left-radius : 0; + border-bottom-right-radius: 0; +} +.chzn-container-active.chzn-with-drop .chzn-single div { + background: transparent; + border-left: none; +} +.chzn-container-active.chzn-with-drop .chzn-single div b { + background-position: -18px 2px; +} +.chzn-container-active .chzn-choices { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active .chzn-choices .search-field input { + color: #111 !important; +} +/* @end */ + +/* @group Disabled Support */ +.chzn-disabled { + cursor: default; + opacity:0.5 !important; +} +.chzn-disabled .chzn-single { + cursor: default; +} +.chzn-disabled .chzn-choices .search-choice .search-choice-close { + cursor: default; +} + +/* @group Right to Left */ +.chzn-rtl { text-align: right; } +.chzn-rtl .chzn-single { padding: 0 8px 0 0; overflow: visible; } +.chzn-rtl .chzn-single span { margin-left: 26px; margin-right: 0; direction: rtl; } + +.chzn-rtl .chzn-single div { left: 3px; right: auto; } +.chzn-rtl .chzn-single abbr { + left: 26px; + right: auto; +} +.chzn-rtl .chzn-choices .search-field input { direction: rtl; } +.chzn-rtl .chzn-choices li { float: right; } +.chzn-rtl .chzn-choices .search-choice { padding: 3px 5px 3px 19px; margin: 3px 5px 3px 0; } +.chzn-rtl .chzn-choices .search-choice .search-choice-close { left: 4px; right: auto; } +.chzn-rtl .chzn-search { left: 9999px; } +.chzn-rtl.chzn-with-drop .chzn-search { left: 0px; } +.chzn-rtl .chzn-drop { left: 9999px; } +.chzn-rtl.chzn-container-single .chzn-results { margin: 0 0 4px 4px; padding: 0 4px 0 0; } +.chzn-rtl .chzn-results .group-option { padding-left: 0; padding-right: 15px; } +.chzn-rtl.chzn-container-active.chzn-with-drop .chzn-single div { border-right: none; } +.chzn-rtl .chzn-search input { + background: #fff url('chosen-sprite.png') no-repeat -30px -20px; + background: url('chosen-sprite.png') no-repeat -30px -20px, -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: url('chosen-sprite.png') no-repeat -30px -20px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat -30px -20px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat -30px -20px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('chosen-sprite.png') no-repeat -30px -20px, linear-gradient(#eeeeee 1%, #ffffff 15%); + padding: 4px 5px 4px 20px; + direction: rtl; +} +.chzn-container-single.chzn-rtl .chzn-single div b { + background-position: 6px 2px; +} +.chzn-container-single.chzn-rtl.chzn-with-drop .chzn-single div b { + background-position: -12px 2px; +} +/* @end */ + +/* @group Retina compatibility */ +@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) { + .chzn-rtl .chzn-search input, .chzn-container-single .chzn-single abbr, .chzn-container-single .chzn-single div b, .chzn-container-single .chzn-search input, .chzn-container-multi .chzn-choices .search-choice .search-choice-close, .chzn-container .chzn-results-scroll-down span, .chzn-container .chzn-results-scroll-up span { + background-image: url('chosen-sprite@2x.png') !important; + background-repeat: no-repeat !important; + background-size: 52px 37px !important; + } +} +/* @end */ diff --git a/resources/jquery.chosen/chosen.jquery.js b/resources/jquery.chosen/chosen.jquery.js new file mode 100644 index 00000000..745174f7 --- /dev/null +++ b/resources/jquery.chosen/chosen.jquery.js @@ -0,0 +1,1103 @@ +// Chosen, a Select Box Enhancer for jQuery and Protoype +// by Patrick Filler for Harvest, http://getharvest.com +// +// Version 0.9.14 +// Full source at https://github.com/harvesthq/chosen +// Copyright (c) 2011 Harvest http://getharvest.com + +// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +// This file is generated by `cake build`, do not edit it by hand. +(function() { + var SelectParser; + + SelectParser = (function() { + + function SelectParser() { + this.options_index = 0; + this.parsed = []; + } + + SelectParser.prototype.add_node = function(child) { + if (child.nodeName.toUpperCase() === "OPTGROUP") { + return this.add_group(child); + } else { + return this.add_option(child); + } + }; + + SelectParser.prototype.add_group = function(group) { + var group_position, option, _i, _len, _ref, _results; + group_position = this.parsed.length; + this.parsed.push({ + array_index: group_position, + group: true, + label: group.label, + children: 0, + disabled: group.disabled + }); + _ref = group.childNodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + _results.push(this.add_option(option, group_position, group.disabled)); + } + return _results; + }; + + SelectParser.prototype.add_option = function(option, group_position, group_disabled) { + if (option.nodeName.toUpperCase() === "OPTION") { + if (option.text !== "") { + if (group_position != null) { + this.parsed[group_position].children += 1; + } + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + value: option.value, + text: option.text, + html: option.innerHTML, + selected: option.selected, + disabled: group_disabled === true ? group_disabled : option.disabled, + group_array_index: group_position, + classes: option.className, + style: option.style.cssText + }); + } else { + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + empty: true + }); + } + return this.options_index += 1; + } + }; + + return SelectParser; + + })(); + + SelectParser.select_to_array = function(select) { + var child, parser, _i, _len, _ref; + parser = new SelectParser(); + _ref = select.childNodes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + parser.add_node(child); + } + return parser.parsed; + }; + + this.SelectParser = SelectParser; + +}).call(this); + +/* +Chosen source: generate output using 'cake build' +Copyright (c) 2011 by Harvest +*/ + + +(function() { + var AbstractChosen, root; + + root = this; + + AbstractChosen = (function() { + + function AbstractChosen(form_field, options) { + this.form_field = form_field; + this.options = options != null ? options : {}; + if (!AbstractChosen.browser_is_supported()) { + return; + } + this.is_multiple = this.form_field.multiple; + this.set_default_text(); + this.set_default_values(); + this.setup(); + this.set_up_html(); + this.register_observers(); + this.finish_setup(); + } + + AbstractChosen.prototype.set_default_values = function() { + var _this = this; + this.click_test_action = function(evt) { + return _this.test_active_click(evt); + }; + this.activate_action = function(evt) { + return _this.activate_field(evt); + }; + this.active_field = false; + this.mouse_on_container = false; + this.results_showing = false; + this.result_highlighted = null; + this.result_single_selected = null; + this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; + this.disable_search_threshold = this.options.disable_search_threshold || 0; + this.disable_search = this.options.disable_search || false; + this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true; + this.search_contains = this.options.search_contains || false; + this.choices = 0; + this.single_backstroke_delete = this.options.single_backstroke_delete || false; + this.max_selected_options = this.options.max_selected_options || Infinity; + return this.inherit_select_classes = this.options.inherit_select_classes || false; + }; + + AbstractChosen.prototype.set_default_text = function() { + if (this.form_field.getAttribute("data-placeholder")) { + this.default_text = this.form_field.getAttribute("data-placeholder"); + } else if (this.is_multiple) { + this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text; + } else { + this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text; + } + return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text; + }; + + AbstractChosen.prototype.mouse_enter = function() { + return this.mouse_on_container = true; + }; + + AbstractChosen.prototype.mouse_leave = function() { + return this.mouse_on_container = false; + }; + + AbstractChosen.prototype.input_focus = function(evt) { + var _this = this; + if (this.is_multiple) { + if (!this.active_field) { + return setTimeout((function() { + return _this.container_mousedown(); + }), 50); + } + } else { + if (!this.active_field) { + return this.activate_field(); + } + } + }; + + AbstractChosen.prototype.input_blur = function(evt) { + var _this = this; + if (!this.mouse_on_container) { + this.active_field = false; + return setTimeout((function() { + return _this.blur_test(); + }), 100); + } + }; + + AbstractChosen.prototype.result_add_option = function(option) { + var classes, style; + if (!option.disabled) { + option.dom_id = this.container_id + "_o_" + option.array_index; + classes = option.selected && this.is_multiple ? [] : ["active-result"]; + if (option.selected) { + classes.push("result-selected"); + } + if (option.group_array_index != null) { + classes.push("group-option"); + } + if (option.classes !== "") { + classes.push(option.classes); + } + style = option.style.cssText !== "" ? " style=\"" + option.style + "\"" : ""; + return '<li id="' + option.dom_id + '" class="' + classes.join(' ') + '"' + style + '>' + option.html + '</li>'; + } else { + return ""; + } + }; + + AbstractChosen.prototype.results_update_field = function() { + this.set_default_text(); + if (!this.is_multiple) { + this.results_reset_cleanup(); + } + this.result_clear_highlight(); + this.result_single_selected = null; + return this.results_build(); + }; + + AbstractChosen.prototype.results_toggle = function() { + if (this.results_showing) { + return this.results_hide(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.results_search = function(evt) { + if (this.results_showing) { + return this.winnow_results(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.choices_click = function(evt) { + evt.preventDefault(); + if (!this.results_showing) { + return this.results_show(); + } + }; + + AbstractChosen.prototype.keyup_checker = function(evt) { + var stroke, _ref; + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + switch (stroke) { + case 8: + if (this.is_multiple && this.backstroke_length < 1 && this.choices > 0) { + return this.keydown_backstroke(); + } else if (!this.pending_backstroke) { + this.result_clear_highlight(); + return this.results_search(); + } + break; + case 13: + evt.preventDefault(); + if (this.results_showing) { + return this.result_select(evt); + } + break; + case 27: + if (this.results_showing) { + this.results_hide(); + } + return true; + case 9: + case 38: + case 40: + case 16: + case 91: + case 17: + break; + default: + return this.results_search(); + } + }; + + AbstractChosen.prototype.generate_field_id = function() { + var new_id; + new_id = this.generate_random_id(); + this.form_field.id = new_id; + return new_id; + }; + + AbstractChosen.prototype.generate_random_char = function() { + var chars, newchar, rand; + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + rand = Math.floor(Math.random() * chars.length); + return newchar = chars.substring(rand, rand + 1); + }; + + AbstractChosen.prototype.container_width = function() { + var width; + if (this.options.width != null) { + return this.options.width; + } + width = window.getComputedStyle != null ? parseFloat(window.getComputedStyle(this.form_field).getPropertyValue('width')) : (typeof jQuery !== "undefined" && jQuery !== null) && (this.form_field_jq != null) ? this.form_field_jq.outerWidth() : this.form_field.getWidth(); + return width + "px"; + }; + + AbstractChosen.browser_is_supported = function() { + var _ref; + if (window.navigator.appName === "Microsoft Internet Explorer") { + return (null !== (_ref = document.documentMode) && _ref >= 8); + } + return true; + }; + + AbstractChosen.default_multiple_text = "Select Some Options"; + + AbstractChosen.default_single_text = "Select an Option"; + + AbstractChosen.default_no_result_text = "No results match"; + + return AbstractChosen; + + })(); + + root.AbstractChosen = AbstractChosen; + +}).call(this); + +/* +Chosen source: generate output using 'cake build' +Copyright (c) 2011 by Harvest +*/ + + +(function() { + var $, Chosen, root, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + root = this; + + $ = jQuery; + + $.fn.extend({ + chosen: function(options) { + if (!AbstractChosen.browser_is_supported()) { + return this; + } + return this.each(function(input_field) { + var $this; + $this = $(this); + if (!$this.hasClass("chzn-done")) { + return $this.data('chosen', new Chosen(this, options)); + } + }); + } + }); + + Chosen = (function(_super) { + + __extends(Chosen, _super); + + function Chosen() { + return Chosen.__super__.constructor.apply(this, arguments); + } + + Chosen.prototype.setup = function() { + this.form_field_jq = $(this.form_field); + this.current_selectedIndex = this.form_field.selectedIndex; + return this.is_rtl = this.form_field_jq.hasClass("chzn-rtl"); + }; + + Chosen.prototype.finish_setup = function() { + return this.form_field_jq.addClass("chzn-done"); + }; + + Chosen.prototype.set_up_html = function() { + var container_classes, container_props; + this.container_id = this.form_field.id.length ? this.form_field.id.replace(/[^\w]/g, '_') : this.generate_field_id(); + this.container_id += "_chzn"; + container_classes = ["chzn-container"]; + container_classes.push("chzn-container-" + (this.is_multiple ? "multi" : "single")); + if (this.inherit_select_classes && this.form_field.className) { + container_classes.push(this.form_field.className); + } + if (this.is_rtl) { + container_classes.push("chzn-rtl"); + } + container_props = { + 'id': this.container_id, + 'class': container_classes.join(' '), + 'style': "width: " + (this.container_width()) + ";", + 'title': this.form_field.title + }; + this.container = $("<div />", container_props); + if (this.is_multiple) { + this.container.html('<ul class="chzn-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:auto;" /></li></ul><div class="chzn-drop"><ul class="chzn-results"></ul></div>'); + } else { + this.container.html('<a href="javascript:void(0)" class="chzn-single chzn-default" tabindex="-1"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chzn-drop"><div class="chzn-search"><input type="text" autocomplete="off" /></div><ul class="chzn-results"></ul></div>'); + } + this.form_field_jq.hide().after(this.container); + this.dropdown = this.container.find('div.chzn-drop').first(); + this.search_field = this.container.find('input').first(); + this.search_results = this.container.find('ul.chzn-results').first(); + this.search_field_scale(); + this.search_no_results = this.container.find('li.no-results').first(); + if (this.is_multiple) { + this.search_choices = this.container.find('ul.chzn-choices').first(); + this.search_container = this.container.find('li.search-field').first(); + } else { + this.search_container = this.container.find('div.chzn-search').first(); + this.selected_item = this.container.find('.chzn-single').first(); + } + this.results_build(); + this.set_tab_index(); + this.set_label_behavior(); + return this.form_field_jq.trigger("liszt:ready", { + chosen: this + }); + }; + + Chosen.prototype.register_observers = function() { + var _this = this; + this.container.mousedown(function(evt) { + _this.container_mousedown(evt); + }); + this.container.mouseup(function(evt) { + _this.container_mouseup(evt); + }); + this.container.mouseenter(function(evt) { + _this.mouse_enter(evt); + }); + this.container.mouseleave(function(evt) { + _this.mouse_leave(evt); + }); + this.search_results.mouseup(function(evt) { + _this.search_results_mouseup(evt); + }); + this.search_results.mouseover(function(evt) { + _this.search_results_mouseover(evt); + }); + this.search_results.mouseout(function(evt) { + _this.search_results_mouseout(evt); + }); + this.search_results.bind('mousewheel DOMMouseScroll', function(evt) { + _this.search_results_mousewheel(evt); + }); + this.form_field_jq.bind("liszt:updated", function(evt) { + _this.results_update_field(evt); + }); + this.form_field_jq.bind("liszt:activate", function(evt) { + _this.activate_field(evt); + }); + this.form_field_jq.bind("liszt:open", function(evt) { + _this.container_mousedown(evt); + }); + this.search_field.blur(function(evt) { + _this.input_blur(evt); + }); + this.search_field.keyup(function(evt) { + _this.keyup_checker(evt); + }); + this.search_field.keydown(function(evt) { + _this.keydown_checker(evt); + }); + this.search_field.focus(function(evt) { + _this.input_focus(evt); + }); + if (this.is_multiple) { + return this.search_choices.click(function(evt) { + _this.choices_click(evt); + }); + } else { + return this.container.click(function(evt) { + evt.preventDefault(); + }); + } + }; + + Chosen.prototype.search_field_disabled = function() { + this.is_disabled = this.form_field_jq[0].disabled; + if (this.is_disabled) { + this.container.addClass('chzn-disabled'); + this.search_field[0].disabled = true; + if (!this.is_multiple) { + this.selected_item.unbind("focus", this.activate_action); + } + return this.close_field(); + } else { + this.container.removeClass('chzn-disabled'); + this.search_field[0].disabled = false; + if (!this.is_multiple) { + return this.selected_item.bind("focus", this.activate_action); + } + } + }; + + Chosen.prototype.container_mousedown = function(evt) { + if (!this.is_disabled) { + if (evt && evt.type === "mousedown" && !this.results_showing) { + evt.preventDefault(); + } + if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) { + if (!this.active_field) { + if (this.is_multiple) { + this.search_field.val(""); + } + $(document).click(this.click_test_action); + this.results_show(); + } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chzn-single").length)) { + evt.preventDefault(); + this.results_toggle(); + } + return this.activate_field(); + } + } + }; + + Chosen.prototype.container_mouseup = function(evt) { + if (evt.target.nodeName === "ABBR" && !this.is_disabled) { + return this.results_reset(evt); + } + }; + + Chosen.prototype.search_results_mousewheel = function(evt) { + var delta, _ref, _ref1; + delta = -((_ref = evt.originalEvent) != null ? _ref.wheelDelta : void 0) || ((_ref1 = evt.originialEvent) != null ? _ref1.detail : void 0); + if (delta != null) { + evt.preventDefault(); + if (evt.type === 'DOMMouseScroll') { + delta = delta * 40; + } + return this.search_results.scrollTop(delta + this.search_results.scrollTop()); + } + }; + + Chosen.prototype.blur_test = function(evt) { + if (!this.active_field && this.container.hasClass("chzn-container-active")) { + return this.close_field(); + } + }; + + Chosen.prototype.close_field = function() { + $(document).unbind("click", this.click_test_action); + this.active_field = false; + this.results_hide(); + this.container.removeClass("chzn-container-active"); + this.winnow_results_clear(); + this.clear_backstroke(); + this.show_search_field_default(); + return this.search_field_scale(); + }; + + Chosen.prototype.activate_field = function() { + this.container.addClass("chzn-container-active"); + this.active_field = true; + this.search_field.val(this.search_field.val()); + return this.search_field.focus(); + }; + + Chosen.prototype.test_active_click = function(evt) { + if ($(evt.target).parents('#' + this.container_id).length) { + return this.active_field = true; + } else { + return this.close_field(); + } + }; + + Chosen.prototype.results_build = function() { + var content, data, _i, _len, _ref; + this.parsing = true; + this.results_data = root.SelectParser.select_to_array(this.form_field); + if (this.is_multiple && this.choices > 0) { + this.search_choices.find("li.search-choice").remove(); + this.choices = 0; + } else if (!this.is_multiple) { + this.selected_item.addClass("chzn-default").find("span").text(this.default_text); + if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) { + this.container.addClass("chzn-container-single-nosearch"); + } else { + this.container.removeClass("chzn-container-single-nosearch"); + } + } + content = ''; + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + if (data.group) { + content += this.result_add_group(data); + } else if (!data.empty) { + content += this.result_add_option(data); + if (data.selected && this.is_multiple) { + this.choice_build(data); + } else if (data.selected && !this.is_multiple) { + this.selected_item.removeClass("chzn-default").find("span").text(data.text); + if (this.allow_single_deselect) { + this.single_deselect_control_build(); + } + } + } + } + this.search_field_disabled(); + this.show_search_field_default(); + this.search_field_scale(); + this.search_results.html(content); + return this.parsing = false; + }; + + Chosen.prototype.result_add_group = function(group) { + if (!group.disabled) { + group.dom_id = this.container_id + "_g_" + group.array_index; + return '<li id="' + group.dom_id + '" class="group-result">' + $("<div />").text(group.label).html() + '</li>'; + } else { + return ""; + } + }; + + Chosen.prototype.result_do_highlight = function(el) { + var high_bottom, high_top, maxHeight, visible_bottom, visible_top; + if (el.length) { + this.result_clear_highlight(); + this.result_highlight = el; + this.result_highlight.addClass("highlighted"); + maxHeight = parseInt(this.search_results.css("maxHeight"), 10); + visible_top = this.search_results.scrollTop(); + visible_bottom = maxHeight + visible_top; + high_top = this.result_highlight.position().top + this.search_results.scrollTop(); + high_bottom = high_top + this.result_highlight.outerHeight(); + if (high_bottom >= visible_bottom) { + return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0); + } else if (high_top < visible_top) { + return this.search_results.scrollTop(high_top); + } + } + }; + + Chosen.prototype.result_clear_highlight = function() { + if (this.result_highlight) { + this.result_highlight.removeClass("highlighted"); + } + return this.result_highlight = null; + }; + + Chosen.prototype.results_show = function() { + if (this.result_single_selected != null) { + this.result_do_highlight(this.result_single_selected); + } else if (this.is_multiple && this.max_selected_options <= this.choices) { + this.form_field_jq.trigger("liszt:maxselected", { + chosen: this + }); + return false; + } + this.container.addClass("chzn-with-drop"); + this.form_field_jq.trigger("liszt:showing_dropdown", { + chosen: this + }); + this.results_showing = true; + this.search_field.focus(); + this.search_field.val(this.search_field.val()); + return this.winnow_results(); + }; + + Chosen.prototype.results_hide = function() { + this.result_clear_highlight(); + this.container.removeClass("chzn-with-drop"); + this.form_field_jq.trigger("liszt:hiding_dropdown", { + chosen: this + }); + return this.results_showing = false; + }; + + Chosen.prototype.set_tab_index = function(el) { + var ti; + if (this.form_field_jq.attr("tabindex")) { + ti = this.form_field_jq.attr("tabindex"); + this.form_field_jq.attr("tabindex", -1); + return this.search_field.attr("tabindex", ti); + } + }; + + Chosen.prototype.set_label_behavior = function() { + var _this = this; + this.form_field_label = this.form_field_jq.parents("label"); + if (!this.form_field_label.length && this.form_field.id.length) { + this.form_field_label = $("label[for=" + this.form_field.id + "]"); + } + if (this.form_field_label.length > 0) { + return this.form_field_label.click(function(evt) { + if (_this.is_multiple) { + return _this.container_mousedown(evt); + } else { + return _this.activate_field(); + } + }); + } + }; + + Chosen.prototype.show_search_field_default = function() { + if (this.is_multiple && this.choices < 1 && !this.active_field) { + this.search_field.val(this.default_text); + return this.search_field.addClass("default"); + } else { + this.search_field.val(""); + return this.search_field.removeClass("default"); + } + }; + + Chosen.prototype.search_results_mouseup = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target.length) { + this.result_highlight = target; + this.result_select(evt); + return this.search_field.focus(); + } + }; + + Chosen.prototype.search_results_mouseover = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target) { + return this.result_do_highlight(target); + } + }; + + Chosen.prototype.search_results_mouseout = function(evt) { + if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) { + return this.result_clear_highlight(); + } + }; + + Chosen.prototype.choice_build = function(item) { + var choice_id, html, link, + _this = this; + if (this.is_multiple && this.max_selected_options <= this.choices) { + this.form_field_jq.trigger("liszt:maxselected", { + chosen: this + }); + return false; + } + choice_id = this.container_id + "_c_" + item.array_index; + this.choices += 1; + if (item.disabled) { + html = '<li class="search-choice search-choice-disabled" id="' + choice_id + '"><span>' + item.html + '</span></li>'; + } else { + html = '<li class="search-choice" id="' + choice_id + '"><span>' + item.html + '</span><a href="javascript:void(0)" class="search-choice-close" rel="' + item.array_index + '"></a></li>'; + } + this.search_container.before(html); + link = $('#' + choice_id).find("a").first(); + return link.click(function(evt) { + return _this.choice_destroy_link_click(evt); + }); + }; + + Chosen.prototype.choice_destroy_link_click = function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (!this.is_disabled) { + return this.choice_destroy($(evt.target)); + } + }; + + Chosen.prototype.choice_destroy = function(link) { + if (this.result_deselect(link.attr("rel"))) { + this.choices -= 1; + this.show_search_field_default(); + if (this.is_multiple && this.choices > 0 && this.search_field.val().length < 1) { + this.results_hide(); + } + link.parents('li').first().remove(); + return this.search_field_scale(); + } + }; + + Chosen.prototype.results_reset = function() { + this.form_field.options[0].selected = true; + this.selected_item.find("span").text(this.default_text); + if (!this.is_multiple) { + this.selected_item.addClass("chzn-default"); + } + this.show_search_field_default(); + this.results_reset_cleanup(); + this.form_field_jq.trigger("change"); + if (this.active_field) { + return this.results_hide(); + } + }; + + Chosen.prototype.results_reset_cleanup = function() { + this.current_selectedIndex = this.form_field.selectedIndex; + return this.selected_item.find("abbr").remove(); + }; + + Chosen.prototype.result_select = function(evt) { + var high, high_id, item, position; + if (this.result_highlight) { + high = this.result_highlight; + high_id = high.attr("id"); + this.result_clear_highlight(); + if (this.is_multiple) { + this.result_deactivate(high); + } else { + this.search_results.find(".result-selected").removeClass("result-selected"); + this.result_single_selected = high; + this.selected_item.removeClass("chzn-default"); + } + high.addClass("result-selected"); + position = high_id.substr(high_id.lastIndexOf("_") + 1); + item = this.results_data[position]; + item.selected = true; + this.form_field.options[item.options_index].selected = true; + if (this.is_multiple) { + this.choice_build(item); + } else { + this.selected_item.find("span").first().text(item.text); + if (this.allow_single_deselect) { + this.single_deselect_control_build(); + } + } + if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) { + this.results_hide(); + } + this.search_field.val(""); + if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) { + this.form_field_jq.trigger("change", { + 'selected': this.form_field.options[item.options_index].value + }); + } + this.current_selectedIndex = this.form_field.selectedIndex; + return this.search_field_scale(); + } + }; + + Chosen.prototype.result_activate = function(el) { + return el.addClass("active-result"); + }; + + Chosen.prototype.result_deactivate = function(el) { + return el.removeClass("active-result"); + }; + + Chosen.prototype.result_deselect = function(pos) { + var result, result_data; + result_data = this.results_data[pos]; + if (!this.form_field.options[result_data.options_index].disabled) { + result_data.selected = false; + this.form_field.options[result_data.options_index].selected = false; + result = $("#" + this.container_id + "_o_" + pos); + result.removeClass("result-selected").addClass("active-result").show(); + this.result_clear_highlight(); + this.winnow_results(); + this.form_field_jq.trigger("change", { + deselected: this.form_field.options[result_data.options_index].value + }); + this.search_field_scale(); + return true; + } else { + return false; + } + }; + + Chosen.prototype.single_deselect_control_build = function() { + if (this.allow_single_deselect && this.selected_item.find("abbr").length < 1) { + return this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>"); + } + }; + + Chosen.prototype.winnow_results = function() { + var found, option, part, parts, regex, regexAnchor, result, result_id, results, searchText, startpos, text, zregex, _i, _j, _len, _len1, _ref; + this.no_results_clear(); + results = 0; + searchText = this.search_field.val() === this.default_text ? "" : $('<div/>').text($.trim(this.search_field.val())).html(); + regexAnchor = this.search_contains ? "" : "^"; + regex = new RegExp(regexAnchor + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); + zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + if (!option.disabled && !option.empty) { + if (option.group) { + $('#' + option.dom_id).css('display', 'none'); + } else if (!(this.is_multiple && option.selected)) { + found = false; + result_id = option.dom_id; + result = $("#" + result_id); + if (regex.test(option.html)) { + found = true; + results += 1; + } else if (this.enable_split_word_search && (option.html.indexOf(" ") >= 0 || option.html.indexOf("[") === 0)) { + parts = option.html.replace(/\[|\]/g, "").split(" "); + if (parts.length) { + for (_j = 0, _len1 = parts.length; _j < _len1; _j++) { + part = parts[_j]; + if (regex.test(part)) { + found = true; + results += 1; + } + } + } + } + if (found) { + if (searchText.length) { + startpos = option.html.search(zregex); + text = option.html.substr(0, startpos + searchText.length) + '</em>' + option.html.substr(startpos + searchText.length); + text = text.substr(0, startpos) + '<em>' + text.substr(startpos); + } else { + text = option.html; + } + result.html(text); + this.result_activate(result); + if (option.group_array_index != null) { + $("#" + this.results_data[option.group_array_index].dom_id).css('display', 'list-item'); + } + } else { + if (this.result_highlight && result_id === this.result_highlight.attr('id')) { + this.result_clear_highlight(); + } + this.result_deactivate(result); + } + } + } + } + if (results < 1 && searchText.length) { + return this.no_results(searchText); + } else { + return this.winnow_results_set_highlight(); + } + }; + + Chosen.prototype.winnow_results_clear = function() { + var li, lis, _i, _len, _results; + this.search_field.val(""); + lis = this.search_results.find("li"); + _results = []; + for (_i = 0, _len = lis.length; _i < _len; _i++) { + li = lis[_i]; + li = $(li); + if (li.hasClass("group-result")) { + _results.push(li.css('display', 'auto')); + } else if (!this.is_multiple || !li.hasClass("result-selected")) { + _results.push(this.result_activate(li)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Chosen.prototype.winnow_results_set_highlight = function() { + var do_high, selected_results; + if (!this.result_highlight) { + selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : []; + do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first(); + if (do_high != null) { + return this.result_do_highlight(do_high); + } + } + }; + + Chosen.prototype.no_results = function(terms) { + var no_results_html; + no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>'); + no_results_html.find("span").first().html(terms); + return this.search_results.append(no_results_html); + }; + + Chosen.prototype.no_results_clear = function() { + return this.search_results.find(".no-results").remove(); + }; + + Chosen.prototype.keydown_arrow = function() { + var first_active, next_sib; + if (!this.result_highlight) { + first_active = this.search_results.find("li.active-result").first(); + if (first_active) { + this.result_do_highlight($(first_active)); + } + } else if (this.results_showing) { + next_sib = this.result_highlight.nextAll("li.active-result").first(); + if (next_sib) { + this.result_do_highlight(next_sib); + } + } + if (!this.results_showing) { + return this.results_show(); + } + }; + + Chosen.prototype.keyup_arrow = function() { + var prev_sibs; + if (!this.results_showing && !this.is_multiple) { + return this.results_show(); + } else if (this.result_highlight) { + prev_sibs = this.result_highlight.prevAll("li.active-result"); + if (prev_sibs.length) { + return this.result_do_highlight(prev_sibs.first()); + } else { + if (this.choices > 0) { + this.results_hide(); + } + return this.result_clear_highlight(); + } + } + }; + + Chosen.prototype.keydown_backstroke = function() { + var next_available_destroy; + if (this.pending_backstroke) { + this.choice_destroy(this.pending_backstroke.find("a").first()); + return this.clear_backstroke(); + } else { + next_available_destroy = this.search_container.siblings("li.search-choice").last(); + if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) { + this.pending_backstroke = next_available_destroy; + if (this.single_backstroke_delete) { + return this.keydown_backstroke(); + } else { + return this.pending_backstroke.addClass("search-choice-focus"); + } + } + } + }; + + Chosen.prototype.clear_backstroke = function() { + if (this.pending_backstroke) { + this.pending_backstroke.removeClass("search-choice-focus"); + } + return this.pending_backstroke = null; + }; + + Chosen.prototype.keydown_checker = function(evt) { + var stroke, _ref; + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + if (stroke !== 8 && this.pending_backstroke) { + this.clear_backstroke(); + } + switch (stroke) { + case 8: + this.backstroke_length = this.search_field.val().length; + break; + case 9: + if (this.results_showing && !this.is_multiple) { + this.result_select(evt); + } + this.mouse_on_container = false; + break; + case 13: + evt.preventDefault(); + break; + case 38: + evt.preventDefault(); + this.keyup_arrow(); + break; + case 40: + this.keydown_arrow(); + break; + } + }; + + Chosen.prototype.search_field_scale = function() { + var div, h, style, style_block, styles, w, _i, _len; + if (this.is_multiple) { + h = 0; + w = 0; + style_block = "position:absolute; left: -1000px; top: -1000px; display:none;"; + styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing']; + for (_i = 0, _len = styles.length; _i < _len; _i++) { + style = styles[_i]; + style_block += style + ":" + this.search_field.css(style) + ";"; + } + div = $('<div />', { + 'style': style_block + }); + div.text(this.search_field.val()); + $('body').append(div); + w = div.width() + 25; + div.remove(); + if (!this.f_width) { + this.f_width = this.container.outerWidth(); + } + if (w > this.f_width - 10) { + w = this.f_width - 10; + } + return this.search_field.css({ + 'width': w + 'px' + }); + } + }; + + Chosen.prototype.generate_random_id = function() { + var string; + string = "sel" + this.generate_random_char() + this.generate_random_char() + this.generate_random_char(); + while ($("#" + string).length > 0) { + string += this.generate_random_char(); + } + return string; + }; + + return Chosen; + + })(AbstractChosen); + + root.Chosen = Chosen; + +}).call(this); diff --git a/resources/jquery.tipsy/images/tipsy.png b/resources/jquery.tipsy/images/tipsy.png Binary files differindex fef8c4b5..ef17cc32 100644 --- a/resources/jquery.tipsy/images/tipsy.png +++ b/resources/jquery.tipsy/images/tipsy.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_flat_0_aaaaaa_40x100.png b/resources/jquery.ui/themes/default/images/ui-bg_flat_0_aaaaaa_40x100.png Binary files differindex 5b5dab2a..e425e6e4 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_flat_0_aaaaaa_40x100.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_flat_75_ffffff_40x100.png b/resources/jquery.ui/themes/default/images/ui-bg_flat_75_ffffff_40x100.png Binary files differindex ac8b229a..72d47573 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_flat_75_ffffff_40x100.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_glass_55_fbf9ee_1x400.png b/resources/jquery.ui/themes/default/images/ui-bg_glass_55_fbf9ee_1x400.png Binary files differindex ad3d6346..3b2914a2 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_glass_55_fbf9ee_1x400.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_glass_65_ffffff_1x400.png b/resources/jquery.ui/themes/default/images/ui-bg_glass_65_ffffff_1x400.png Binary files differindex 42ccba26..8569c1bc 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_glass_65_ffffff_1x400.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_glass_75_dadada_1x400.png b/resources/jquery.ui/themes/default/images/ui-bg_glass_75_dadada_1x400.png Binary files differindex 5a46b47c..d6cc3c58 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_glass_75_dadada_1x400.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_glass_75_dadada_1x400.png diff --git a/resources/jquery.ui/themes/default/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/resources/jquery.ui/themes/default/images/ui-bg_highlight-soft_75_cccccc_1x100.png Binary files differindex 7c9fa6c6..3cd467e1 100644 --- a/resources/jquery.ui/themes/default/images/ui-bg_highlight-soft_75_cccccc_1x100.png +++ b/resources/jquery.ui/themes/default/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/resources/jquery.ui/themes/default/images/ui-icons_222222_256x240.png b/resources/jquery.ui/themes/default/images/ui-icons_222222_256x240.png Binary files differindex ee039dc0..9a9606f7 100644 --- a/resources/jquery.ui/themes/default/images/ui-icons_222222_256x240.png +++ b/resources/jquery.ui/themes/default/images/ui-icons_222222_256x240.png diff --git a/resources/jquery.ui/themes/default/images/ui-icons_2e83ff_256x240.png b/resources/jquery.ui/themes/default/images/ui-icons_2e83ff_256x240.png Binary files differindex 45e8928e..08d26179 100644 --- a/resources/jquery.ui/themes/default/images/ui-icons_2e83ff_256x240.png +++ b/resources/jquery.ui/themes/default/images/ui-icons_2e83ff_256x240.png diff --git a/resources/jquery.ui/themes/default/images/ui-icons_454545_256x240.png b/resources/jquery.ui/themes/default/images/ui-icons_454545_256x240.png Binary files differindex 7ec70d11..80cb644a 100644 --- a/resources/jquery.ui/themes/default/images/ui-icons_454545_256x240.png +++ b/resources/jquery.ui/themes/default/images/ui-icons_454545_256x240.png diff --git a/resources/jquery.ui/themes/default/images/ui-icons_888888_256x240.png b/resources/jquery.ui/themes/default/images/ui-icons_888888_256x240.png Binary files differindex 5ba708c3..8373712d 100644 --- a/resources/jquery.ui/themes/default/images/ui-icons_888888_256x240.png +++ b/resources/jquery.ui/themes/default/images/ui-icons_888888_256x240.png diff --git a/resources/jquery.ui/themes/default/images/ui-icons_cd0a0a_256x240.png b/resources/jquery.ui/themes/default/images/ui-icons_cd0a0a_256x240.png Binary files differindex 7930a558..34fc8937 100644 --- a/resources/jquery.ui/themes/default/images/ui-icons_cd0a0a_256x240.png +++ b/resources/jquery.ui/themes/default/images/ui-icons_cd0a0a_256x240.png diff --git a/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png b/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png Binary files differdeleted file mode 100644 index 8f7cf74e..00000000 --- a/resources/jquery.ui/themes/vector/images/button-blue-hover-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-blue-hover.png b/resources/jquery.ui/themes/vector/images/button-blue-hover.png Binary files differdeleted file mode 100644 index 1fc28163..00000000 --- a/resources/jquery.ui/themes/vector/images/button-blue-hover.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-blue-large.png b/resources/jquery.ui/themes/vector/images/button-blue-large.png Binary files differdeleted file mode 100644 index 22ee5e59..00000000 --- a/resources/jquery.ui/themes/vector/images/button-blue-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-blue.png b/resources/jquery.ui/themes/vector/images/button-blue.png Binary files differdeleted file mode 100644 index 2e6d121a..00000000 --- a/resources/jquery.ui/themes/vector/images/button-blue.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-blue.png b/resources/jquery.ui/themes/vector/images/button-disabled-blue.png Binary files differdeleted file mode 100644 index 28eb1fcd..00000000 --- a/resources/jquery.ui/themes/vector/images/button-disabled-blue.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-green.png b/resources/jquery.ui/themes/vector/images/button-disabled-green.png Binary files differdeleted file mode 100644 index 0e29d85b..00000000 --- a/resources/jquery.ui/themes/vector/images/button-disabled-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-disabled-red.png b/resources/jquery.ui/themes/vector/images/button-disabled-red.png Binary files differdeleted file mode 100644 index ede69988..00000000 --- a/resources/jquery.ui/themes/vector/images/button-disabled-red.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-disabled.png b/resources/jquery.ui/themes/vector/images/button-disabled.png Binary files differdeleted file mode 100644 index e4e4ec1c..00000000 --- a/resources/jquery.ui/themes/vector/images/button-disabled.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-down-blue.png b/resources/jquery.ui/themes/vector/images/button-down-blue.png Binary files differdeleted file mode 100644 index 766e5746..00000000 --- a/resources/jquery.ui/themes/vector/images/button-down-blue.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-down-green.png b/resources/jquery.ui/themes/vector/images/button-down-green.png Binary files differdeleted file mode 100644 index 90969c39..00000000 --- a/resources/jquery.ui/themes/vector/images/button-down-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-down-red.png b/resources/jquery.ui/themes/vector/images/button-down-red.png Binary files differdeleted file mode 100644 index f6257298..00000000 --- a/resources/jquery.ui/themes/vector/images/button-down-red.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-down.png b/resources/jquery.ui/themes/vector/images/button-down.png Binary files differdeleted file mode 100644 index c6467571..00000000 --- a/resources/jquery.ui/themes/vector/images/button-down.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-green-hover-large.png b/resources/jquery.ui/themes/vector/images/button-green-hover-large.png Binary files differdeleted file mode 100644 index dd8851e1..00000000 --- a/resources/jquery.ui/themes/vector/images/button-green-hover-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-green-hover.png b/resources/jquery.ui/themes/vector/images/button-green-hover.png Binary files differdeleted file mode 100644 index 19c39911..00000000 --- a/resources/jquery.ui/themes/vector/images/button-green-hover.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-green-large.png b/resources/jquery.ui/themes/vector/images/button-green-large.png Binary files differdeleted file mode 100644 index a8e830eb..00000000 --- a/resources/jquery.ui/themes/vector/images/button-green-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-green.png b/resources/jquery.ui/themes/vector/images/button-green.png Binary files differdeleted file mode 100644 index 54c418e9..00000000 --- a/resources/jquery.ui/themes/vector/images/button-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png b/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png Binary files differdeleted file mode 100644 index f76f7b05..00000000 --- a/resources/jquery.ui/themes/vector/images/button-large-disabled-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-large-off-green.png b/resources/jquery.ui/themes/vector/images/button-large-off-green.png Binary files differdeleted file mode 100644 index f997431b..00000000 --- a/resources/jquery.ui/themes/vector/images/button-large-off-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-off-blue.png b/resources/jquery.ui/themes/vector/images/button-off-blue.png Binary files differdeleted file mode 100644 index 82dedb53..00000000 --- a/resources/jquery.ui/themes/vector/images/button-off-blue.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-off-green.png b/resources/jquery.ui/themes/vector/images/button-off-green.png Binary files differdeleted file mode 100644 index 109907f0..00000000 --- a/resources/jquery.ui/themes/vector/images/button-off-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-off-red.png b/resources/jquery.ui/themes/vector/images/button-off-red.png Binary files differdeleted file mode 100644 index 5a85b8aa..00000000 --- a/resources/jquery.ui/themes/vector/images/button-off-red.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-off.png b/resources/jquery.ui/themes/vector/images/button-off.png Binary files differdeleted file mode 100644 index cc5eb119..00000000 --- a/resources/jquery.ui/themes/vector/images/button-off.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png b/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png Binary files differdeleted file mode 100644 index 6f0dbd5d..00000000 --- a/resources/jquery.ui/themes/vector/images/button-orange-hover-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-orange-hover.png b/resources/jquery.ui/themes/vector/images/button-orange-hover.png Binary files differdeleted file mode 100644 index a1077c58..00000000 --- a/resources/jquery.ui/themes/vector/images/button-orange-hover.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-orange-large.png b/resources/jquery.ui/themes/vector/images/button-orange-large.png Binary files differdeleted file mode 100644 index 3d7f37f6..00000000 --- a/resources/jquery.ui/themes/vector/images/button-orange-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-orange.png b/resources/jquery.ui/themes/vector/images/button-orange.png Binary files differdeleted file mode 100644 index 02347cf9..00000000 --- a/resources/jquery.ui/themes/vector/images/button-orange.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-over-blue.png b/resources/jquery.ui/themes/vector/images/button-over-blue.png Binary files differdeleted file mode 100644 index 9edb7aa2..00000000 --- a/resources/jquery.ui/themes/vector/images/button-over-blue.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-over-green.png b/resources/jquery.ui/themes/vector/images/button-over-green.png Binary files differdeleted file mode 100644 index 47a0b1b8..00000000 --- a/resources/jquery.ui/themes/vector/images/button-over-green.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-over-red.png b/resources/jquery.ui/themes/vector/images/button-over-red.png Binary files differdeleted file mode 100644 index a2445365..00000000 --- a/resources/jquery.ui/themes/vector/images/button-over-red.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-over.png b/resources/jquery.ui/themes/vector/images/button-over.png Binary files differdeleted file mode 100644 index 558f1f80..00000000 --- a/resources/jquery.ui/themes/vector/images/button-over.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-red-hover-large.png b/resources/jquery.ui/themes/vector/images/button-red-hover-large.png Binary files differdeleted file mode 100644 index 11ccef06..00000000 --- a/resources/jquery.ui/themes/vector/images/button-red-hover-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-red-hover.png b/resources/jquery.ui/themes/vector/images/button-red-hover.png Binary files differdeleted file mode 100644 index 55a61743..00000000 --- a/resources/jquery.ui/themes/vector/images/button-red-hover.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-red-large.png b/resources/jquery.ui/themes/vector/images/button-red-large.png Binary files differdeleted file mode 100644 index 8d089efd..00000000 --- a/resources/jquery.ui/themes/vector/images/button-red-large.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/button-red.png b/resources/jquery.ui/themes/vector/images/button-red.png Binary files differdeleted file mode 100644 index 7a292fc1..00000000 --- a/resources/jquery.ui/themes/vector/images/button-red.png +++ /dev/null diff --git a/resources/jquery.ui/themes/vector/images/titlebar-fade.png b/resources/jquery.ui/themes/vector/images/titlebar-fade.png Binary files differindex f9fde8b3..12a80c88 100644 --- a/resources/jquery.ui/themes/vector/images/titlebar-fade.png +++ b/resources/jquery.ui/themes/vector/images/titlebar-fade.png diff --git a/resources/jquery.ui/themes/vector/jquery.ui.button.css b/resources/jquery.ui/themes/vector/jquery.ui.button.css index a6a1b544..ea14723f 100644 --- a/resources/jquery.ui/themes/vector/jquery.ui.button.css +++ b/resources/jquery.ui/themes/vector/jquery.ui.button.css @@ -12,23 +12,12 @@ 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; + text-shadow: 0 1px 1px #fff; } .ui-button-text-only .ui-button-text { padding: 0.3em 1em 0.25em 1em; @@ -90,7 +79,7 @@ input.ui-button { } .ui-buttonset .ui-button { margin-left: 0; - margin-right: -.3em; + margin-right: -.4em; } /* workarounds */ @@ -98,17 +87,21 @@ 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; - border: 1px solid #a6a6a6 !important; +/* Disables the annoying dashed border Firefox puts on active buttons */ +body button.ui-button::-moz-focus-inner { + border: 0; +} +/* Give large buttons some extra padding */ +body .ui-button-large { + padding: 5px; +} +/* Use white icons for colored buttons */ +.ui-button-green .ui-icon, +.ui-button-blue .ui-icon, +.ui-button-red .ui-icon, +.ui-button-orange .ui-icon { /* @embed */ - background: #f2f2f2 url(images/button-off.png) repeat-x scroll 50% 100% !important; - cursor: pointer; - font-size: 1em; - line-height: 1.4em; - width: auto; - overflow: visible; + background-image: url(images/ui-icons_ffffff_256x240.png) !important; } /* Corner radius */ @@ -124,6 +117,7 @@ body .ui-button { } .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; @@ -147,145 +141,239 @@ body .ui-button { border-bottom-right-radius: 4px; } -body .ui-button:hover { - border-color: #6e7273; - /* @embed */ - background: #e1e1e1 url(images/button-over.png) repeat-x scroll 50% 100% !important; -} -body .ui-button:active, -body .ui-button:focus { - border-color: #707271; - /* @embed */ - background: #bfbfbf url(images/button-down.png) repeat-x scroll 50% 100% !important; +body .ui-button { + color: #2779aa; + margin: 0.5em 0 0.5em 0.4em; + border: 1px solid #aaa !important; + background: #f0f0f0 !important; + background: -moz-linear-gradient(top, #fff 0%, #ddd 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #fff 0%, #ddd 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #fff 0%, #ddd 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #fff 0%, #ddd 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #fff 0%, #ddd 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dddddd', GradientType=0); /* IE6-8 */ + cursor: pointer; + font-size: 1em; + line-height: 1.4em; + width: auto; + overflow: visible; + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.2); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.2); + box-shadow: 0 1px 3px rgba(0,0,0,.2); } -body .ui-button.disabled { - color: #7f7f7f; - border-color: #cccccc; - /* @embed */ - background: #f2f2f2 url(images/button-disabled.png) repeat-x scroll 50% 100% !important; + +body .ui-button-icon-only { + width: 2.2em; } -/* Disables the annoying dashed border Firefox puts on active buttons */ -body button.ui-button::-moz-focus-inner { - border: 0; + +body .ui-button-icons-only { + width: 3.4em; } -/* Give large buttons some extra padding */ -body .ui-button-large { - padding: 5px; + +body .ui-button:hover { + color: #2779aa; + border-color: #bbb !important; + background: #fff !important; + background: -moz-linear-gradient(top, #fff 0%, #eee 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #fff 0%, #eee 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #fff 0%, #eee 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #fff 0%, #eee 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #fff 0%, #eee 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.1); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.1); + box-shadow: 0 1px 3px rgba(0,0,0,.1); } -/* Use white icons for colored buttons */ -.ui-button-green .ui-icon, .ui-button-blue .ui-icon, .ui-button-red .ui-icon, .ui-button-orange .ui-icon { - /* @embed */ - background-image: url(images/ui-icons_ffffff_256x240.png) !important; +body .ui-button:active, +body .ui-button:focus { + border-color: #8ad !important; + -webkit-box-shadow: 0 0 1px 1px rgba(167,215,249,.5); + -moz-box-shadow: 0 0 1px 1px rgba(167,215,249,.5); + box-shadow: 0 0 1px 1px rgba(167,215,249,.5); +} +body .ui-button:active { + background: #e0e0e0 !important; + background: -moz-linear-gradient(top, #f0f0f0 0%, #d0d0d0 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #f0f0f0 0%, #d0d0d0 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #f0f0f0 0%, #d0d0d0 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #f0f0f0 0%, #d0d0d0 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #f0f0f0 0%, #d0d0d0 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f0f0f0', endColorstr='#d0d0d0', GradientType=0); /* IE6-8 */ } /* Green buttons */ - +body .ui-button-green, +body .ui-button-green .ui-button-text { + color: white; + text-shadow: 0 -1px 1px #072; +} body .ui-button.ui-button-green { - color: white !important; - border-color: #97af7e !important; - /* @embed */ - background: #3cb677 url(images/button-green.png) repeat-x scroll 50% 100% !important; + border-color: #294 !important; + background: #295 !important; + background: -moz-linear-gradient(top, #3c8 0%, #295 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #3c8 0%, #295 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #3c8 0%, #295 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #3c8 0%, #295 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #3c8 0%, #295 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#33cc88', endColorstr='#229955', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.3); + box-shadow: 0 1px 3px rgba(0,0,0,.3); } body .ui-button.ui-button-green:hover { - border-color: #778e61 !important; - /* @embed */ - background: #339b65 url(images/button-green-hover.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-green.ui-button-large { - /* @embed */ - background: #3cb677 url(images/button-green-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-green.ui-button-large:hover { - /* @embed */ - background: #339b65 url(images/button-green-hover-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-green.disabled { - filter:alpha(opacity=50); - -moz-opacity:0.50; - -khtml-opacity: 0.50; - opacity: 0.50; + background: #33a055 !important; + background: -moz-linear-gradient(top, #44d388 0%, #33a055 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #44d388 0%, #33a055 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #44d388 0%, #33a055 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #44d388 0%, #33a055 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #44d388 0%, #33a055 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#44d388', endColorstr='#33a055', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.25); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.25); + box-shadow: 0 1px 3px rgba(0,0,0,.25); +} +body .ui-button.ui-button-green:active, +body .ui-button.ui-button-green:focus { + border-color: #172 !important; + -webkit-box-shadow: 0 0 2px 2px rgba(167,215,249,.75); + -moz-box-shadow: 0 0 2px 2px rgba(167,215,249,.75); + box-shadow: 0 0 2px 2px rgba(167,215,249,.75); +} +body .ui-button.ui-button-green:active { + background: #338855 !important; + background: -moz-linear-gradient(top, #30c080 0%, #338855 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #30c080 0%, #338855 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #30c080 0%, #338855 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #30c080 0%, #338855 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #30c080 0%, #338855 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#30c080', endColorstr='#338855', GradientType=0); /* IE6-8 */ } /* Blue buttons */ - +body .ui-button-blue, +body .ui-button-blue .ui-button-text { + color: white; + text-shadow: 0 -1px 1px #037; +} body .ui-button.ui-button-blue { - color: white !important; - border-color: #628acb !important; - /* @embed */ - background: #3365ba url(images/button-blue.png) repeat-x scroll 50% 100% !important; + border-color: #468 !important; + background: #36b !important; + background: -moz-linear-gradient(top, #48e 0%, #36b 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #48e 0%, #36b 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #48e 0%, #36b 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #48e 0%, #36b 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #48e 0%, #36b 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4488ee', endColorstr='#3366bb', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.35); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.35); + box-shadow: 0 1px 3px rgba(0,0,0,.35); } body .ui-button.ui-button-blue:hover { - border-color: #5375ad !important; - /* @embed */ - background: #2b569e url(images/button-blue-hover.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-blue.ui-button-large { - /* @embed */ - background: #3365ba url(images/button-blue-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-blue.ui-button-large:hover { - /* @embed */ - background: #2b569e url(images/button-blue-hover-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-blue.disabled { - filter:alpha(opacity=50); - -moz-opacity:0.50; - -khtml-opacity: 0.50; - opacity: 0.50; + background: #36c !important; + background: -moz-linear-gradient(top, #59e 0%, #36c 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #59e 0%, #36c 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #59e 0%, #36c 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #59e 0%, #36c 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #59e 0%, #36c 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5599ee', endColorstr='#3366cc', GradientType=0); /* IE6-8 */ +} +body .ui-button.ui-button-blue:active, +body .ui-button.ui-button-blue:focus { + border-color: #357 !important; + -webkit-box-shadow: 0 0 2px 2px rgba(167,215,249,.75); + -moz-box-shadow: 0 0 2px 2px rgba(167,215,249,.75); + box-shadow: 0 0 2px 2px rgba(167,215,249,.75); +} +body .ui-button.ui-button-blue:active { + background: #3060a0 !important; + background: -moz-linear-gradient(top, #4080e0 0%, #3060a0 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #4080e0 0%, #3060a0 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #4080e0 0%, #3060a0 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #4080e0 0%, #3060a0 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #4080e0 0%, #3060a0 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4080e0', endColorstr='#3060a0', GradientType=0); /* IE6-8 */ } /* Red buttons */ - +body .ui-button-red, +body .ui-button-red .ui-button-text { + color: white; + text-shadow: 0 -1px 1px #700; +} body .ui-button.ui-button-red { - color: white !important; - border-color: #af977e !important; - /* @embed */ - background: #cb0000 url(images/button-red.png) repeat-x scroll 50% 100% !important; + border-color: #944 !important; + background: #a22 !important; + background: -moz-linear-gradient(top, #d44 0%, #a22 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #d44 0%, #a22 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #d44 0%, #a22 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #d44 0%, #a22 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #d44 0%, #a22 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#dd4444', endColorstr='#aa2222', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.35); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.35); + box-shadow: 0 1px 3px rgba(0,0,0,.35); } body .ui-button.ui-button-red:hover { - border-color: #8e7761 !important; - /* @embed */ - background: #ad0000 url(images/button-red-hover.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-red.ui-button-large { - /* @embed */ - background: #cb0000 url(images/button-red.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-red.ui-button-large:hover { - /* @embed */ - background: #ad0000 url(images/button-red-hover.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-red.disabled { - filter:alpha(opacity=50); - -moz-opacity:0.50; - -khtml-opacity: 0.50; - opacity: 0.50; + border-color: #a44 !important; + background: #b03333 !important; + background: -moz-linear-gradient(top, #ee4646 0%, #b03333 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #ee4646 0%, #b03333 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #ee4646 0%, #b03333 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ee4646 0%, #b03333 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #ee4646 0%, #b03333 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee4646', endColorstr='#b03333', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.3); + box-shadow: 0 1px 3px rgba(0,0,0,.3); +} +body .ui-button.ui-button-red:active, +body .ui-button.ui-button-red:focus { + border-color: #747 !important; + -webkit-box-shadow: 0 0 2px 2px rgba(167,215,249,.7); + -moz-box-shadow: 0 0 2px 2px rgba(167,215,249,.7); + box-shadow: 0 0 2px 2px rgba(167,215,249,.7); +} +body .ui-button.ui-button-red:active { + background: #952020 !important; + background: -moz-linear-gradient(top, #d04545 0%, #952020 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #d04545 0%, #952020 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #d04545 0%, #952020 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #d04545 0%, #952020 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #d04545 0%, #952020 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d04545', endColorstr='#952020', GradientType=0); /* IE6-8 */ } -/* Orange buttons */ - -body .ui-button.ui-button-orange { - color: white !important; - border-color: #f3a863 !important; - /* @embed */ - background: #f07f14 url(images/button-orange.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-orange:hover { - border-color: #ce9055 !important; - /* @embed */ - background: #cc6c11 url(images/button-orange-hover.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-orange.ui-button-large { - /* @embed */ - background: #f07f14 url(images/button-orange-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-orange.ui-button-large:hover { - /* @embed */ - background: #cc6c11 url(images/button-orange-hover-large.png) repeat-x scroll 50% 100% !important; -} -body .ui-button.ui-button-orange.disabled { - filter:alpha(opacity=50); - -moz-opacity:0.50; - -khtml-opacity: 0.50; - opacity: 0.50; +/* Disabled buttons */ +body .ui-button-green.disabled, +body .ui-button-green.disabled:hover, +body .ui-button-green.disabled:active, +body .ui-button-green.disabled:focus, +body .ui-button-blue.disabled, +body .ui-button-blue.disabled:hover, +body .ui-button-blue.disabled:active, +body .ui-button-blue.disabled:focus, +body .ui-button-red.disabled, +body .ui-button-red.disabled:hover, +body .ui-button-red.disabled:active, +body .ui-button-red.disabled:focus, +body .ui-button.disabled, +body .ui-button.disabled:hover { + color: #aaa; + border-color: #ccc !important; + background: #eee !important; + background: -moz-linear-gradient(top, #f6f6f6 0%, #eee 90%) !important; /* FF3.6+ */ + background: -webkit-linear-gradient(top, #f6f6f6 0%, #eee 90%) !important; /* Chrome10+, Safari5.1+ */ + background: -o-linear-gradient(top, #f6f6f6 0%, #eee 90%) !important; /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #f6f6f6 0%, #eee 90%) !important; /* IE10+ */ + background: linear-gradient(to bottom, #f6f6f6 0%, #eee 90%) !important; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#eeeeee', GradientType=0); /* IE6-8 */ + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,0); + box-shadow: 0 1px 3px rgba(0,0,0,0); +} +body .ui-button-green.disabled .ui-button-text, +body .ui-button-blue.disabled .ui-button-text, +body .ui-button-red.disabled .ui-button-text { + color: #aaa; + text-shadow: 0 1px 1px #fff; } diff --git a/resources/jquery/images/jquery.arrowSteps.divider-ltr.png b/resources/jquery/images/jquery.arrowSteps.divider-ltr.png Binary files differindex 83d6ff84..84ed2a2d 100644 --- a/resources/jquery/images/jquery.arrowSteps.divider-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.divider-ltr.png diff --git a/resources/jquery/images/jquery.arrowSteps.divider-rtl.png b/resources/jquery/images/jquery.arrowSteps.divider-rtl.png Binary files differindex 529d7b84..7cfbfeba 100644 --- a/resources/jquery/images/jquery.arrowSteps.divider-rtl.png +++ b/resources/jquery/images/jquery.arrowSteps.divider-rtl.png diff --git a/resources/jquery/images/jquery.arrowSteps.head-ltr.png b/resources/jquery/images/jquery.arrowSteps.head-ltr.png Binary files differindex 3289617d..eb070280 100644 --- a/resources/jquery/images/jquery.arrowSteps.head-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.head-ltr.png diff --git a/resources/jquery/images/jquery.arrowSteps.head-rtl.png b/resources/jquery/images/jquery.arrowSteps.head-rtl.png Binary files differindex 3d9f70cb..7ea2fdb5 100644 --- a/resources/jquery/images/jquery.arrowSteps.head-rtl.png +++ b/resources/jquery/images/jquery.arrowSteps.head-rtl.png diff --git a/resources/jquery/images/jquery.arrowSteps.tail-ltr.png b/resources/jquery/images/jquery.arrowSteps.tail-ltr.png Binary files differindex 92b872b2..3ad990b6 100644 --- a/resources/jquery/images/jquery.arrowSteps.tail-ltr.png +++ b/resources/jquery/images/jquery.arrowSteps.tail-ltr.png diff --git a/resources/jquery/images/marker.png b/resources/jquery/images/marker.png Binary files differindex 3929bbb5..19efb6ce 100644 --- a/resources/jquery/images/marker.png +++ b/resources/jquery/images/marker.png diff --git a/resources/jquery/images/mask.png b/resources/jquery/images/mask.png Binary files differindex b0a4d406..fe08de0e 100644 --- a/resources/jquery/images/mask.png +++ b/resources/jquery/images/mask.png diff --git a/resources/jquery/jquery.badge.css b/resources/jquery/jquery.badge.css index d961bf3d..f313663e 100644 --- a/resources/jquery/jquery.badge.css +++ b/resources/jquery/jquery.badge.css @@ -1,13 +1,12 @@ .mw-badge { 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; + cursor: pointer; } .mw-badge-content { @@ -18,8 +17,12 @@ } .mw-badge-inline { - display: inline-block; margin-left: 3px; + display: inline-block; + /* Hack for IE6 and IE7 (bug 47926) */ + zoom: 1; + *display: inline; + } .mw-badge-overlay { position: absolute; diff --git a/resources/jquery/jquery.byteLength.js b/resources/jquery/jquery.byteLength.js index 3d5b7206..398937e6 100644 --- a/resources/jquery/jquery.byteLength.js +++ b/resources/jquery/jquery.byteLength.js @@ -4,6 +4,8 @@ * Calculate the byte length of a string (accounting for UTF-8). * * @author Jan Paul Posma, 2011 + * @author Timo Tijhof, 2012 + * @author David Chan, 2013 */ jQuery.byteLength = function ( str ) { @@ -12,8 +14,18 @@ jQuery.byteLength = function ( str ) { // Note, surrogate (\uD800-\uDFFF) characters are counted as 2 bytes, since there's two of them // and the actual character takes 4 bytes in UTF-8 (2*2=4). Might not work perfectly in // edge cases such as illegal sequences, but that should never happen. + + // https://en.wikipedia.org/wiki/UTF-8#Description + // The mapping from UTF-16 code units to UTF-8 bytes is as follows: + // > Range 0000-007F: codepoints that become 1 byte of UTF-8 + // > Range 0080-07FF: codepoints that become 2 bytes of UTF-8 + // > Range 0800-D7FF: codepoints that become 3 bytes of UTF-8 + // > Range D800-DFFF: Surrogates (each pair becomes 4 bytes of UTF-8) + // > Range E000-FFFF: codepoints that become 3 bytes of UTF-8 (continued) + return str .replace( /[\u0080-\u07FF\uD800-\uDFFF]/g, '**' ) .replace( /[\u0800-\uD7FF\uE000-\uFFFF]/g, '***' ) .length; + }; diff --git a/resources/jquery/jquery.byteLimit.js b/resources/jquery/jquery.byteLimit.js index f2b98f09..a8c0b065 100644 --- a/resources/jquery/jquery.byteLimit.js +++ b/resources/jquery/jquery.byteLimit.js @@ -78,7 +78,8 @@ // Chop off characters from the end of the "inserted content" string // until the limit is statisfied. if ( fn ) { - while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit ) { + // stop, when there is nothing to slice - bug 41450 + while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[1].length > 0 ) { inpParts[1] = inpParts[1].slice( 0, -1 ); } } else { diff --git a/resources/jquery/jquery.checkboxShiftClick.js b/resources/jquery/jquery.checkboxShiftClick.js index aced0633..b2065665 100644 --- a/resources/jquery/jquery.checkboxShiftClick.js +++ b/resources/jquery/jquery.checkboxShiftClick.js @@ -15,11 +15,17 @@ $box.click( function ( e ) { // And one has been clicked before... if ( prevCheckbox !== null && e.shiftKey ) { - // Check or uncheck this one and all in-between checkboxes - $box.slice( - Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ), - Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1 - ).prop( 'checked', !!e.target.checked ); + // Check or uncheck this one and all in-between checkboxes, + // except for disabled ones + $box + .slice( + Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ), + Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1 + ) + .filter( function () { + return !this.disabled; + } ) + .prop( 'checked', !!e.target.checked ); } // Either way, update the prevCheckbox variable to the one clicked now prevCheckbox = e.target; diff --git a/resources/jquery/jquery.client.js b/resources/jquery/jquery.client.js index b0bd6850..5a95dc5b 100644 --- a/resources/jquery/jquery.client.js +++ b/resources/jquery/jquery.client.js @@ -6,7 +6,7 @@ /* Private Members */ /** - * @var profileCache {Object} Keyed by userAgent string, + * @var {Object} profileCache Keyed by userAgent string, * value is the parsed $.client.profile object for that user agent. */ var profileCache = {}; @@ -18,9 +18,9 @@ /** * Get an object containing information about the client. * - * @param nav {Object} An object with atleast a 'userAgent' and 'platform' key. + * @param {Object} nav An object with atleast a 'userAgent' and 'platform' key. * Defaults to the global Navigator object. - * @return {Object} The resulting client object will be in the following format: + * @returns {Object} The resulting client object will be in the following format: * { * 'name': 'firefox', * 'layout': 'gecko', @@ -50,47 +50,47 @@ // Generic version digit x = 'x', // Strings found in user agent strings that need to be conformed - wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3'], + wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3', 'Iceweasel'], // Translations for conforming user agent strings userAgentTranslations = [ // Tons of browsers lie about being something they are not - [/(Firefox|MSIE|KHTML,\slike\sGecko|Konqueror)/, ''], + [/(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 + // This helps keep different versions consistent ['Navigator', 'Netscape'], // This prevents version extraction issues, otherwise translation would happen later ['PLAYSTATION 3', 'PS3'] ], - // Strings which precede a version number in a user agent string - combined and used as match 1 in - // version detectection + // Strings which precede a version number in a user agent string - combined and used as + // match 1 in version detection versionPrefixes = [ 'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'netscape6', 'opera', 'version', 'konqueror', - 'lynx', 'msie', 'safari', 'ps3' + 'lynx', 'msie', 'safari', 'ps3', 'android' ], // Used as matches 2, 3 and 4 in version extraction - 3 is used as actual version number versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)', // Names of known browsers names = [ 'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', - 'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq' + 'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq', 'android' ], // Tanslations for conforming browser names nameTranslations = [], // Names of known layout engines - layouts = ['gecko', 'konqueror', 'msie', 'opera', 'webkit'], + layouts = ['gecko', 'konqueror', 'msie', 'trident', 'opera', 'webkit'], // Translations for conforming layout names layoutTranslations = [ ['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto'] ], // Names of supported layout engines for version number - layoutVersions = ['applewebkit', 'gecko'], + layoutVersions = ['applewebkit', 'gecko', 'trident'], // Names of known operating systems - platforms = ['win', 'mac', 'linux', 'sunos', 'solaris', 'iphone'], + platforms = ['win', 'wow64', 'mac', 'linux', 'sunos', 'solaris', 'iphone'], // Translations for conforming operating system names - platformTranslations = [ ['sunos', 'solaris'] ], + platformTranslations = [ ['sunos', 'solaris'], ['wow64', 'win'] ], /* Methods */ @@ -143,18 +143,33 @@ /* Edge Cases -- did I mention about how user agent string lie? */ // Decode Safari's crazy 400+ version numbers - if ( name.match( /safari/ ) && version > 400 ) { + if ( name === 'safari' && version > 400 ) { version = '2.0'; } // Expose Opera 10's lies about being Opera 9.8 - if ( name === 'opera' && version >= 9.8) { - match = ua.match( /version\/([0-9\.]*)/i ); + if ( name === 'opera' && version >= 9.8 ) { + match = ua.match( /\bversion\/([0-9\.]*)/ ); if ( match && match[1] ) { version = match[1]; } else { version = '10'; } } + // And Opera 15's lies about being Chrome + if ( name === 'chrome' && ( match = ua.match( /\bopr\/([0-9\.]*)/ ) ) ) { + if ( match[1] ) { + name = 'opera'; + version = match[1]; + } + } + // And IE 11's lies about being not being IE + if ( layout === 'trident' && layoutversion >= 7 && ( match = ua.match( /\brv[ :\/]([0-9\.]*)/ ) ) ) { + if ( match[1] ) { + name = 'msie'; + version = match[1]; + } + } + versionNumber = parseFloat( version, 10 ) || 0.0; /* Caching */ @@ -173,46 +188,61 @@ }, /** - * Checks the current browser against a support map object to determine if the browser has been black-listed or - * not. If the browser was not configured specifically it is assumed to work. It is assumed that the body - * element is classified as either "ltr" or "rtl". If neither is set, "ltr" is assumed. + * Checks the current browser against a support map object. * * A browser map is in the following format: * { + * // Multiple rules with configurable operators + * 'msie': [['>=', 7], ['!=', 9]], + * // Match no versions + * 'iphone': false, + * // Match any version + * 'android': null + * } + * + * It can optionally be split into ltr/rtl sections: + * { * 'ltr': { - * // Multiple rules with configurable operators - * 'msie': [['>=', 7], ['!=', 9]], - * // Blocked entirely + * 'android': null, * 'iphone': false * }, * 'rtl': { - * // Test against a string - * 'msie': [['!==', '8.1.2.3']], - * // RTL rules do not fall through to LTR rules, you must explicity set each of them + * 'android': false, + * // rules are not inherited from ltr * 'iphone': false * } * } * - * @param map {Object} Browser support map - * @param profile {Object} (optional) a client-profile object. + * @param {Object} map Browser support map + * @param {Object} [profile] A client-profile object + * @param {boolean} [exactMatchOnly=false] Only return true if the browser is matched, otherwise + * returns true if the browser is not found. * - * @return Boolean true if browser known or assumed to be supported, false if blacklisted + * @returns {boolean} The current browser is in the support map */ - test: function ( map, profile ) { + test: function ( map, profile, exactMatchOnly ) { /*jshint evil: true */ var conditions, dir, i, op, val; profile = $.isPlainObject( profile ) ? profile : $.client.profile(); - dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'; + if ( map.ltr && map.rtl ) { + dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'; + map = map[dir]; + } // Check over each browser condition to determine if we are running in a compatible client - if ( typeof map[dir] !== 'object' || map[dir][profile.name] === undefined ) { - // Unknown, so we assume it's working - return true; + if ( typeof map !== 'object' || map[profile.name] === undefined ) { + // Not found, return true if exactMatchOnly not set, false otherwise + return !exactMatchOnly; } - conditions = map[dir][profile.name]; + conditions = map[profile.name]; if ( conditions === false ) { + // Match no versions return false; } + if ( conditions === null ) { + // Match all versions + return true; + } for ( i = 0; i < conditions.length; i++ ) { op = conditions[i][0]; val = conditions[i][1]; diff --git a/resources/jquery/jquery.makeCollapsible.js b/resources/jquery/jquery.makeCollapsible.js index 1407f53b..0cd6417c 100644 --- a/resources/jquery/jquery.makeCollapsible.js +++ b/resources/jquery/jquery.makeCollapsible.js @@ -18,13 +18,15 @@ var lpx = 'jquery.makeCollapsible> '; /** + * Handler for a click on a collapsible toggler. + * * @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; + var $collapsibleContent, $containers, hookCallback; options = options || {}; // Validate parameters @@ -47,6 +49,14 @@ return; } + // Trigger a custom event to allow callers to hook to the collapsing/expanding, + // allowing the module to be testable, and making it possible to + // e.g. implement persistence via cookies + $collapsible.trigger( action === 'expand' ? 'beforeExpand.mw-collapsible' : 'beforeCollapse.mw-collapsible' ); + hookCallback = function () { + $collapsible.trigger( action === 'expand' ? 'afterExpand.mw-collapsible' : 'afterCollapse.mw-collapsible' ); + }; + // Handle different kinds of elements if ( !options.plainMode && $collapsible.is( 'table' ) ) { @@ -63,11 +73,12 @@ // http://stackoverflow.com/questions/467336#920480 if ( options.instantHide ) { $containers.hide(); + hookCallback(); } else { - $containers.stop( true, true ).fadeOut(); + $containers.stop( true, true ).fadeOut().promise().done( hookCallback ); } } else { - $containers.stop( true, true ).fadeIn(); + $containers.stop( true, true ).fadeIn().promise().done( hookCallback ); } } else if ( !options.plainMode && ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) ) { @@ -81,11 +92,12 @@ if ( action === 'collapse' ) { if ( options.instantHide ) { $containers.hide(); + hookCallback(); } else { - $containers.stop( true, true ).slideUp(); + $containers.stop( true, true ).slideUp().promise().done( hookCallback ); } } else { - $containers.stop( true, true ).slideDown(); + $containers.stop( true, true ).slideDown().promise().done( hookCallback ); } } else { @@ -97,11 +109,12 @@ if ( action === 'collapse' ) { if ( options.instantHide ) { $collapsibleContent.hide(); + hookCallback(); } else { - $collapsibleContent.slideUp(); + $collapsibleContent.slideUp().promise().done( hookCallback ); } } else { - $collapsibleContent.slideDown(); + $collapsibleContent.slideDown().promise().done( hookCallback ); } // Otherwise assume this is a customcollapse with a remote toggle @@ -110,18 +123,19 @@ if ( action === 'collapse' ) { if ( options.instantHide ) { $collapsible.hide(); + hookCallback(); } else { if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { - $collapsible.fadeOut(); + $collapsible.fadeOut().promise().done( hookCallback ); } else { - $collapsible.slideUp(); + $collapsible.slideUp().promise().done( hookCallback ); } } } else { if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) { - $collapsible.fadeIn(); + $collapsible.fadeIn().promise().done( hookCallback ); } else { - $collapsible.slideDown(); + $collapsible.slideDown().promise().done( hookCallback ); } } } @@ -129,7 +143,7 @@ } /** - * Handles clicking on the collapsible element toggle and other + * Handles clicking/keypressing on the collapsible element toggle and other * situations where a collapsible element is toggled (e.g. the initial * toggle for collapsed ones). * @@ -138,20 +152,32 @@ * @param {jQuery.Event|null} e either the event or null if unavailable * @param {Object|undefined} options */ - function togglingHandler( $toggle, $collapsible, event, options ) { + function togglingHandler( $toggle, $collapsible, e, 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; + if ( options === undefined ) { + options = {}; + } + + if ( e ) { + if ( e.type === 'click' && options.linksPassthru && $.nodeName( e.target, 'a' ) ) { + // Don't fire if a link was clicked, if requested (for premade togglers by default) + return; + } else if ( e.type === 'keypress' && e.which !== 13 && e.which !== 32 ) { + // Only handle keypresses on the "Enter" or "Space" keys + return; } else { - event.preventDefault(); - event.stopPropagation(); + e.preventDefault(); + e.stopPropagation(); } } - wasCollapsed = $collapsible.hasClass( 'mw-collapsed' ); + // This allows the element to be hidden on initial toggle without fiddling with the class + if ( options.wasCollapsed !== undefined ) { + wasCollapsed = options.wasCollapsed; + } else { + wasCollapsed = $collapsible.hasClass( 'mw-collapsed' ); + } // Toggle the state of the collapsible element (that is, expand or collapse) $collapsible.toggleClass( 'mw-collapsed', !wasCollapsed ); @@ -180,45 +206,6 @@ } /** - * 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: @@ -243,17 +230,17 @@ * 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 = {}; + } - if ( options === undefined ) { - options = {}; - } + return this.each( function () { + var $collapsible, collapseText, expandText, $toggle, actionHandler, buildDefaultToggleLink, + premadeToggleHandler, $toggleLink, $firstItem, collapsibleId, $customTogglers, firstval; // 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' ); + $collapsible = $( this ).addClass( 'mw-collapsible' ); // Return if it has been enabled already. if ( $collapsible.data( 'mw-made-collapsible' ) ) { @@ -263,21 +250,35 @@ } // Use custom text or default? - collapsetext = options.collapseText || $collapsible.attr( 'data-collapsetext' ) || mw.msg( 'collapsible-collapse' ); - expandtext = options.expandText || $collapsible.attr( 'data-expandtext' ) || mw.msg( 'collapsible-expand' ); - - // Create toggle link with a space around the brackets ( [text] ) - $toggleLink = - $( '<a href="#"></a>' ) - .text( collapsetext ) + collapseText = options.collapseText || $collapsible.attr( 'data-collapsetext' ) || mw.msg( 'collapsible-collapse' ); + expandText = options.expandText || $collapsible.attr( 'data-expandtext' ) || mw.msg( 'collapsible-expand' ); + + // Default click/keypress handler and toggle link to use when none is present + actionHandler = function ( e, opts ) { + var defaultOpts = { + toggleClasses: true, + toggleText: { collapseText: collapseText, expandText: expandText } + }; + opts = $.extend( defaultOpts, options, opts ); + togglingHandler( $( this ), $collapsible, e, opts ); + }; + // Default toggle link. Only build it when needed to avoid jQuery memory leaks (event data). + buildDefaultToggleLink = function () { + return $( '<a href="#"></a>' ) + .text( collapseText ) .wrap( '<span class="mw-collapsible-toggle"></span>' ) .parent() .prepend( ' [' ) .append( '] ' ) - .on( 'click.mw-collapse', function ( e, opts ) { - opts = $.extend( { toggleText: { collapseText: collapsetext, expandText: expandtext } }, options, opts ); - toggleLinkDefault( $(this), e, opts ); - } ); + .on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler ); + }; + + // Default handler for clicking on premade toggles + premadeToggleHandler = function ( e, opts ) { + var defaultOpts = { toggleClasses: true, linksPassthru: true }; + opts = $.extend( defaultOpts, options, opts ); + togglingHandler( $( this ), $collapsible, e, opts ); + }; // Check if this element has a custom position for the toggle link // (ie. outside the container or deeper inside the tree) @@ -296,25 +297,21 @@ } } - // Bind the custom togglers + // Bind the togglers if ( $customTogglers && $customTogglers.length ) { - $customTogglers.on( 'click.mw-collapse', function ( e, opts ) { - opts = $.extend( {}, options, opts ); - toggleLinkCustom( $(this), e, opts, $collapsible ); - } ); - - // 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 ); - } + actionHandler = function ( e, opts ) { + var defaultOpts = {}; + opts = $.extend( defaultOpts, options, opts ); + togglingHandler( $( this ), $collapsible, e, opts ); + }; + + $toggleLink = $customTogglers; + $toggleLink.on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler ); - // If this is not a custom case, do the default: - // Wrap the contents and add the toggle link } else { - // Elements are treated differently + // If this is not a custom case, do the default: wrap the + // contents and add the toggle link. Different 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' ); @@ -322,12 +319,10 @@ // If theres no toggle link, add it to the last cell if ( !$toggle.length ) { - $firstItem.eq(-1).prepend( $toggleLink ); + $toggleLink = buildDefaultToggleLink().prependTo( $firstItem.eq( -1 ) ); } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { - opts = $.extend( {}, options, opts ); - toggleLinkPremade( $toggle, e, opts ); - } ); + actionHandler = premadeToggleHandler; + $toggleLink = $toggle.on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler ); } } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) { @@ -339,17 +334,16 @@ 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. + // If no value was set WebKit returns "", Mozilla returns '-1', others return 0, 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() ); + $toggleLink = buildDefaultToggleLink(); + $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent().prependTo( $collapsible ); } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { - opts = $.extend( {}, options, opts ); - toggleLinkPremade( $toggle, e, opts ); - } ); + actionHandler = premadeToggleHandler; + $toggleLink = $toggle.on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler ); } } else { // <div>, <p> etc. @@ -364,28 +358,23 @@ // If theres no toggle link, add it if ( !$toggle.length ) { - $collapsible.prepend( $toggleLink ); + $toggleLink = buildDefaultToggleLink().prependTo( $collapsible ); } else { - $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, opts ) { - opts = $.extend( {}, options, opts ); - toggleLinkPremade( $toggle, e, opts ); - } ); + actionHandler = premadeToggleHandler; + $toggleLink = $toggle.on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler ); } } } - // 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 } ] ); + // Attributes for accessibility. This isn't necessary when the toggler is already + // an <a> or a <button> etc., but it doesn't hurt either, and it's consistent. + $toggleLink.prop( 'tabIndex', 0 ); + + // Initial state + if ( options.collapsed || $collapsible.hasClass( 'mw-collapsed' ) ) { + // One toggler can hook to multiple elements, and one element can have + // multiple togglers. This is the sanest way to handle that. + actionHandler.call( $toggleLink.get( 0 ), null, { instantHide: true, wasCollapsed: false } ); } } ); }; diff --git a/resources/jquery/jquery.placeholder.js b/resources/jquery/jquery.placeholder.js index 7badb11a..8044d880 100644 --- a/resources/jquery/jquery.placeholder.js +++ b/resources/jquery/jquery.placeholder.js @@ -10,17 +10,22 @@ */ ( function ( $ ) { - $.fn.placeholder = function () { + $.fn.placeholder = function ( text ) { + var hasArg = arguments.length; return this.each( function () { var placeholder, $input; + if ( hasArg ) { + this.setAttribute( 'placeholder', text ); + } + // If the HTML5 placeholder attribute is supported, use it if ( this.placeholder && 'placeholder' in document.createElement( this.tagName ) ) { return; } - placeholder = this.getAttribute( 'placeholder' ); + placeholder = hasArg ? text : this.getAttribute( 'placeholder' ); $input = $(this); // Show initially, if empty diff --git a/resources/jquery/jquery.spinner.css b/resources/jquery/jquery.spinner.css index 4a775283..a9e06dbe 100644 --- a/resources/jquery/jquery.spinner.css +++ b/resources/jquery/jquery.spinner.css @@ -33,7 +33,7 @@ .mw-spinner-inline { display: inline-block; vertical-align: middle; - + /* IE < 8 */ zoom: 1; *display: inline; diff --git a/resources/jquery/jquery.spinner.js b/resources/jquery/jquery.spinner.js index 93e30b9a..27dabc6c 100644 --- a/resources/jquery/jquery.spinner.js +++ b/resources/jquery/jquery.spinner.js @@ -1,7 +1,9 @@ /** - * jQuery spinner + * jQuery Spinner * * Simple jQuery plugin to create, inject and remove spinners. + * + * @class jQuery.plugin.spinner */ ( function ( $ ) { @@ -15,36 +17,37 @@ $.extend({ /** - * Creates a spinner element. + * Create a spinner element * * The argument is an object with options used to construct the spinner. These can be: * * It is a good practice to keep a reference to the created spinner to be able to remove it later. - * Alternatively one can use the id option and removeSpinner() (but make sure to choose an id + * Alternatively one can use the id option and #removeSpinner (but make sure to choose an id * that's unlikely to cause conflicts, e.g. with extensions, gadgets or user scripts). * * CSS classes used: - * .mw-spinner for every spinner - * .mw-spinner-small / .mw-spinner-large for size - * .mw-spinner-block / .mw-spinner-inline for display types + * - .mw-spinner for every spinner + * - .mw-spinner-small / .mw-spinner-large for size + * - .mw-spinner-block / .mw-spinner-inline for display types * - * @example * // Create a large spinner reserving all available horizontal space. * var $spinner = $.createSpinner({ size: 'large', type: 'block' }); * // Insert above page content. * $( '#mw-content-text' ).prepend( $spinner ); - * @example + * * // Place a small inline spinner next to the "Save" button * var $spinner = $.createSpinner({ size: 'small', type: 'inline' }); * // Alternatively, just `$.createSpinner();` as these are the default options. * $( '#wpSave' ).after( $spinner ); - * @example + * * // The following two are equivalent: * $.createSpinner( 'magic' ); * $.createSpinner({ id: 'magic' }); * - * @param {Object|String} opts [optional] ID string or options: - * - id: If given, spinner will be given an id of "mw-spinner-<id>" + * @static + * @inheritable + * @param {Object|string} [opts] ID string or options: + * - id: If given, spinner will be given an id of "mw-spinner-{id}" * - size: 'small' (default) or 'large' for a 20-pixel or 32-pixel spinner * - type: 'inline' (default) or 'block'. Inline creates an inline-block with width and * height equal to spinner size. Block is a block-level element with width 100%, height @@ -72,10 +75,12 @@ }, /** - * Removes a spinner element. + * Remove a spinner element * - * @param {String} id [optional] Id of the spinner, as passed to createSpinner. - * @return {jQuery} The (now detached) spinner. + * @static + * @inheritable + * @param {string} id Id of the spinner, as passed to #createSpinner + * @return {jQuery} The (now detached) spinner element */ removeSpinner: function ( id ) { return $( '#mw-spinner-' + id ).remove(); @@ -83,13 +88,21 @@ }); /** - * Injects a spinner after the elements in the jQuery collection - * (as siblings, not children). Collection contents remain unchanged. + * Inject a spinner after each element in the collection + * + * Inserts spinner as siblings, not children, of the target elements. + * Collection contents remain unchanged. * - * @param {Object|String} opts See createSpinner() for description. + * @param {Object|string} [opts] See #createSpinner * @return {jQuery} */ $.fn.injectSpinner = function ( opts ) { return this.after( $.createSpinner( opts ) ); }; + + /** + * @class jQuery + * @mixins jQuery.plugin.spinner + */ + }( jQuery ) ); diff --git a/resources/jquery/jquery.suggestions.js b/resources/jquery/jquery.suggestions.js index 44382f0d..28e2afc4 100644 --- a/resources/jquery/jquery.suggestions.js +++ b/resources/jquery/jquery.suggestions.js @@ -220,7 +220,7 @@ $.suggestions = { } else { // Expand from right newCSS.left = 'auto'; - newCSS.right = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); + newCSS.right = $( document ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); } context.data.$container.css( newCSS ); @@ -585,10 +585,12 @@ $.fn.suggestions = function () { switch ( context.data.keypressed ) { // This preventDefault logic is duplicated from // $.suggestions.keypress(), which sucks + // Arrow down case 40: e.preventDefault(); e.stopImmediatePropagation(); break; + // Arrow up, Escape and Enter case 38: case 27: case 13: diff --git a/resources/jquery/jquery.tablesorter.js b/resources/jquery/jquery.tablesorter.js index e08c9aaf..b3d7bb3d 100644 --- a/resources/jquery/jquery.tablesorter.js +++ b/resources/jquery/jquery.tablesorter.js @@ -8,8 +8,9 @@ * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * - * Depends on mw.config (wgDigitTransformTable, wgMonthNames, wgMonthNamesShort, - * wgDefaultDateFormat, wgContentLanguage) + * Depends on mw.config (wgDigitTransformTable, wgDefaultDateFormat, wgContentLanguage) + * and mw.language.months. + * * Uses 'tableSorterCollation' in mw.config (if available) */ /** @@ -287,44 +288,113 @@ function buildHeaders( table, msg ) { var maxSeen = 0, + colspanOffset = 0, longest, - realCellIndex = 0, - $tableHeaders = $( 'thead:eq(0) > tr', table ); - if ( $tableHeaders.length > 1 ) { - $tableHeaders.each( function () { - if ( this.cells.length > maxSeen ) { - maxSeen = this.cells.length; - longest = this; + columns, + i, + $tableHeaders = $( [] ), + $tableRows = $( 'thead:eq(0) > tr', table ); + if ( $tableRows.length <= 1 ) { + $tableHeaders = $tableRows.children( 'th' ); + } else { + // We need to find the cells of the row containing the most columns + var rowspan, + headersIndex = []; + $tableRows.each( function ( rowIndex ) { + $.each( this.cells, function( index2, cell ) { + rowspan = Number( cell.rowSpan ); + for ( i = 0; i < rowspan; i++ ) { + if ( headersIndex[rowIndex+i] === undefined ) { + headersIndex[rowIndex+i] = $( [] ); + } + headersIndex[rowIndex+i].push( cell ); + } + } ); + } ); + $.each( headersIndex, function ( index, cellArray ) { + if ( cellArray.length >= maxSeen ) { + maxSeen = cellArray.length; + longest = index; } - }); - $tableHeaders = $( longest ); + } ); + $tableHeaders = headersIndex[longest]; } - $tableHeaders = $tableHeaders.children( 'th' ).each( function ( index ) { - this.column = realCellIndex; - var colspan = this.colspan; - colspan = colspan ? parseInt( colspan, 10 ) : 1; - realCellIndex += colspan; + // 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 + table.headerToColumns = []; + table.columnToHeader = []; + + $tableHeaders.each( function ( headerIndex ) { + columns = []; + for ( i = 0; i < this.colSpan; i++ ) { + table.columnToHeader[ colspanOffset + i ] = headerIndex; + columns.push( colspanOffset + i ); + } + + table.headerToColumns[ headerIndex ] = columns; + colspanOffset += this.colSpan; + this.headerIndex = headerIndex; this.order = 0; this.count = 0; - if ( $( this ).is( '.unsortable' ) ) { + if ( $( this ).hasClass( table.config.unsortableClass ) ) { this.sortDisabled = true; } if ( !this.sortDisabled ) { - $( this ).addClass( table.config.cssHeader ).attr( 'title', msg[1] ); + $( this ) + .addClass( table.config.cssHeader ) + .prop( 'tabIndex', 0 ) + .attr( { + role: 'columnheader button', + title: msg[1] + } ); } // add cell to headerList - table.config.headerList[index] = this; + table.config.headerList[headerIndex] = this; } ); return $tableHeaders; } + /** + * Sets the sort count of the columns that are not affected by the sorting to have them sorted + * in default (ascending) order when their header cell is clicked the next time. + * + * @param {jQuery} $headers + * @param {Number[][]} sortList + * @param {Number[][]} headerToColumns + */ + function setHeadersOrder( $headers, sortList, headerToColumns ) { + // Loop through all headers to retrieve the indices of the columns the header spans across: + $.each( headerToColumns, function( headerIndex, columns ) { + + $.each( columns, function( i, columnIndex ) { + var header = $headers[headerIndex]; + + if ( !isValueInArray( columnIndex, sortList ) ) { + // Column shall not be sorted: Reset header count and order. + header.order = 0; + header.count = 0; + } else { + // Column shall be sorted: Apply designated count and order. + $.each( sortList, function( j, sortColumn ) { + if ( sortColumn[0] === i ) { + header.order = sortColumn[1]; + header.count = sortColumn[1] + 1; + return false; + } + } ); + } + } ); + + } ); + } + function isValueInArray( v, a ) { var l = a.length; for ( var i = 0; i < l; i++ ) { @@ -407,12 +477,15 @@ var regex = []; ts.monthNames = {}; - for ( var i = 1; i < 13; i++ ) { - var name = mw.config.get( 'wgMonthNames' )[i].toLowerCase(); - ts.monthNames[name] = i; + for ( var i = 0; i < 12; i++ ) { + var name = mw.language.months.names[i].toLowerCase(); + ts.monthNames[name] = i + 1; regex.push( $.escapeRE( name ) ); - name = mw.config.get( 'wgMonthNamesShort' )[i].toLowerCase().replace( '.', '' ); - ts.monthNames[name] = i; + name = mw.language.months.genitive[i].toLowerCase(); + ts.monthNames[name] = i + 1; + regex.push( $.escapeRE( name ) ); + name = mw.language.months.abbrev[i].toLowerCase().replace( '.', '' ); + ts.monthNames[name] = i + 1; regex.push( $.escapeRE( name ) ); } @@ -424,10 +497,10 @@ ts.dateRegex[0] = new RegExp( /^\s*(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{2,4})\s*?/i); // Written Month name, dmy - ts.dateRegex[1] = new RegExp( '^\\s*(\\d{1,2})[\\,\\.\\-\\/\'\\s]*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*(\\d{2,4})\\s*$', 'i' ); + ts.dateRegex[1] = new RegExp( '^\\s*(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); // Written Month name, mdy - ts.dateRegex[2] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]*(\\d{1,2})[\\,\\.\\-\\/\'\\s]*(\\d{2,4})\\s*$', 'i' ); + ts.dateRegex[2] = new RegExp( '^\\s*(' + regex + ')' + '[\\,\\.\\-\\/\'\\s]+(\\d{1,2})[\\,\\.\\-\\/\'\\s]+(\\d{2,4})\\s*$', 'i' ); } @@ -572,7 +645,7 @@ $.each( sortObjects, function( i, sortObject ) { $.each ( sortObject, function( columnIndex, order ) { var orderIndex = ( order === 'desc' ) ? 1 : 0; - sortList.push( [columnIndex, orderIndex] ); + sortList.push( [parseInt( columnIndex, 10 ), orderIndex] ); } ); } ); return sortList; @@ -590,6 +663,7 @@ sortInitialOrder: 'asc', sortMultiSortKey: 'shiftKey', sortLocaleCompare: false, + unsortableClass: 'unsortable', parsers: {}, widgets: [], headers: {}, @@ -611,7 +685,6 @@ return $tables.each( function ( i, table ) { // Declare and cache. var $headers, cache, config, - headerToColumns, columnToHeader, colspanOffset, $table = $( table ), firstTime = true; @@ -648,10 +721,9 @@ // Build headers $headers = buildHeaders( table, sortMsg ); - // Grab and process locale settings + // Grab and process locale settings. buildTransformTable(); buildDateTable(); - buildCollationTable(); // Precaching regexps can bring 10 fold // performance improvements in some browsers. @@ -660,6 +732,12 @@ function setupForFirstSort() { firstTime = false; + // Defer buildCollationTable to first sort. As user and site scripts + // may customize tableSorterCollation but load after $.ready(), other + // scripts may call .tablesorter() before they have done the + // tableSorterCollation customizations. + buildCollationTable(); + // 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> @@ -679,28 +757,17 @@ 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.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 + $headers.not( '.' + table.config.unsortableClass ).on( 'keypress click', function ( e ) { + if ( e.type === 'click' && e.target.nodeName.toLowerCase() === 'a' ) { + // The user clicked on a link inside a table header. + // Do nothing and let the default link click action continue. + return true; + } + + if ( e.type === 'keypress' && e.which !== 13 ) { + // Only handle keypresses on the "Enter" key. return true; } @@ -724,7 +791,7 @@ var cell = this; // Get current column index - var columns = headerToColumns[this.column]; + var columns = table.headerToColumns[ this.headerIndex ]; var newSortList = $.map( columns, function (c) { // jQuery "helpfully" flattens the arrays... return [[c, cell.order]]; @@ -758,8 +825,11 @@ } } + // Reset order/counts of cells not affected by sorting + setHeadersOrder( $headers, config.sortList, table.headerToColumns ); + // Set CSS for headers - setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg, columnToHeader ); + setHeadersCss( $table[0], $headers, config.sortList, sortCSS, sortMsg, table.columnToHeader ); appendToTable( $table[0], multisort( $table[0], config.sortList, cache ) ); @@ -798,11 +868,15 @@ sortList = convertSortList( sortList ); } + // Set each column's sort count to be able to determine the correct sort + // order when clicking on a header cell the next time + setHeadersOrder( $headers, sortList, table.headerToColumns ); + // re-build the cache for the tbody cells cache = buildCache( table ); // set css for headers - setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, columnToHeader ); + setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, table.columnToHeader ); // sort the table and append it to the dom appendToTable( table, multisort( table, sortList, cache ) ); diff --git a/resources/jquery/jquery.textSelection.js b/resources/jquery/jquery.textSelection.js index daff9d93..2b5a4406 100644 --- a/resources/jquery/jquery.textSelection.js +++ b/resources/jquery/jquery.textSelection.js @@ -89,7 +89,7 @@ * Ported from skins/common/edit.js by Trevor Parscal * (c) 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org * - * Inserts text at the begining and end of a text selection, optionally + * Inserts text at the beginning and end of a text selection, optionally * inserting text at the caret when selection is empty. * * @fixme document the options parameters @@ -257,7 +257,7 @@ * Some code copied from * http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ * - * Get the position (in resolution of bytes not nessecarily characters) + * Get the position (in resolution of bytes not necessarily characters) * in a textarea * * Will focus the textarea in some browsers (IE/Opera) diff --git a/resources/mediawiki.action/images/green-checkmark.png b/resources/mediawiki.action/images/green-checkmark.png Binary files differnew file mode 100644 index 00000000..8ec604ea --- /dev/null +++ b/resources/mediawiki.action/images/green-checkmark.png diff --git a/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css b/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css new file mode 100644 index 00000000..1af4a7a0 --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css @@ -0,0 +1,17 @@ +/* Styles for collapsible lists of templates used and hidden categories */ +.mw-editfooter-toggler { + cursor: pointer; + background-position: left center; + padding-left: 16px; +} + +.mw-editfooter-list { + margin-bottom: 1em; + margin-left: 2.5em; +} + +/* Show/hide animation is incorrect if the table has a margin set. Extra + * "table.wikitable" is needed in the selector for CSS specificity. */ +table.wikitable.preview-limit-report { + margin: 0; +} diff --git a/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js b/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js new file mode 100644 index 00000000..7ae51aba --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js @@ -0,0 +1,54 @@ +jQuery( document ).ready( function ( $ ) { + var collapsibleLists, i, handleOne; + + // Collapsible lists of categories and templates + collapsibleLists = [ + { + $list: $( '.templatesUsed ul' ), + $toggler: $( '.mw-templatesUsedExplanation' ), + cookieName: 'templates-used-list' + }, + { + $list: $( '.hiddencats ul' ), + $toggler: $( '.mw-hiddenCategoriesExplanation' ), + cookieName: 'hidden-categories-list' + }, + { + $list: $( '.preview-limit-report-wrapper' ), + $toggler: $( '.mw-limitReportExplanation' ), + cookieName: 'preview-limit-report' + } + ]; + + handleOne = function ( $list, $toggler, cookieName ) { + var isCollapsed = $.cookie( cookieName ) !== 'expanded'; + + // Style the toggler with an arrow icon and add a tabIndex and a role for accessibility + $toggler.addClass( 'mw-editfooter-toggler' ).prop( 'tabIndex', 0 ).attr( 'role', 'button' ); + $list.addClass( 'mw-editfooter-list' ); + + $list.makeCollapsible( { + $customTogglers: $toggler, + linksPassthru: true, + plainMode: true, + collapsed: isCollapsed + } ); + + $toggler.addClass( isCollapsed ? 'mw-icon-arrow-collapsed' : 'mw-icon-arrow-expanded' ); + + $list.on( 'beforeExpand.mw-collapsible', function () { + $toggler.removeClass( 'mw-icon-arrow-collapsed' ).addClass( 'mw-icon-arrow-expanded' ); + $.cookie( cookieName, 'expanded' ); + } ); + + $list.on( 'beforeCollapse.mw-collapsible', function () { + $toggler.removeClass( 'mw-icon-arrow-expanded' ).addClass( 'mw-icon-arrow-collapsed' ); + $.cookie( cookieName, 'collapsed' ); + } ); + }; + + for ( i = 0; i < collapsibleLists.length; i++ ) { + // Pass to a function for iteration-local variables + handleOne( collapsibleLists[i].$list, collapsibleLists[i].$toggler, collapsibleLists[i].cookieName ); + } +} ); diff --git a/resources/mediawiki.action/mediawiki.action.edit.editWarning.js b/resources/mediawiki.action/mediawiki.action.edit.editWarning.js new file mode 100644 index 00000000..89bb64df --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.edit.editWarning.js @@ -0,0 +1,56 @@ +/* + * Javascript for module editWarning + */ +( function ( mw, $ ) { + $( function () { + // Check if EditWarning is enabled and if we need it + if ( $( '#wpTextbox1' ).length === 0 ) { + return true; + } + // Get the original values of some form elements + $( '#wpTextbox1, #wpSummary' ).each( function () { + $( this ).data( 'origtext', $( this ).val() ); + }); + var savedWindowOnBeforeUnload; + $( window ) + .on( 'beforeunload.editwarning', function () { + var retval; + + // Check if the current values of some form elements are the same as + // the original values + if ( + mw.config.get( 'wgAction' ) === 'submit' || + $( '#wpTextbox1' ).data( 'origtext' ) !== $( '#wpTextbox1' ).val() || + $( '#wpSummary' ).data( 'origtext' ) !== $( '#wpSummary' ).val() + ) { + // Return our message + retval = mw.msg( 'editwarning-warning' ); + } + + // Unset the onbeforeunload handler so we don't break page caching in Firefox + savedWindowOnBeforeUnload = window.onbeforeunload; + window.onbeforeunload = null; + if ( retval !== undefined ) { + // ...but if the user chooses not to leave the page, we need to rebind it + setTimeout( function () { + window.onbeforeunload = savedWindowOnBeforeUnload; + }, 1 ); + return retval; + } + } ) + .on( 'pageshow.editwarning', function () { + // Re-add onbeforeunload handler + if ( !window.onbeforeunload ) { + window.onbeforeunload = savedWindowOnBeforeUnload; + } + } ); + + // Add form submission handler + $( '#editform' ).submit( function () { + // Unbind our handlers + $( window ).off( '.editwarning' ); + } ); + } ); + +}( mediaWiki, jQuery ) ); + diff --git a/resources/mediawiki.action/mediawiki.action.edit.js b/resources/mediawiki.action/mediawiki.action.edit.js index 2835c9cc..ba711aae 100644 --- a/resources/mediawiki.action/mediawiki.action.edit.js +++ b/resources/mediawiki.action/mediawiki.action.edit.js @@ -1,17 +1,20 @@ +/** + * Interface for the classic edit toolbar. + * + * @class mw.toolbar + * @singleton + */ ( function ( mw, $ ) { - var isReady, toolbar, currentFocused, queue, $toolbar, slice; - - isReady = false; - queue = []; - $toolbar = false; - slice = Array.prototype.slice; + var toolbar, isReady, $toolbar, queue, slice, $currentFocused; /** - * Internal helper that does the actual insertion - * of the button into the toolbar. - * See mw.toolbar.addButton for parameter documentation. + * Internal helper that does the actual insertion of the button into the toolbar. + * + * See #addButton for parameter documentation. + * + * @private */ - function insertButton( b /* imageFile */, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ) { + function insertButton( b, speedTip, tagOpen, tagClose, sampleText, imageId ) { // Backwards compatibility if ( typeof b !== 'object' ) { b = { @@ -20,11 +23,10 @@ tagOpen: tagOpen, tagClose: tagClose, sampleText: sampleText, - imageId: imageId, - selectText: selectText + imageId: imageId }; } - var $image = $( '<img>', { + var $image = $( '<img>' ).attr( { width : 23, height: 22, src : b.imageFile, @@ -33,30 +35,43 @@ id : b.imageId || undefined, 'class': 'mw-toolbar-editbutton' } ).click( function () { - toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText, b.selectText ); + toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText ); return false; } ); $toolbar.append( $image ); - return true; } + isReady = false; + $toolbar = false; + /** + * @private + * @property {Array} + * Contains button objects (and for backwards compatibilty, it can + * also contains an arguments array for insertButton). + */ + queue = []; + slice = queue.slice; + toolbar = { + /** * Add buttons to the toolbar. + * * Takes care of race conditions and time-based dependencies * by placing buttons in a queue if this method is called before * the toolbar is created. - * @param {Object} button: Object with the following properties: - * - imageFile - * - speedTip - * - tagOpen - * - tagClose - * - sampleText - * - imageId - * - selectText - * For compatiblity, passing the above as separate arguments + * + * For compatiblity, passing the properties listed below as separate arguments * (in the listed order) is also supported. + * + * @param {Object} button Object with the following properties: + * @param {string} button.imageFile + * @param {string} button.speedTip + * @param {string} button.tagOpen + * @param {string} button.tagClose + * @param {string} button.sampleText + * @param {string} [button.imageId] */ addButton: function () { if ( isReady ) { @@ -66,18 +81,44 @@ queue.push( slice.call( arguments ) ); } }, + /** + * Example usage: + * addButtons( [ { .. }, { .. }, { .. } ] ); + * addButtons( { .. }, { .. } ); + * + * @param {Object|Array} [buttons...] An array of button objects or the first + * button object in a list of variadic arguments. + */ + addButtons: function ( buttons ) { + if ( !$.isArray( buttons ) ) { + buttons = slice.call( arguments ); + } + if ( isReady ) { + $.each( buttons, function () { + insertButton( this ); + } ); + } else { + // Push each button into the queue + queue.push.apply( queue, buttons ); + } + }, /** - * Apply tagOpen/tagClose to selection in textarea, - * use sampleText instead of selection if there is none. + * Apply tagOpen/tagClose to selection in currently focused textarea. + * + * Uses `sampleText` if selection is empty. + * + * @param {string} tagOpen + * @param {string} tagClose + * @param {string} sampleText */ insertTags: function ( tagOpen, tagClose, sampleText ) { - if ( currentFocused && currentFocused.length ) { - currentFocused.textSelection( + if ( $currentFocused && $currentFocused.length ) { + $currentFocused.textSelection( 'encapsulateSelection', { - 'pre': tagOpen, - 'peri': sampleText, - 'post': tagClose + pre: tagOpen, + peri: sampleText, + post: tagClose } ); } @@ -95,64 +136,58 @@ // Explose API publicly mw.toolbar = toolbar; - $( document ).ready( function () { - var buttons, i, b, $iframe; + $( function () { + var i, b, $iframe, editBox, scrollTop, $editForm; // currentFocus is used to determine where to insert tags - currentFocused = $( '#wpTextbox1' ); + $currentFocused = $( '#wpTextbox1' ); // Populate the selector cache for $toolbar $toolbar = $( '#toolbar' ); - // Legacy: Merge buttons from mwCustomEditButtons - buttons = [].concat( queue, window.mwCustomEditButtons ); - // Clear queue - queue.length = 0; - for ( i = 0; i < buttons.length; i++ ) { - b = buttons[i]; + for ( i = 0; i < queue.length; i++ ) { + b = queue[i]; if ( $.isArray( b ) ) { // Forwarded arguments array from mw.toolbar.addButton insertButton.apply( toolbar, b ); } else { - // Raw object from legacy mwCustomEditButtons + // Raw object from mw.toolbar.addButtons insertButton( b ); } } + // Clear queue + queue.length = 0; + // This causes further calls to addButton to go to insertion directly - // instead of to the toolbar.buttons queue. + // instead of to the queue. // It is important that this is after the one and only loop through - // the the toolbar.buttons queue + // the the queue isReady = true; // Make sure edit summary does not exceed byte limit $( '#wpSummary' ).byteLimit( 255 ); - /** - * Restore the edit box scroll state following a preview operation, - * and set up a form submission handler to remember this state - */ - ( function scrollEditBox() { - var editBox, scrollTop, $editForm; - - editBox = document.getElementById( 'wpTextbox1' ); - scrollTop = document.getElementById( 'wpScrolltop' ); - $editForm = $( '#editform' ); - if ( $editForm.length && editBox && scrollTop ) { - if ( scrollTop.value ) { - editBox.scrollTop = scrollTop.value; - } - $editForm.submit( function () { - scrollTop.value = editBox.scrollTop; - }); + // Restore the edit box scroll state following a preview operation, + // and set up a form submission handler to remember this state. + editBox = document.getElementById( 'wpTextbox1' ); + scrollTop = document.getElementById( 'wpScrolltop' ); + $editForm = $( '#editform' ); + if ( $editForm.length && editBox && scrollTop ) { + if ( scrollTop.value ) { + editBox.scrollTop = scrollTop.value; } - }() ); + $editForm.submit( function () { + scrollTop.value = editBox.scrollTop; + }); + } - $( 'textarea, input:text' ).focus( function () { - currentFocused = $(this); - }); + // Apply to dynamically created textboxes as well as normal ones + $( document ).on( 'focus', 'textarea, input:text', function () { + $currentFocused = $( this ); + } ); - // HACK: make currentFocused work with the usability iframe + // HACK: make $currentFocused work with the usability iframe // With proper focus detection support (HTML 5!) this'll be much cleaner // TODO: Get rid of this WikiEditor code from MediaWiki core! $iframe = $( '.wikiEditor-ui-text iframe' ); @@ -161,7 +196,7 @@ // for IE .add( $iframe.get( 0 ).contentWindow.document.body ) .focus( function () { - currentFocused = $iframe; + $currentFocused = $iframe; } ); } }); diff --git a/resources/mediawiki.action/mediawiki.action.edit.preview.js b/resources/mediawiki.action/mediawiki.action.edit.preview.js index 602aadb0..c5cd61ef 100644 --- a/resources/mediawiki.action/mediawiki.action.edit.preview.js +++ b/resources/mediawiki.action/mediawiki.action.edit.preview.js @@ -7,14 +7,16 @@ * @param {jQuery.Event} e */ function doLivePreview( e ) { - var $wikiPreview, copySelectors, removeSelectors, $copyElements, $spinner, + var $wikiPreview, $editform, copySelectors, $copyElements, $spinner, targetUrl, postData, $previewDataHolder; e.preventDefault(); + // Deprecated: Use mw.hook instead $( mw ).trigger( 'LivePreviewPrepare' ); $wikiPreview = $( '#wikiPreview' ); + $editform = $( '#editform' ); // Show #wikiPreview if it's hidden to be able to scroll to it // (if it is hidden, it's also empty, so nothing changes in the rendering) @@ -34,16 +36,13 @@ '#p-lang', // Editing-related '.templatesUsed', + '.limitreport', '.mw-summary-preview' ]; $copyElements = $( copySelectors.join( ',' ) ); // Not shown during normal preview, to be removed if present - removeSelectors = [ - '.mw-newarticletext' - ]; - - $( removeSelectors.join( ',' ) ).remove(); + $( '.mw-newarticletext' ).remove(); $spinner = $.createSpinner( { size: 'large', @@ -51,36 +50,29 @@ }); $wikiPreview.before( $spinner ); $spinner.css( { - position: 'absolute', marginTop: $spinner.height() } ); - // Make sure preview area is at least as tall as 2x the height of the spinner. - // 1x because if its smaller, it will spin behind the edit toolbar. - // (this happens on the first preview when editPreview is still empty) - // 2x because the spinner has 1x margin top breathing room. - $wikiPreview.css( 'minHeight', $spinner.height() * 2 ); // Can't use fadeTo because it calls show(), and we might want to keep some elements hidden // (e.g. empty #catlinks) - $copyElements.animate( { - opacity: 0.4 - }, 'fast' ); + $copyElements.animate( { opacity: 0.4 }, 'fast' ); $previewDataHolder = $( '<div>' ); - targetUrl = $( '#editform' ).attr( 'action' ); + targetUrl = $editform.attr( 'action' ); // Gather all the data from the form - postData = $( '#editform' ).formToArray(); + postData = $editform.formToArray(); postData.push( { name: e.target.name, value: '' } ); // Load new preview data. - // TODO: This should use the action=parse API instead of loading the entire page - // Though that requires figuring out how to conver that raw data into proper HTML. + // TODO: This should use the action=parse API instead of loading the entire page, + // although that requires figuring out how to convert that raw data into proper HTML. $previewDataHolder.load( targetUrl + ' ' + copySelectors.join( ',' ), postData, function () { var i, $from; + // Copy the contents of the specified elements from the loaded page to the real page. // Also copy their class attributes. for ( i = 0; i < copySelectors.length; i++ ) { @@ -92,16 +84,19 @@ .attr( 'class', $from.attr( 'class' ) ); } + // Deprecated: Use mw.hook instead + $( mw ).trigger( 'LivePreviewDone', [copySelectors] ); + + mw.hook( 'wikipage.content' ).fire( $wikiPreview ); + $spinner.remove(); $copyElements.animate( { opacity: 1 }, 'fast' ); - - $( mw ).trigger( 'LivePreviewDone', [copySelectors] ); } ); } - $( document ).ready( function () { + $( 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 ) { @@ -109,32 +104,32 @@ } // The following elements can change in a preview but are not output - // by the server when they're empty until the preview reponse. + // by the server when they're empty until the preview response. // TODO: Make the server output these always (in a hidden state), so we don't // have to fish and (hopefully) put them in the right place (since skins // can change where they are output). if ( !document.getElementById( 'p-lang' ) && document.getElementById( 'p-tb' ) ) { $( '#p-tb' ).after( - $( '<div>' ).prop( 'id', 'p-lang' ) + $( '<div>' ).attr( 'id', 'p-lang' ) ); } if ( !$( '.mw-summary-preview' ).length ) { $( '.editCheckboxes' ).before( - $( '<div>' ).prop( 'className', 'mw-summary-preview' ) + $( '<div>' ).addClass( 'mw-summary-preview' ) ); } if ( !document.getElementById( 'wikiDiff' ) && document.getElementById( 'wikiPreview' ) ) { $( '#wikiPreview' ).after( - $( '<div>' ).prop( 'id', 'wikiDiff') + $( '<div>' ).attr( 'id', 'wikiDiff' ) ); } - // Make sure diff styles are loaded - mw.loader.load( 'mediawiki.action.history.diff' ); - + // This should be moved down to '#editform', but is kept on the body for now + // because the LiquidThreads extension is re-using this module with only half + // the EditPage (doesn't include #editform presumably, bug 55463). $( document.body ).on( 'click', '#wpPreview, #wpDiff', doLivePreview ); } ); diff --git a/resources/mediawiki.action/mediawiki.action.edit.styles.css b/resources/mediawiki.action/mediawiki.action.edit.styles.css new file mode 100644 index 00000000..4a2bab3d --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.edit.styles.css @@ -0,0 +1,44 @@ +/** + * Styles for elements of the editing form. + */ + +/* General layout */ +#wpTextbox1 { + margin: 0; + display: block; +} + +.editOptions { + background-color: #F0F0F0; + border: 1px solid silver; + border-top: none; + padding: 1em 1em 1.5em 1em; + margin-bottom: 2em; +} + +/* Adjustments to edit form elements */ +.editCheckboxes { + margin-bottom: 1em; +} + +.editCheckboxes input:first-child { + margin-left: 0; +} + +.cancelLink { + margin-left: 0.5em; +} + +#editpage-copywarn { + font-size: 0.9em; +} + +#wpSummary { + display: block; + margin-top: 0; + margin-bottom: 0.5em; +} + +.editButtons input:first-child { + margin-left: .1em; +} diff --git a/resources/mediawiki.action/mediawiki.action.history.js b/resources/mediawiki.action/mediawiki.action.history.js index e9d320c1..04f045a5 100644 --- a/resources/mediawiki.action/mediawiki.action.history.js +++ b/resources/mediawiki.action/mediawiki.action.history.js @@ -1,7 +1,7 @@ /** * JavaScript for History action */ -jQuery( document ).ready( function ( $ ) { +jQuery( function ( $ ) { var $historyCompareForm = $( '#mw-history-compare' ), $historySubmitter, $lis = $( '#pagehistory > li' ); diff --git a/resources/mediawiki.action/mediawiki.action.view.postEdit.css b/resources/mediawiki.action/mediawiki.action.view.postEdit.css new file mode 100644 index 00000000..be88337e --- /dev/null +++ b/resources/mediawiki.action/mediawiki.action.view.postEdit.css @@ -0,0 +1,77 @@ +.postedit-container { + margin: 0 auto; + position: fixed; + top: 0; + height: 0; + left: 50%; + z-index: 1000; + font-size: 13px; +} + +.postedit-container:hover { + cursor: pointer; +} + +.postedit { + position: relative; + top: 0.6em; + left: -50%; + padding: .6em 3.6em .6em 1.1em; + line-height: 1.5625em; + color: #626465; + background-color: #f4f4f4; + border: 1px solid #dcd9d9; + text-shadow: 0 0.0625em 0 rgba(255, 255, 255, 0.5); + border-radius: 5px; + -webkit-box-shadow: 0 2px 5px 0 #ccc; + box-shadow: 0 2px 5px 0 #ccc; + -webkit-transition: all 0.25s ease-in-out; + -moz-transition: all 0.25s ease-in-out; + -ms-transition: all 0.25s ease-in-out; + -o-transition: all 0.25s ease-in-out; + transition: all 0.25s ease-in-out; +} + +.skin-monobook .postedit { + top: 6em !important; +} + +.postedit-faded { + opacity: 0; +} + +.postedit-icon { + padding-left: 41px; /* 25 + 8 + 8 */ + /* like min-height, but old IE compatible and keeps text vertically aligned, too */ + line-height: 25px; + background-repeat: no-repeat; + background-position: 8px 50%; +} + +.postedit-icon-checkmark { + /* @embed */ + background-image: url(images/green-checkmark.png); + background-position: left; +} + +.postedit-close { + position: absolute; + padding: 0 .8em; + right: 0; + top: 0; + font-size: 1.25em; + font-weight: bold; + line-height: 2.3em; + color: black; + text-shadow: 0 0.0625em 0 white; + text-decoration: none; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.postedit-close:hover { + color: black; + text-decoration: none; + opacity: 0.4; + filter: alpha(opacity=40); +} diff --git a/resources/mediawiki.action/mediawiki.action.view.postEdit.js b/resources/mediawiki.action/mediawiki.action.view.postEdit.js index a11233fa..6e4df9f0 100644 --- a/resources/mediawiki.action/mediawiki.action.view.postEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.postEdit.js @@ -1,15 +1,76 @@ ( function ( mw, $ ) { - // Only a view can be a post-edit. - if ( mw.config.get( 'wgAction' ) !== 'view' ) { - return; + 'use strict'; + + /** + * @event postEdit + * @member mw.hook + * @param {Object} [data] Optional data + * @param {string|jQuery|Array} [data.message] Message that listeners + * should use when displaying notifications. String for plain text, + * use array or jQuery object to pass actual nodes. + * @param {string|mw.user} [data.user=mw.user] User that made the edit. + */ + + /** + * After the listener for #postEdit removes the notification. + * + * @event postEdit_afterRemoval + * @member mw.hook + */ + + var config = mw.config.get( [ 'wgAction', 'wgCookiePrefix', 'wgCurRevisionId' ] ), + // This should match EditPage::POST_EDIT_COOKIE_KEY_PREFIX: + cookieKey = config.wgCookiePrefix + 'PostEditRevision' + config.wgCurRevisionId, + $div, id; + + function showConfirmation( data ) { + data = data || {}; + if ( data.message === undefined ) { + data.message = $.parseHTML( mw.message( 'postedit-confirmation', data.user || mw.user ).escaped() ); + } + + $div = $( + '<div class="postedit-container">' + + '<div class="postedit">' + + '<div class="postedit-icon postedit-icon-checkmark postedit-content"></div>' + + '<a href="#" class="postedit-close">×</a>' + + '</div>' + + '</div>' + ); + + if ( typeof data.message === 'string' ) { + $div.find( '.postedit-content' ).text( data.message ); + } else if ( typeof data.message === 'object' ) { + $div.find( '.postedit-content' ).append( data.message ); + } + + $div + .click( fadeOutConfirmation ) + .prependTo( 'body' ); + + id = setTimeout( fadeOutConfirmation, 3000 ); } - // Matches EditPage::POST_EDIT_COOKIE_KEY_PREFIX - var cookieKey = mw.config.get( 'wgCookiePrefix' ) + 'PostEditRevision' + mw.config.get( 'wgCurRevisionId' ); + function fadeOutConfirmation() { + clearTimeout( id ); + $div.find( '.postedit' ).addClass( 'postedit postedit-faded' ); + setTimeout( removeConfirmation, 500 ); - if ( $.cookie( cookieKey ) === '1' ) { - // We just saved this page + return false; + } + + function removeConfirmation() { + $div.remove(); + mw.hook( 'postEdit.afterRemoval' ).fire(); + } + + mw.hook( 'postEdit' ).add( showConfirmation ); + + if ( config.wgAction === 'view' && $.cookie( cookieKey ) === '1' ) { $.cookie( cookieKey, null, { path: '/' } ); mw.config.set( 'wgPostEdit', true ); + + mw.hook( 'postEdit' ).fire(); } + } ( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js index 61d9d150..93befe3a 100644 --- a/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js +++ b/resources/mediawiki.action/mediawiki.action.view.rightClickEdit.js @@ -5,10 +5,10 @@ */ jQuery( function ( $ ) { // Select all h1-h6 elements that contain editsection links - // Don't use the ":has:(.editsection a)" selector because it performs very bad. + // Don't use the ":has:(.mw-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 = $( this ).find( '.editsection a' ); + var $edit = $( this ).find( '.mw-editsection a' ); if ( !$edit.length ) { return; } diff --git a/resources/mediawiki.api/mediawiki.api.category.js b/resources/mediawiki.api/mediawiki.api.category.js index 4de52911..98a9c54b 100644 --- a/resources/mediawiki.api/mediawiki.api.category.js +++ b/resources/mediawiki.api/mediawiki.api.category.js @@ -14,12 +14,13 @@ * @return {boolean} return.done.isCategory Whether the category exists. */ isCategory: function ( title, ok, err ) { - var d = $.Deferred(); + var d = $.Deferred(), + apiPromise; + // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); + d.done( ok ).fail( err ); - this.get( { + apiPromise = this.get( { prop: 'categoryinfo', titles: title.toString() } ) @@ -36,7 +37,7 @@ }) .fail( d.reject ); - return d.promise(); + return d.promise( { abort: apiPromise.abort } ); }, /** @@ -50,13 +51,14 @@ * @return {String[]} return.done.categories Matched categories */ getCategoriesByPrefix: function ( prefix, ok, err ) { - var d = $.Deferred(); + var d = $.Deferred(), + apiPromise; + // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); + d.done( ok ).fail( err ); // Fetch with allpages to only get categories that have a corresponding description page. - this.get( { + apiPromise = this.get( { list: 'allpages', apprefix: prefix, apnamespace: mw.config.get('wgNamespaceIds').category @@ -72,7 +74,7 @@ }) .fail( d.reject ); - return d.promise(); + return d.promise( { abort: apiPromise.abort } ); }, @@ -88,12 +90,13 @@ * if title was not found. */ getCategories: function ( title, ok, err, async ) { - var d = $.Deferred(); + var d = $.Deferred(), + apiPromise; + // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); + d.done( ok ).fail( err ); - this.get( { + apiPromise = this.get( { prop: 'categories', titles: title.toString() }, { @@ -114,10 +117,10 @@ } ); } d.resolve( ret ); - }) + } ) .fail( d.reject ); - return d.promise(); + return d.promise( { abort: apiPromise.abort } ); } } ); diff --git a/resources/mediawiki.api/mediawiki.api.edit.js b/resources/mediawiki.api/mediawiki.api.edit.js index 3c775ad0..cc83a4b8 100644 --- a/resources/mediawiki.api/mediawiki.api.edit.js +++ b/resources/mediawiki.api/mediawiki.api.edit.js @@ -3,9 +3,6 @@ */ ( function ( mw, $ ) { - // Cache token so we don't have to keep fetching new ones for every single request. - var cachedToken = null; - $.extend( mw.Api.prototype, { /** @@ -19,32 +16,7 @@ * @return {jQuery.Promise} See #post */ postWithEditToken: function ( params, ok, err ) { - var useTokenToPost, getTokenIfBad, - api = this; - if ( cachedToken === null ) { - // We don't have a valid cached token, so get a fresh one and try posting. - // We do not trap any 'badtoken' or 'notoken' errors, because we don't want - // an infinite loop. If this fresh token is bad, something else is very wrong. - useTokenToPost = function ( token ) { - params.token = token; - api.post( params, ok, err ); - }; - return api.getEditToken( useTokenToPost, err ); - } else { - // We do have a token, but it might be expired. So if it is 'bad' then - // start over with a new token. - params.token = cachedToken; - getTokenIfBad = function ( code, result ) { - if ( code === 'badtoken' ) { - // force a new token, clear any old one - cachedToken = null; - api.postWithEditToken( params, ok, err ); - } else { - err( code, result ); - } - }; - return api.post( params, { ok : ok, err : getTokenIfBad }); - } + return this.postWithToken( 'edit', params ).done( ok ).fail( err ); }, /** @@ -57,37 +29,7 @@ * @return {string} return.done.token Received token. */ getEditToken: function ( ok, err ) { - var d = $.Deferred(); - // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); - - this.get( { - action: 'tokens', - type: 'edit' - }, { - // 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; - d.resolve( token ); - } else { - d.reject( 'token-missing', data ); - } - }) - .fail( d.reject ); - - return d.promise(); + return this.getToken( 'edit' ).done( ok ).fail( err ); }, /** @@ -110,8 +52,7 @@ text: message }, ok, err ); } - - } ); + } ); /** * @class mw.Api diff --git a/resources/mediawiki.api/mediawiki.api.js b/resources/mediawiki.api/mediawiki.api.js index cf7443f3..cdc67679 100644 --- a/resources/mediawiki.api/mediawiki.api.js +++ b/resources/mediawiki.api/mediawiki.api.js @@ -20,7 +20,8 @@ dataType: 'json' } - }; + }, + tokenCache = {}; /** * Constructor to create an object to interact with the API of a particular MediaWiki server. @@ -115,7 +116,8 @@ */ ajax: function ( parameters, ajaxOptions ) { var token, - apiDeferred = $.Deferred(); + apiDeferred = $.Deferred(), + xhr; parameters = $.extend( {}, this.defaults.parameters, parameters ); ajaxOptions = $.extend( {}, this.defaults.ajax, ajaxOptions ); @@ -147,7 +149,7 @@ } // Make the AJAX request - $.ajax( ajaxOptions ) + xhr = $.ajax( ajaxOptions ) // If AJAX fails, reject API call with error code 'http' // and details in second argument. .fail( function ( xhr, textStatus, exception ) { @@ -172,11 +174,85 @@ } ); // Return the Promise - return apiDeferred.promise().fail( function ( code, details ) { + return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) { mw.log( 'mw.Api error: ', code, details ); - }); - } + } ); + }, + + /** + * Post to API with specified type of token. If we have no token, get one and try to post. + * If we have a cached token try using that, and if it fails, blank out the + * cached token and start over. For example to change an user option you could do: + * + * new mw.Api().postWithToken( 'options', { + * action: 'options', + * optionname: 'gender', + * optionvalue: 'female' + * } ); + * + * @param {string} tokenType The name of the token, like options or edit. + * @param {Object} params API parameters + * @return {jQuery.Promise} See #post + */ + postWithToken: function ( tokenType, params ) { + var api = this, hasOwn = tokenCache.hasOwnProperty; + if ( hasOwn.call( tokenCache, tokenType ) && tokenCache[tokenType] !== undefined ) { + params.token = tokenCache[tokenType]; + return api.post( params ).then( + null, + function ( code ) { + if ( code === 'badtoken' ) { + // force a new token, clear any old one + tokenCache[tokenType] = params.token = undefined; + return api.post( params ); + } + // Pass the promise forward, so the caller gets error codes + return this; + } + ); + } else { + return api.getToken( tokenType ).then( function ( token ) { + tokenCache[tokenType] = params.token = token; + return api.post( params ); + } ); + } + }, + /** + * Api helper to grab any token. + * + * @param {string} type Token type. + * @return {jQuery.Promise} + * @return {Function} return.done + * @return {string} return.done.token Received token. + */ + getToken: function ( type ) { + var apiPromise, + d = $.Deferred(); + + apiPromise = this.get( { + action: 'tokens', + type: type + }, { + // 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? + } ) + .done( function ( data ) { + // If token type is not available for this user, + // key '...token' is missing or can contain Boolean false + if ( data.tokens && data.tokens[type + 'token'] ) { + d.resolve( data.tokens[type + 'token'] ); + } else { + d.reject( 'token-missing', data ); + } + } ) + .fail( d.reject ); + + return d.promise( { abort: apiPromise.abort } ); + } }; /** diff --git a/resources/mediawiki.api/mediawiki.api.login.js b/resources/mediawiki.api/mediawiki.api.login.js new file mode 100644 index 00000000..ccbae06c --- /dev/null +++ b/resources/mediawiki.api/mediawiki.api.login.js @@ -0,0 +1,54 @@ +/** + * Make the two-step login easier. + * @author Niklas Laxström + * @class mw.Api.plugin.login + * @since 1.22 + */ +( function ( mw, $ ) { + 'use strict'; + + $.extend( mw.Api.prototype, { + /** + * @param {string} username + * @param {string} password + * @return {jQuery.Promise} See mw.Api#post + */ + login: function ( username, password ) { + var params, request, + deferred = $.Deferred(), + api = this; + + params = { + action: 'login', + lgname: username, + lgpassword: password + }; + + request = api.post( params ); + request.fail( deferred.reject ); + request.done( function ( data ) { + params.lgtoken = data.login.token; + api.post( params ) + .fail( deferred.reject ) + .done( function ( data ) { + var code; + if ( data.login && data.login.result === 'Success' ) { + deferred.resolve( data ); + } else { + // Set proper error code whenever possible + code = data.error && data.error.code || 'unknown'; + deferred.reject( code, data ); + } + } ); + } ); + + return deferred.promise( { abort: request.abort } ); + } + } ); + + /** + * @class mw.Api + * @mixins mw.Api.plugin.login + */ + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.api/mediawiki.api.parse.js b/resources/mediawiki.api/mediawiki.api.parse.js index ea0388c1..c4d23b82 100644 --- a/resources/mediawiki.api/mediawiki.api.parse.js +++ b/resources/mediawiki.api/mediawiki.api.parse.js @@ -15,13 +15,15 @@ * @return {string} return.done.data Parsed HTML of `wikitext`. */ parse: function ( wikitext, ok, err ) { - var d = $.Deferred(); + var d = $.Deferred(), + apiPromise; + // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); + d.done( ok ).fail( err ); - this.get( { + apiPromise = this.get( { action: 'parse', + contentmodel: 'wikitext', text: wikitext } ) .done( function ( data ) { @@ -31,7 +33,7 @@ } ) .fail( d.reject ); - return d.promise(); + return d.promise( { abort: apiPromise.abort } ); } } ); diff --git a/resources/mediawiki.api/mediawiki.api.watch.js b/resources/mediawiki.api/mediawiki.api.watch.js index c86a90a7..49a4c622 100644 --- a/resources/mediawiki.api/mediawiki.api.watch.js +++ b/resources/mediawiki.api/mediawiki.api.watch.js @@ -19,10 +19,12 @@ * @return {string} return.done.watch.message Parsed HTML of the confirmational interface message */ function doWatchInternal( page, ok, err, addParams ) { - var params, d = $.Deferred(); + var params, + d = $.Deferred(), + apiPromise; + // Backwards compatibility (< MW 1.20) - d.done( ok ); - d.fail( err ); + d.done( ok ).fail( err ); params = { action: 'watch', @@ -35,13 +37,13 @@ $.extend( params, addParams ); } - this.post( params ) + apiPromise = this.post( params ) .done( function ( data ) { d.resolve( data.watch ); } ) .fail( d.reject ); - return d.promise(); + return d.promise( { abort: apiPromise.abort } ); } $.extend( mw.Api.prototype, { diff --git a/resources/mediawiki.language/mediawiki.language.js b/resources/mediawiki.language/mediawiki.language.js index 7f729bdc..631d13df 100644 --- a/resources/mediawiki.language/mediawiki.language.js +++ b/resources/mediawiki.language/mediawiki.language.js @@ -45,11 +45,34 @@ var language = { */ convertPlural: function ( count, forms ) { var pluralRules, + formCount, + form, + index, + equalsPosition, pluralFormIndex = 0; if ( !forms || forms.length === 0 ) { return ''; } + + // Handle for explicit n= forms + for ( index = 0; index < forms.length; index++ ) { + form = forms[index]; + if ( /^\d+=/.test( form ) ) { + equalsPosition = form.indexOf( '=' ); + formCount = parseInt( form.substring( 0, equalsPosition ), 10 ); + if ( formCount === count ) { + return form.substr( equalsPosition + 1 ); + } + forms[index] = undefined; + } + } + + // Remove explicit plural forms from the forms. + forms = $.map( forms, function ( form ) { + return form; + } ); + pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); if ( !pluralRules ) { // default fallback. diff --git a/resources/mediawiki.language/mediawiki.language.months.js b/resources/mediawiki.language/mediawiki.language.months.js new file mode 100644 index 00000000..3d4b7ee7 --- /dev/null +++ b/resources/mediawiki.language/mediawiki.language.months.js @@ -0,0 +1,54 @@ +/** + * Transfer of month names from messages into mw.language. + * + * Loading this module also ensures the availability of appropriate messages via mw.msg. + */ +( function ( mw, $ ) { + var + monthMessages = [ + 'january', 'february', 'march', 'april', + 'may_long', 'june', 'july', 'august', + 'september', 'october', 'november', 'december' + ], + monthGenMessages = [ + 'january-gen', 'february-gen', 'march-gen', 'april-gen', + 'may-gen', 'june-gen', 'july-gen', 'august-gen', + 'september-gen', 'october-gen', 'november-gen', 'december-gen' + ], + monthAbbrevMessages = [ + 'jan', 'feb', 'mar', 'apr', + 'may', 'jun', 'jul', 'aug', + 'sep', 'oct', 'nov', 'dec' + ]; + + // Function suitable for passing to jQuery.map + // Can't use mw.msg directly because jQuery.map passes element index as second argument + function mwMsgMapper( key ) { + return mw.msg( key ); + } + + /** + * Information about month names in current UI language. + * + * Object keys: + * - `names`: array of month names (in nominative case in languages which have the distinction), + * zero-indexed + * - `genitive`: array of month names in genitive case, zero-indexed + * - `abbrev`: array of three-letter-long abbreviated month names, zero-indexed + * - `keys`: object with three keys like the above, containing zero-indexed arrays of message keys + * for appropriate messages which can be passed to mw.msg. + * + * @property + */ + mw.language.months = { + keys: { + names: monthMessages, + genitive: monthGenMessages, + abbrev: monthAbbrevMessages + }, + names: $.map( monthMessages, mwMsgMapper ), + genitive: $.map( monthGenMessages, mwMsgMapper ), + abbrev: $.map( monthAbbrevMessages, mwMsgMapper ) + }; + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.less/mediawiki.mixins.less b/resources/mediawiki.less/mediawiki.mixins.less new file mode 100644 index 00000000..19a715b9 --- /dev/null +++ b/resources/mediawiki.less/mediawiki.mixins.less @@ -0,0 +1,46 @@ +/** + * Common LESS mixin library for MediaWiki + * + * By default the folder containing this file is included in $wgResourceLoaderLESSImportPaths, + * which makes this file importable by all less files via '@import "mediawiki.mixins";'. + * + * The mixins included below are considered a public interface for MediaWiki extensions. + * The signatures of parametrized mixins should be kept as stable as possible. + * + * See <http://lesscss.org/#-mixins> for more information about how to write mixins. + */ + +.background-image(@url) when (embeddable(@url)) { + background-image: embed(@url); + background-image: url(@url)!ie; +} + +.background-image(@url) when not (embeddable(@url)) { + background-image: url(@url); +} + +/* Note gzip compression means that it is okay to embed twice */ +.background-image-svg(@svg, @fallback) { + background-image: url(@fallback); + /* SVG support using a transparent gradient to guarantee cross-browser + * compatibility (browsers able to understand gradient syntax support also SVG) */ + /* @embed */ background-image: -webkit-linear-gradient(transparent, transparent), url(@svg); + /* @embed */ background-image: linear-gradient(transparent, transparent), url(@svg); +} + +/* Caution: Does not support localisable images */ +.list-style-image(@url) when (embeddable(@url)) { + list-style-image: embed(@url); + list-style-image: url(@url)!ie; +} + +.list-style-image(@url) when not (embeddable(@url)) { + list-style-image: url(@url); +} + +.transition(@string) { + -webkit-transition: @string; + -moz-transition: @string; + -o-transition: @string; + transition: @string; +} diff --git a/resources/mediawiki.libs/mediawiki.libs.jpegmeta.js b/resources/mediawiki.libs/mediawiki.libs.jpegmeta.js index af49889a..22429246 100644 --- a/resources/mediawiki.libs/mediawiki.libs.jpegmeta.js +++ b/resources/mediawiki.libs/mediawiki.libs.jpegmeta.js @@ -274,7 +274,7 @@ this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00"; this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00"; - /* EXIF idents */ + /* Exif idents */ this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00"; /* TIFF types */ diff --git a/resources/mediawiki.page/mediawiki.page.gallery.js b/resources/mediawiki.page/mediawiki.page.gallery.js new file mode 100644 index 00000000..147a869d --- /dev/null +++ b/resources/mediawiki.page/mediawiki.page.gallery.js @@ -0,0 +1,248 @@ +/** + * Show gallery captions when focused. Copied directly from jquery.mw-jump.js. + * Also Dynamically resize images to justify them. + */ +( function ( $, mw ) { + $( function () { + var isTouchScreen, + gettingFocus, + galleries = 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed'; + + // Is there a better way to detect a touchscreen? Current check taken from stack overflow. + isTouchScreen = !!( window.ontouchstart !== undefined || window.DocumentTouch !== undefined && document instanceof window.DocumentTouch ); + + if ( isTouchScreen ) { + // Always show the caption for a touch screen. + $( 'ul.mw-gallery-packed-hover' ) + .addClass( 'mw-gallery-packed-overlay' ) + .removeClass( 'mw-gallery-packed-hover' ); + } else { + // Note use of just "a", not a.image, since we want this to trigger if a link in + // the caption receives focus + $( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) { + // Confusingly jQuery leaves e.type as focusout for delegated blur events + gettingFocus = e.type !== 'blur' && e.type !== 'focusout'; + $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus ); + } ); + } + + // Now on to justification. + // We may still get ragged edges if someone resizes their window. Could bind to + // that event, otoh do we really want to constantly be resizing galleries? + $( galleries ).each( function() { + var lastTop, + $img, + imgWidth, + imgHeight, + rows = [], + $gallery = $( this ); + + $gallery.children( 'li' ).each( function() { + // Math.floor to be paranoid if things are off by 0.00000000001 + var top = Math.floor( $(this ).position().top ), + $this = $( this ); + + if ( top !== lastTop ) { + rows[rows.length] = []; + lastTop = top; + } + + $img = $this.find( 'div.thumb a.image img' ); + if ( $img.length && $img[0].height ) { + imgHeight = $img[0].height; + imgWidth = $img[0].width; + } else { + // If we don't have a real image, get the containing divs width/height. + // Note that if we do have a real image, using this method will generally + // give the same answer, but can be different in the case of a very + // narrow image where extra padding is added. + imgHeight = $this.children().children( 'div:first' ).height(); + imgWidth = $this.children().children( 'div:first' ).width(); + } + + // Hack to make an edge case work ok + if ( imgHeight < 30 ) { + // Don't try and resize this item. + imgHeight = 0; + } + + rows[rows.length-1][rows[rows.length-1].length] = { + $elm: $this, + width: $this.outerWidth(), + imgWidth: imgWidth, + aspect: imgWidth / imgHeight, // XXX: can divide by 0 ever happen? + captionWidth: $this.children().children( 'div.gallerytextwrapper' ).width(), + height: imgHeight + }; + }); + + (function () { + var maxWidth, + combinedAspect, + combinedPadding, + curRow, + curRowHeight, + wantedWidth, + preferredHeight, + newWidth, + padding, + $outerDiv, + $innerDiv, + $imageDiv, + $imageElm, + imageElm, + $caption, + hookInfo, + i, + j, + avgZoom, + totalZoom = 0; + + for ( i = 0; i < rows.length; i++ ) { + maxWidth = $gallery.width(); + combinedAspect = 0; + combinedPadding = 0; + curRow = rows[i]; + curRowHeight = 0; + + for ( j = 0; j < curRow.length; j++ ) { + if ( curRowHeight === 0 ) { + if ( isFinite( curRow[j].height ) ) { + // Get the height of this row, by taking the first + // non-out of bounds height + curRowHeight = curRow[j].height; + } + } + + if ( curRow[j].aspect === 0 || !isFinite( curRow[j].aspect ) ) { + mw.log( 'Skipping item ' + j + ' due to aspect: ' + curRow[j].aspect ); + // One of the dimensions are 0. Probably should + // not try to resize. + combinedPadding += curRow[j].width; + } else { + combinedAspect += curRow[j].aspect; + combinedPadding += curRow[j].width - curRow[j].imgWidth; + } + } + + // Add some padding for inter-element spacing. + combinedPadding += 5 * curRow.length; + wantedWidth = maxWidth - combinedPadding; + preferredHeight = wantedWidth / combinedAspect; + + if ( preferredHeight > curRowHeight * 1.5 ) { + // Only expand at most 1.5 times current size + // As that's as high a resolution as we have. + // Also on the off chance there is a bug in this + // code, would prevent accidentally expanding to + // be 10 billion pixels wide. + mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight/curRowHeight ); + if ( i === rows.length - 1 ) { + // If its the last row, and we can't fit it, + // don't make the entire row huge. + avgZoom = ( totalZoom / ( rows.length - 1 ) ) * curRowHeight; + if ( isFinite( avgZoom ) && avgZoom >= 1 && avgZoom <= 1.5 ) { + preferredHeight = avgZoom; + } else { + // Probably a single row gallery + preferredHeight = curRowHeight; + } + } else { + preferredHeight = 1.5 * curRowHeight; + } + } + if ( !isFinite( preferredHeight ) ) { + // This *definitely* should not happen. + mw.log( 'mw.page.gallery: Trying to resize row ' + i + ' to ' + preferredHeight + '?!' ); + // Skip this row. + continue; + } + if ( preferredHeight < 5 ) { + // Well something clearly went wrong... + mw.log( {maxWidth: maxWidth, combinedPadding: combinedPadding, combinedAspect: combinedAspect, wantedWidth: wantedWidth } ); + mw.log( 'mw.page.gallery: [BUG!] Fitting row ' + i + ' to too small a size: ' + preferredHeight ); + // Skip this row. + continue; + } + + if ( preferredHeight / curRowHeight > 1 ) { + totalZoom += preferredHeight / curRowHeight; + } else { + // If we shrink, still consider that a zoom of 1 + totalZoom += 1; + } + + for ( j = 0; j < curRow.length; j++ ) { + newWidth = preferredHeight * curRow[j].aspect; + padding = curRow[j].width - curRow[j].imgWidth; + $outerDiv = curRow[j].$elm; + $innerDiv = $outerDiv.children( 'div' ).first(); + $imageDiv = $innerDiv.children( 'div.thumb' ); + $imageElm = $imageDiv.find( 'img' ).first(); + imageElm = $imageElm.length ? $imageElm[0] : null; + $caption = $outerDiv.find( 'div.gallerytextwrapper' ); + + + // Since we are going to re-adjust the height, the vertical + // centering margins need to be reset. + $imageDiv.children( 'div' ).css( 'margin', '0px auto' ); + + if ( newWidth < 60 || !isFinite( newWidth ) ) { + // Making something skinnier than this will mess up captions, + mw.log( 'mw.page.gallery: Tried to make image ' + newWidth + 'px wide but too narrow.' ); + if ( newWidth < 1 || !isFinite( newWidth ) ) { + $innerDiv.height( preferredHeight ); + // Don't even try and touch the image size if it could mean + // making it disappear. + continue; + } + } else { + $outerDiv.width( newWidth + padding ); + $innerDiv.width( newWidth + padding ); + $imageDiv.width( newWidth ); + $caption.width( curRow[j].captionWidth + (newWidth - curRow[j].imgWidth ) ); + } + + hookInfo = { + fullWidth: newWidth + padding, + imgWidth: newWidth, + imgHeight: preferredHeight, + $innerDiv: $innerDiv, + $imageDiv: $imageDiv, + $outerDiv: $outerDiv, + // Whether the hook took action + resolved: false + }; + + /** + * Gallery resize. + * + * If your handler resizes an image, it should also set the resolved + * property to true. Additionally, because this module only exposes this + * logic temporarily, you should load your module in position top to + * ensure it is registered before this runs (FIXME: Don't use mw.hook) + * + * See TimedMediaHandler for an example. + * + * @event mediawiki_page_gallery_resize + * @member mw.hook + * @param {Object} hookInfo + */ + mw.hook( 'mediawiki.page.gallery.resize' ).fire( hookInfo ); + + if ( !hookInfo.resolved ) { + if ( imageElm ) { + // We don't always have an img, e.g. in the case of an invalid file. + imageElm.width = newWidth; + imageElm.height = preferredHeight; + } else { + // Not a file box. + $imageDiv.height( preferredHeight ); + } + } + } + } + } )(); + } ); + } ); +} )( jQuery, mediaWiki ); diff --git a/resources/mediawiki.page/mediawiki.page.image.pagination.js b/resources/mediawiki.page/mediawiki.page.image.pagination.js new file mode 100644 index 00000000..fb44a76f --- /dev/null +++ b/resources/mediawiki.page/mediawiki.page.image.pagination.js @@ -0,0 +1,51 @@ +/** + * Change multi-page image navigation so that the current page display can be changed + * without a page reload. Currently, the only image formats that can be multi-page images are + * PDF and DjVu files + */ +( function (mw, $) { + // Use jQuery's load function to specifically select and replace table.multipageimage's child + // tr with the new page's table.multipageimage's tr element. + // table.multipageimage always has only one row. + function loadPage( page ) { + var $multipageimage = $( 'table.multipageimage' ), + $spinner = $.createSpinner( { + size: 'large', + type: 'block' + } ); + + // Set the spinner's dimensions equal to the table's dimensions so that + // the current scroll position is not lost after the table is emptied prior to + // its contents being updated + $spinner.css( { + height: $multipageimage.find( 'tr' ).height(), + width: $multipageimage.find( 'tr' ).width() + } ); + + $multipageimage.empty().append( $spinner ).load( + page + ' table.multipageimage tr', + ajaxifyPageNavigation + ); + } + + function ajaxifyPageNavigation() { + // Intercept the default action of the links in the thumbnail navigation + $( '.multipageimagenavbox' ).one( 'click', 'a', function ( e ) { + loadPage( this.href ); + e.preventDefault(); + } ); + + // Prevent the submission of the page select form and instead call loadPage + $( 'form[name="pageselector"]' ).one( 'change submit', function ( e ) { + loadPage( this.action + '?' + $( this ).serialize() ); + e.preventDefault(); + } ); + } + + $( document ).ready( function() { + // The presence of table.multipageimage signifies that this file is a multi-page image + if( mw.config.get( 'wgNamespaceNumber' ) === 6 && $( 'table.multipageimage' ).length !== 0 ) { + ajaxifyPageNavigation(); + } + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.patrol.ajax.js b/resources/mediawiki.page/mediawiki.page.patrol.ajax.js index d7a07d71..75908eee 100644 --- a/resources/mediawiki.page/mediawiki.page.patrol.ajax.js +++ b/resources/mediawiki.page/mediawiki.page.patrol.ajax.js @@ -11,7 +11,7 @@ // that didn't have patrolToken yet. return; } - $( document ).ready( function () { + $( function () { var $patrolLinks = $( '.patrollink a' ); $patrolLinks.on( 'click', function ( e ) { var $spinner, href, rcid, apiRequest; diff --git a/resources/mediawiki.page/mediawiki.page.ready.js b/resources/mediawiki.page/mediawiki.page.ready.js index 684f582f..ee416d67 100644 --- a/resources/mediawiki.page/mediawiki.page.ready.js +++ b/resources/mediawiki.page/mediawiki.page.ready.js @@ -1,28 +1,40 @@ -( function ( mw, $ ) { - $( function () { +( function ( mw , $ ) { + var supportsPlaceholder = 'placeholder' in document.createElement( 'input' ); + + mw.hook( 'wikipage.content' ).add( function ( $content ) { var $sortableTables; - /* Emulate placeholder if not supported by browser */ - if ( !( 'placeholder' in document.createElement( 'input' ) ) ) { - $( 'input[placeholder]' ).placeholder(); + // Run jquery.placeholder polyfill if placeholder is not supported + if ( !supportsPlaceholder ) { + $content.find( 'input[placeholder]' ).placeholder(); } - /* Enable makeCollapsible */ - $( '.mw-collapsible' ).makeCollapsible(); + // Run jquery.makeCollapsible + $content.find( '.mw-collapsible' ).makeCollapsible(); - /* Lazy load jquery.tablesorter */ - $sortableTables = $( 'table.sortable' ); + // Lazy load jquery.tablesorter + $sortableTables = $content.find( 'table.sortable' ); if ( $sortableTables.length ) { mw.loader.using( 'jquery.tablesorter', function () { $sortableTables.tablesorter(); - }); + } ); } - /* Enable CheckboxShiftClick */ - $( 'input[type=checkbox]:not(.noshiftselect)' ).checkboxShiftClick(); + // Run jquery.checkboxShiftClick + $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick(); + } ); + + // Things outside the wikipage content + $( function () { + + if ( !supportsPlaceholder ) { + // Exclude content to avoid hitting it twice for the (first) wikipage content + $( 'input[placeholder]' ).not( '#mw-content-text input' ).placeholder(); + } - /* Add accesskey hints to the tooltips */ + // Add accesskey hints to the tooltips mw.util.updateTooltipAccessKeys(); } ); + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.startup.js b/resources/mediawiki.page/mediawiki.page.startup.js index 6a11d3e1..38466818 100644 --- a/resources/mediawiki.page/mediawiki.page.startup.js +++ b/resources/mediawiki.page/mediawiki.page.startup.js @@ -5,14 +5,23 @@ // Client profile classes for <html> // Allows for easy hiding/showing of JS or no-JS-specific UI elements $( 'html' ) - .addClass('client-js' ) + .addClass( 'client-js' ) .removeClass( 'client-nojs' ); - // Initialize utilities as soon as the document is ready (mw.util.$content, - // messageBoxNew, profile, tooltip access keys, Table of contents toggle, ..). - // Enqueued into domready from here instead of mediawiki.page.ready to ensure that it gets enqueued - // before other modules hook into document ready, so that mw.util.$content (defined by mw.util.init), - // is defined for them. - $( mw.util.init ); + $( function () { + // Initialize utilities as soon as the document is ready (mw.util.$content, + // messageBoxNew, profile, tooltip access keys, Table of contents toggle, ..). + // In the domready here instead of in mediawiki.page.ready to ensure that it gets enqueued + // before other modules hook into domready, so that mw.util.$content (defined by + // mw.util.init), is defined for them. + mw.util.init(); + + /** + * @event wikipage_content + * @member mw.hook + * @param {jQuery} $content + */ + mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) ); + } ); }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.watch.ajax.js b/resources/mediawiki.page/mediawiki.page.watch.ajax.js index f945fa9d..e9afa4a2 100644 --- a/resources/mediawiki.page/mediawiki.page.watch.ajax.js +++ b/resources/mediawiki.page/mediawiki.page.watch.ajax.js @@ -71,7 +71,7 @@ actionPaths = mw.config.get( 'wgActionPaths' ); - // @todo: Does MediaWiki give action path or query param + // @todo Does MediaWiki give action path or query param // precedence ? If the former, move this to the bottom action = mw.util.getParamValue( 'action', url ); if ( action !== null ) { @@ -100,7 +100,7 @@ updateWatchLink: updateWatchLink }; - $( document ).ready( function () { + $( function () { var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); @@ -161,7 +161,7 @@ cleanTitle = title.replace( /_/g, ' ' ); link = mw.html.element( 'a', { - href: mw.util.wikiGetlink( title ), + href: mw.util.getUrl( title ), title: cleanTitle }, cleanTitle ); diff --git a/resources/mediawiki.special/images/arrow-collapsed-ltr.png b/resources/mediawiki.special/images/arrow-collapsed-ltr.png Binary files differdeleted file mode 100644 index 467a555a..00000000 --- a/resources/mediawiki.special/images/arrow-collapsed-ltr.png +++ /dev/null diff --git a/resources/mediawiki.special/images/arrow-collapsed-rtl.png b/resources/mediawiki.special/images/arrow-collapsed-rtl.png Binary files differdeleted file mode 100644 index 2246254f..00000000 --- a/resources/mediawiki.special/images/arrow-collapsed-rtl.png +++ /dev/null diff --git a/resources/mediawiki.special/images/arrow-expanded.png b/resources/mediawiki.special/images/arrow-expanded.png Binary files differdeleted file mode 100644 index 58a9fc66..00000000 --- a/resources/mediawiki.special/images/arrow-expanded.png +++ /dev/null diff --git a/resources/mediawiki.special/images/glyph-people-large.png b/resources/mediawiki.special/images/glyph-people-large.png Binary files differnew file mode 100644 index 00000000..0578be0b --- /dev/null +++ b/resources/mediawiki.special/images/glyph-people-large.png diff --git a/resources/mediawiki.special/images/icon-contributors.png b/resources/mediawiki.special/images/icon-contributors.png Binary files differnew file mode 100644 index 00000000..f933aa69 --- /dev/null +++ b/resources/mediawiki.special/images/icon-contributors.png diff --git a/resources/mediawiki.special/images/icon-edits.png b/resources/mediawiki.special/images/icon-edits.png Binary files differnew file mode 100644 index 00000000..39f4f2de --- /dev/null +++ b/resources/mediawiki.special/images/icon-edits.png diff --git a/resources/mediawiki.special/images/icon-lock.png b/resources/mediawiki.special/images/icon-lock.png Binary files differnew file mode 100644 index 00000000..03f0eecd --- /dev/null +++ b/resources/mediawiki.special/images/icon-lock.png diff --git a/resources/mediawiki.special/images/icon-pages.png b/resources/mediawiki.special/images/icon-pages.png Binary files differnew file mode 100644 index 00000000..59513db2 --- /dev/null +++ b/resources/mediawiki.special/images/icon-pages.png diff --git a/resources/mediawiki.special/mediawiki.special.block.js b/resources/mediawiki.special/mediawiki.special.block.js index 2a158dfb..b8bcf177 100644 --- a/resources/mediawiki.special/mediawiki.special.block.js +++ b/resources/mediawiki.special/mediawiki.special.block.js @@ -2,7 +2,7 @@ * JavaScript for Special:Block */ ( function ( mw, $ ) { - $( document ).ready( function () { + $( function () { var $blockTarget = $( '#mw-bi-target' ), $anonOnlyRow = $( '#mw-input-wpHardBlock' ).closest( 'tr' ), $enableAutoblockRow = $( '#mw-input-wpAutoBlock' ).closest( 'tr' ), diff --git a/resources/mediawiki.special/mediawiki.special.changeemail.js b/resources/mediawiki.special/mediawiki.special.changeemail.js index 14c2f036..2d22bad0 100644 --- a/resources/mediawiki.special/mediawiki.special.changeemail.js +++ b/resources/mediawiki.special/mediawiki.special.changeemail.js @@ -23,7 +23,7 @@ } } - $( document ).ready( function () { + $( 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. diff --git a/resources/mediawiki.special/mediawiki.special.changeslist.css b/resources/mediawiki.special/mediawiki.special.changeslist.css index fcdeba1b..5e4af7b6 100644 --- a/resources/mediawiki.special/mediawiki.special.changeslist.css +++ b/resources/mediawiki.special/mediawiki.special.changeslist.css @@ -2,63 +2,6 @@ * Styling for Special:Watchlist and Special:RecentChanges */ -table.mw-enhanced-rc { - border: 0; - border-spacing: 0; -} - -table.mw-enhanced-rc th, -table.mw-enhanced-rc td { - padding: 0; - vertical-align: top; -} - -td.mw-enhanced-rc { - white-space: nowrap; - font-family: monospace; -} - -.mw-enhanced-rc-time { - font-family: monospace; -} - -table.mw-enhanced-rc td.mw-enhanced-rc-nested { - padding-left: 1em; -} - -/* Show/hide arrows in enhanced changeslist */ -.mw-enhanced-rc .collapsible-expander { - float: none; -} - -/* 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-space { - display: inline-block; - *display: inline; /* IE7 and below */ - zoom: 1; - width: 15px; - height: 15px; -} - -/* let it look like it is clickable */ -.mw-enhancedchanges-arrow.mw-collapsible-toggle { - cursor: pointer; -} - -.mw-enhancedchanges-arrow.mw-collapsible-toggle-collapsed { - /* @embed */ - background: url(images/arrow-collapsed-ltr.png) no-repeat left center; -} - -.mw-enhancedchanges-arrow.mw-collapsible-toggle-expanded { - /* @embed */ - background: url(images/arrow-expanded.png) no-repeat left center; -} - .mw-changeslist-line-watched .mw-title { font-weight: bold; } diff --git a/resources/mediawiki.special/mediawiki.special.changeslist.enhanced.css b/resources/mediawiki.special/mediawiki.special.changeslist.enhanced.css new file mode 100644 index 00000000..bed580d7 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.changeslist.enhanced.css @@ -0,0 +1,66 @@ +/** + * Styling for Special:Watchlist and Special:RecentChanges when preference 'usenewrc' + * a.k.a. Enhanced Recent Changes is enabled. + */ + +table.mw-enhanced-rc { + border: 0; + border-spacing: 0; +} + +table.mw-enhanced-rc th, +table.mw-enhanced-rc td { + padding: 0; + vertical-align: top; +} + +td.mw-enhanced-rc { + white-space: nowrap; + font-family: monospace; +} + +.mw-enhanced-rc-time { + font-family: monospace; +} + +table.mw-enhanced-rc td.mw-enhanced-rc-nested { + padding-left: 1em; +} + +/* Show/hide arrows in enhanced changeslist */ +.mw-enhanced-rc .collapsible-expander { + float: none; +} + +/* If JS is disabled, the arrows or the placeholder space shouldn't be shown */ +.client-nojs .mw-enhancedchanges-arrow-space { + display: none; +} + +/* + * And if it's enabled, let's optimize the collapsing a little: hide the rows + * that would be hidden by jquery.makeCollapsible with CSS to save us some + * reflows and repaints. This doesn't work on browsers that don't fully support + * CSS2 (IE6), but it's okay, this will be done in JavaScript with old degraded + * performance instead. + */ +.client-js table.mw-enhanced-rc.mw-collapsed tr + tr { + display: none; +} + +.mw-enhancedchanges-arrow-space { + display: inline-block; + *display: inline; /* IE7 and below */ + zoom: 1; + width: 15px; + height: 15px; +} + +/* let it look like it is clickable */ +.mw-enhancedchanges-arrow.mw-collapsible-toggle { + cursor: pointer; +} + +.mw-enhanced-watched .mw-enhanced-rc-time { + font-weight: bold; +} diff --git a/resources/mediawiki.special/mediawiki.special.createAccount.css b/resources/mediawiki.special/mediawiki.special.createAccount.css new file mode 100644 index 00000000..11d00e75 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.createAccount.css @@ -0,0 +1,89 @@ +/* Disable the underline that Vector puts on h2 headings, and bold them. */ +.mw-ui-container h2 { + border: 0; + font-weight: bold; +} + +/**** shuffled CAPTCHA ****/ +#wpCaptchaWord { + margin-top: 6px; +} + +.mw-createacct-captcha-container { + background-color: #f8f8f8; + border: 1px solid #c9c9c9; + padding: 10px; + text-align: center; +} + +.mw-createacct-captcha-assisted { + display: block; + margin-top: 0.5em; +} + +/* Put a border around the fancycaptcha-image-container. */ +.mw-createacct-captcha-and-reload { + border: 1px solid #c9c9c9; + display: table-cell; /* Other display formats end up too wide */ + width: 270px; + background-color: #FFF; +} + +/* Make the fancycaptcha-image-container full-width within its parent. */ +.fancycaptcha-image-container +{ + width: 100%; +} + +/**** Benefits column CSS to the right (if it fits) of the form. ****/ +.mw-ui-container #userloginForm { + float: left; +} + +div.mw-createacct-benefits-container { + /* Keeps this column compact and close to the form, but tends to squish contents. */ + float: left; +} + +div.mw-createacct-benefits-container h2 { + margin-bottom: 30px; +} + +.mw-number-text.icon-edits { + /* @embed */ + background: url(images/icon-edits.png) no-repeat left center; +} + +.mw-number-text.icon-pages { + /* @embed */ + background: url(images/icon-pages.png) no-repeat left center; +} + +.mw-number-text.icon-contributors { + /* @embed */ + background: url(images/icon-contributors.png) no-repeat left center; +} + +/* Special font for numbers in benefits*/ +div.mw-number-text h3 { + top: 0; + margin: 0; + padding: 0; + color: #252525; + font-family: 'Georgia', serif; + font-weight: normal; + font-size: 2.2em; + line-height: 1.2; + text-align: center; +} + +/* Contains a number and explanatory text, with space for an icon */ +div.mw-number-text { + display: block; + font-size: 1.2em; + color: #444; + margin-top: 1em; + padding: 0 0 0 95px; /* 80px wide icon plus "margin" */ + min-height: 75px; /* matches max icon height, ensures icon emblem is visible */ + text-align: center; +} diff --git a/resources/mediawiki.special/mediawiki.special.createAccount.js b/resources/mediawiki.special/mediawiki.special.createAccount.js new file mode 100644 index 00000000..5cbb1ee0 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.createAccount.js @@ -0,0 +1,112 @@ +/** + * JavaScript for Create account form (Special:UserLogin?type=signup). + */ +( function ( mw, $ ) { + + // When sending password by email, hide the password input fields. + // This function doesn't need to be loaded early by ResourceLoader, but is tiny. + function hidePasswordOnEmail() { + // Always required if checked, otherwise it depends, so we use the original + var $emailLabel = $( 'label[for="wpEmail"]' ), + originalText = $emailLabel.text(), + requiredText = mw.message( 'createacct-emailrequired' ).text(), + $createByMailCheckbox = $( '#wpCreateaccountMail' ), + $beforePwds = $( '.mw-row-password:first' ).prev(), + $pwds; + + function updateForCheckbox() { + var checked = $createByMailCheckbox.prop( 'checked' ); + if ( checked ) { + $pwds = $( '.mw-row-password' ).detach(); + $emailLabel.text( requiredText ); + } else { + if ( $pwds ) { + $beforePwds.after( $pwds ); + $pwds = null; + } + $emailLabel.text( originalText ); + } + } + + $createByMailCheckbox.on( 'change', updateForCheckbox ); + updateForCheckbox(); + } + + // Move the FancyCaptcha image into a more attractive container. + // This function does need to be run early by ResourceLoader. + function adjustFancyCaptcha() { + var $content = $( '#mw-content-text' ), + $submit = $content.find( '#wpCreateaccount' ), + tabIndex, + $captchaStuff, + $captchaImageContainer, + // JavaScript can't yet parse the message createacct-imgcaptcha-help when it + // contains a MediaWiki transclusion, so PHP parses it and sends the HTML. + helpMsg = mw.config.get( 'wgCreateacctImgcaptchaHelp' ), + helpHtml = ''; + + /* + * CAPTCHA + * The CAPTCHA is in a div style="captcha" at the top of the form. + * If it's a FancyCaptcha, then we remove it and insert it lower down, + * in a customized div with just what we need (e.g. no + * fancycaptcha-createaccount message). + */ + if ( !$submit.length) { + return; + } + tabIndex = $submit.prop( 'tabindex' ) - 1; + $captchaStuff = $content.find ( '.captcha' ); + + if ( $captchaStuff.length ) { + + // The FancyCaptcha has this class in the ConfirmEdit extension + // after 2013-04-18. + $captchaImageContainer = $captchaStuff.find( '.fancycaptcha-image-container' ); + if ( $captchaImageContainer.length !== 1 ) { + return; + } + + $captchaStuff.remove(); + + if ( helpMsg) { + helpHtml = '<small class="mw-createacct-captcha-assisted">' + helpMsg + '</small>'; + } + + // Insert another div before the submit button that will include the + // repositioned FancyCaptcha div, an input field, and possible help. + $submit.closest( 'div' ) + .before( [ + '<div>', + '<label for="wpCaptchaWord">' + mw.message( 'createacct-captcha' ).escaped() + '</label>', + '<div class="mw-createacct-captcha-container">', + '<div class="mw-createacct-captcha-and-reload" />', + '<input id="wpCaptchaWord" name="wpCaptchaWord" type="text" placeholder="' + + mw.message( 'createacct-imgcaptcha-ph' ).escaped() + + '" tabindex="' + tabIndex + '" autocapitalize="off" autocorrect="off">', + helpHtml, + '</div>', + '</div>' + ].join( '' ) + ); + + // Stick the FancyCaptcha container inside our bordered and framed parents. + $captchaImageContainer + .prependTo( $content.find( '.mw-createacct-captcha-and-reload' ) ); + + // Find the input field, add the text (if any) of the existing CAPTCHA + // field (although usually it's blanked out on every redisplay), + // and after it move over the hidden field that tells the CAPTCHA + // what to do. + $content.find( '#wpCaptchaWord' ) + .val( $captchaStuff.find( '#wpCaptchaWord' ).val() ) + .after( $captchaStuff.find( '#wpCaptchaId' ) ); + } + } + + $( function () { + adjustFancyCaptcha(); + hidePasswordOnEmail(); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki.special/mediawiki.special.movePage.js b/resources/mediawiki.special/mediawiki.special.movePage.js index f719d07c..922eba5e 100644 --- a/resources/mediawiki.special/mediawiki.special.movePage.js +++ b/resources/mediawiki.special/mediawiki.special.movePage.js @@ -1,6 +1,6 @@ /** * JavaScript for Special:MovePage */ -jQuery( document ).ready( function ( $ ) { +jQuery( function ( $ ) { $( '#wpReason, #wpNewTitleMain' ).byteLimit(); } ); diff --git a/resources/mediawiki.special/mediawiki.special.pagesWithProp.css b/resources/mediawiki.special/mediawiki.special.pagesWithProp.css new file mode 100644 index 00000000..7ef75d0c --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.pagesWithProp.css @@ -0,0 +1,4 @@ +/* Distinguish actual data from information about it being hidden visually */ +.prop-value-hidden { + font-style: italic; +} diff --git a/resources/mediawiki.special/mediawiki.special.preferences.js b/resources/mediawiki.special/mediawiki.special.preferences.js index 6eaec6a1..03d93d00 100644 --- a/resources/mediawiki.special/mediawiki.special.preferences.js +++ b/resources/mediawiki.special/mediawiki.special.preferences.js @@ -1,7 +1,7 @@ /** * JavaScript for Special:Preferences */ -jQuery( document ).ready( function ( $ ) { +jQuery( function ( $ ) { var $preftoc, $preferences, $fieldsets, $legends, hash, $tzSelect, $tzTextbox, $localtimeHolder, servertime; diff --git a/resources/mediawiki.special/mediawiki.special.recentchanges.js b/resources/mediawiki.special/mediawiki.special.recentchanges.js index d1c1354f..79d793af 100644 --- a/resources/mediawiki.special/mediawiki.special.recentchanges.js +++ b/resources/mediawiki.special/mediawiki.special.recentchanges.js @@ -27,7 +27,7 @@ } }; - $( document ).ready( rc.init ); + $( rc.init ); mw.special.recentchanges = rc; diff --git a/resources/mediawiki.special/mediawiki.special.search.js b/resources/mediawiki.special/mediawiki.special.search.js index 2dab3026..035252bf 100644 --- a/resources/mediawiki.special/mediawiki.special.search.js +++ b/resources/mediawiki.special/mediawiki.special.search.js @@ -2,7 +2,7 @@ * JavaScript for Special:Search */ ( function ( mw, $ ) { - $( document ).ready( function () { + $( function () { var $checkboxes, $headerLinks; // Emulate HTML5 autofocus behavior in non HTML5 compliant browsers diff --git a/resources/mediawiki.special/mediawiki.special.undelete.js b/resources/mediawiki.special/mediawiki.special.undelete.js index d20aab51..0dea3ef9 100644 --- a/resources/mediawiki.special/mediawiki.special.undelete.js +++ b/resources/mediawiki.special/mediawiki.special.undelete.js @@ -1,7 +1,7 @@ /** * JavaScript for Special:Undelete */ -jQuery( document ).ready( function ( $ ) { +jQuery( function ( $ ) { $( '#mw-undelete-invert' ).click( function ( e ) { $( '#undelete input[type="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 75532f18..3f40c549 100644 --- a/resources/mediawiki.special/mediawiki.special.upload.js +++ b/resources/mediawiki.special/mediawiki.special.upload.js @@ -6,7 +6,7 @@ /** * Add a preview to the upload form */ - $( document ).ready( function () { + $( function () { /** * Is the FileAPI available with sufficient functionality? */ @@ -290,7 +290,7 @@ /** * Disable all upload source fields except the selected one */ - $( document ).ready( function () { + $( function () { var i, $row, $rows = $( '.mw-htmlform-field-UploadSourceField' ); diff --git a/resources/mediawiki.special/mediawiki.special.userLogin.css b/resources/mediawiki.special/mediawiki.special.userLogin.css new file mode 100644 index 00000000..24c8d771 --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.userLogin.css @@ -0,0 +1,39 @@ +/* Styles just for VForm user login */ +#mw-userlogin-help { + text-align: center; +} + +.mw-ui-vform .mw-secure { + /* @embed */ + background: url(images/icon-lock.png) no-repeat scroll left center transparent; + margin: 0 0 0 1px; + padding: 0 0 0 11px; +} + +/* The login form invites users to create an account */ +#mw-createaccount-cta { + width: 20em; + height: 10em; + /* @embed */ + background: url(images/glyph-people-large.png) no-repeat 50%; + margin: 0 auto; +} + +#mw-createaccount-cta h3, +#mw-createaccount-another h3 { + font-size: 0.9em; + font-weight: normal; + text-align: center; +} + +#mw-createaccount-cta h3 { + padding-top: 4em; +} + +#mw-createaccount-join { + margin-left: 0.75em; + /* Separate from background image */ + box-shadow: 4px 4px 4px 4px rgba(255, 255, 255, 1); + width: auto; + display: inline-block; +} diff --git a/resources/mediawiki.special/mediawiki.special.userLogin.signup.js b/resources/mediawiki.special/mediawiki.special.userLogin.signup.js deleted file mode 100644 index bba42605..00000000 --- a/resources/mediawiki.special/mediawiki.special.userLogin.signup.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 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.special/mediawiki.special.vforms.css b/resources/mediawiki.special/mediawiki.special.vforms.css new file mode 100644 index 00000000..768a9c6e --- /dev/null +++ b/resources/mediawiki.special/mediawiki.special.vforms.css @@ -0,0 +1,46 @@ +/* + * When inside the VForm style, disable the border that Vector and other skins + * put on the div surrounding the login/create account form. + * Also disable the margin and padding that Vector puts around the form. + */ +.mw-ui-container #userloginForm, +.mw-ui-container #userlogin { + border: 0; + margin: 0; + padding: 0; +} + +/* Reposition and resize language links, which appear on a per-wiki basis */ +.mw-ui-container #languagelinks { + margin-bottom: 2em; + font-size: 0.8em; +} + +/* Put some space under template's header, which may contain CAPTCHA HTML.*/ +section.mw-form-header { + margin-bottom: 10px; +} + +/* + * Styles for information boxes. + */ +.mw-ui-vform .errorbox, +.mw-ui-vform .warningbox, +.mw-ui-vform .successbox { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + margin: 0 0 1em 0; + padding: 0.5em; + word-wrap: break-word; +} + +/* + * Override the right margin of the form to give space in case a benefits + * column appears to the side. + * + */ +.mw-ui-container #userloginForm { + margin-right: 100px; +} diff --git a/resources/mediawiki.ui/mediawiki.ui.default.css b/resources/mediawiki.ui/mediawiki.ui.default.css new file mode 100644 index 00000000..b0726165 --- /dev/null +++ b/resources/mediawiki.ui/mediawiki.ui.default.css @@ -0,0 +1,272 @@ +@charset "UTF-8"; +/** + * Provide Agora appearance for mw-ui-* classes when using a skin other than + * Vector. + * Compass builds these Agora styles from source Sass files in + * extensions/Agora/modules/scss + */ +/* _effects.scss */ +/* Mixins for visual effects in CSS3 */ +/* line 7, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-flush-left { + float: left; + margin-left: 0; + padding-left: 0; +} + +/* line 11, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-flush-right { + float: right; + margin-right: 0; + padding-right: 0; +} + +/* line 15, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-center-block { + display: block; + margin-left: auto; + margin-right: auto; +} + +/* line 4, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button { + display: -moz-inline-stack; + display: inline-block; + vertical-align: middle; + *vertical-align: auto; + zoom: 1; + *display: inline; + padding: 0.4em 1em 0.4em 1em; + margin: 0; + background-color: #c9c9c9; + *background-color: #c9c9c9; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDCDCDC', endColorstr='#FFC9C9C9'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dcdcdc), color-stop(100%, #c9c9c9)); + background-image: -webkit-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: -moz-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: -o-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: linear-gradient(top, #dcdcdc, #c9c9c9); + color: black; + text-shadow: 0 1px 1px rgba(201, 201, 201, 0.3); + border: 1px solid #c4c4c4; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + vertical-align: middle; + text-align: center; + text-decoration: none; + font-weight: bold; + cursor: pointer; +} +/* line 38, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:hover, .mw-ui-button.mw-ui-hover { + background-color: gainsboro; + *background-color: gainsboro; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFE9E9E9', endColorstr='#FFDCDCDC'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #e9e9e9), color-stop(100%, #dcdcdc)); + background-image: -webkit-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: -moz-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: -o-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: linear-gradient(top, #e9e9e9, #dcdcdc); + text-decoration: none; +} +/* line 44, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:active, .mw-ui-button.mw-ui-active { + background-image: none; + background-color: #c1c1c1; + text-shadow: none; +} +/* line 54, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:disabled, .mw-ui-button.mw-ui-disabled { + background-image: none; + background-color: #c9c9c9; + opacity: 0.5; + text-shadow: none; +} +/* line 30, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button:disabled, .mw-ui-button.mw-ui-disabled { + cursor: default; +} +/* line 36, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button.mw-ui-big { + font-size: 1.3em; +} +/* line 41, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button.mw-ui-block { + display: block; + width: 100%; +} + +/* line 49, sourcefiles/scss/components/default/_buttons.scss */ +a.mw-ui-button { + text-decoration: none; +} + +/* line 56, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > * { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + float: left; +} +/* line 60, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > *:first-child { + -moz-border-radius-topleft: 3px; + -webkit-border-top-left-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -webkit-border-bottom-left-radius: 3px; + border-bottom-left-radius: 3px; +} +/* line 65, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > *:last-child { + -moz-border-radius-topright: 3px; + -webkit-border-top-right-radius: 3px; + border-top-right-radius: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-bottom-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +/* line 14, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 290px; +} +/* line 20, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; +} +/* line 28, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input, +.mw-ui-vform > div .mw-ui-button { + display: block; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0; + width: 100%; +} +/* line 37, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]) { + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + color: #252525; + padding: 0.35em 0 0.35em 0.5em; +} +/* line 11, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]):focus { + box-shadow: #4091ed 0px 0px 5px; + border-color: #4091ed; +} +/* line 13, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]):focus:not([type=checkbox]):not([type=radio]) { + outline: 0; +} +/* line 41, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div label { + display: block; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + color: #4a4a4a; + width: auto; + margin: 0 0 0.2em 0; + padding: 0; +} +/* line 38, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div label * { + font-weight: normal; +} +/* line 52, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input[type="checkbox"], +.mw-ui-vform > div input[type="radio"] { + display: inline; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + width: auto; +} +/* line 63, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform .error { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + margin: 0 0 1em 0; + padding: 0.5em; + color: #cc0000; + border: 1px solid #fac5c5; + background-color: #fae3e3; + text-shadow: 0 1px #fae3e3; + word-wrap: break-word; +} + +/* line 86, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform-div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; +} + +/* line 96, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-input { + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + color: #252525; + padding: 0.35em 0 0.35em 0.5em; +} +/* line 11, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-input:focus { + box-shadow: #4091ed 0px 0px 5px; + border-color: #4091ed; +} +/* line 13, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-input:focus:not([type=checkbox]):not([type=radio]) { + outline: 0; +} + +/* line 103, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-label { + font-size: 0.9em; + color: #4a4a4a; +} +/* line 38, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-label * { + font-weight: normal; +} + +/* line 112, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-checkbox-label, .mw-ui-radio-label { + margin-bottom: 0.5em; + cursor: pointer; + vertical-align: bottom; + line-height: normal; + font-weight: normal; +} +/* line 54, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-checkbox-label > input[type="checkbox"], .mw-ui-checkbox-label > input[type="radio"], .mw-ui-radio-label > input[type="checkbox"], .mw-ui-radio-label > input[type="radio"] { + width: auto; + height: auto; + margin: 0 0.1em 0em 0; + padding: 0; + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + cursor: pointer; +} diff --git a/resources/mediawiki.ui/mediawiki.ui.vector.css b/resources/mediawiki.ui/mediawiki.ui.vector.css new file mode 100644 index 00000000..fd9e0915 --- /dev/null +++ b/resources/mediawiki.ui/mediawiki.ui.vector.css @@ -0,0 +1,414 @@ +@charset "UTF-8"; +/** + * Provide Agora appearance for mw-ui-* classes when using the Vector skin. + * Compass builds these Agora styles from source Sass files in + * extensions/Agora/modules/scss + */ +/* _effects.scss */ +/* Mixins for visual effects in CSS3 */ +/* line 7, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-flush-left { + float: left; + margin-left: 0; + padding-left: 0; +} + +/* line 11, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-flush-right { + float: right; + margin-right: 0; + padding-right: 0; +} + +/* line 15, sourcefiles/scss/components/_utilities.scss */ +.mw-ui-center-block { + display: block; + margin-left: auto; + margin-right: auto; +} + +/* line 4, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button { + display: -moz-inline-stack; + display: inline-block; + vertical-align: middle; + *vertical-align: auto; + zoom: 1; + *display: inline; + padding: 0.4em 1em 0.4em 1em; + margin: 0; + background-color: #c9c9c9; + *background-color: #c9c9c9; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDCDCDC', endColorstr='#FFC9C9C9'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dcdcdc), color-stop(100%, #c9c9c9)); + background-image: -webkit-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: -moz-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: -o-linear-gradient(top, #dcdcdc, #c9c9c9); + background-image: linear-gradient(top, #dcdcdc, #c9c9c9); + color: black; + text-shadow: 0 1px 1px rgba(201, 201, 201, 0.3); + border: 1px solid #c4c4c4; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + vertical-align: middle; + text-align: center; + text-decoration: none; + font-weight: bold; + cursor: pointer; +} +/* line 38, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:hover, .mw-ui-button.mw-ui-hover { + background-color: gainsboro; + *background-color: gainsboro; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFE9E9E9', endColorstr='#FFDCDCDC'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #e9e9e9), color-stop(100%, #dcdcdc)); + background-image: -webkit-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: -moz-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: -o-linear-gradient(top, #e9e9e9, #dcdcdc); + background-image: linear-gradient(top, #e9e9e9, #dcdcdc); + text-decoration: none; +} +/* line 44, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:active, .mw-ui-button.mw-ui-active { + background-image: none; + background-color: #c1c1c1; + text-shadow: none; +} +/* line 54, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button:disabled, .mw-ui-button.mw-ui-disabled { + background-image: none; + background-color: #c9c9c9; + opacity: 0.5; + text-shadow: none; +} +/* line 30, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button:disabled, .mw-ui-button.mw-ui-disabled { + cursor: default; +} +/* line 36, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button.mw-ui-big { + font-size: 1.3em; +} +/* line 41, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button.mw-ui-block { + display: block; + width: 100%; +} + +/* line 49, sourcefiles/scss/components/default/_buttons.scss */ +a.mw-ui-button { + text-decoration: none; +} + +/* line 56, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > * { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + float: left; +} +/* line 60, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > *:first-child { + -moz-border-radius-topleft: 3px; + -webkit-border-top-left-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -webkit-border-bottom-left-radius: 3px; + border-bottom-left-radius: 3px; +} +/* line 65, sourcefiles/scss/components/default/_buttons.scss */ +.mw-ui-button-group > *:last-child { + -moz-border-radius-topright: 3px; + -webkit-border-top-right-radius: 3px; + border-top-right-radius: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-bottom-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +/* line 3, sourcefiles/scss/components/vector/_buttons.scss */ +.mw-ui-button { + font-size: 1em; + line-height: 1.4em; +} +/* line 6, sourcefiles/scss/components/vector/_buttons.scss */ +.mw-ui-button.mw-ui-primary { + background-color: #3366bb; + *background-color: #3366bb; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF4779CD', endColorstr='#FF3366BB'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4779cd), color-stop(100%, #3366bb)); + background-image: -webkit-linear-gradient(top, #4779cd, #3366bb); + background-image: -moz-linear-gradient(top, #4779cd, #3366bb); + background-image: -o-linear-gradient(top, #4779cd, #3366bb); + background-image: linear-gradient(top, #4779cd, #3366bb); + color: white; + text-shadow: 0 1px 1px rgba(51, 102, 187, 0.75); + border: 1px solid #3162b3; +} +/* line 38, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-primary:hover, .mw-ui-button.mw-ui-primary.mw-ui-hover { + background-color: #4779cd; + *background-color: #4779cd; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF5B88D2', endColorstr='#FF4779CD'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5b88d2), color-stop(100%, #4779cd)); + background-image: -webkit-linear-gradient(top, #5b88d2, #4779cd); + background-image: -moz-linear-gradient(top, #5b88d2, #4779cd); + background-image: -o-linear-gradient(top, #5b88d2, #4779cd); + background-image: linear-gradient(top, #5b88d2, #4779cd); + text-decoration: none; +} +/* line 44, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-primary:active, .mw-ui-button.mw-ui-primary.mw-ui-active { + background-image: none; + background-color: #305faf; + text-shadow: none; +} +/* line 54, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-primary:disabled, .mw-ui-button.mw-ui-primary.mw-ui-disabled { + background-image: none; + background-color: #3366bb; + opacity: 0.5; + text-shadow: none; +} +/* line 10, sourcefiles/scss/components/vector/_buttons.scss */ +.mw-ui-button.mw-ui-constructive { + background-color: #27aa65; + *background-color: #27aa65; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF2EC977', endColorstr='#FF27AA65'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2ec977), color-stop(100%, #27aa65)); + background-image: -webkit-linear-gradient(top, #2ec977, #27aa65); + background-image: -moz-linear-gradient(top, #2ec977, #27aa65); + background-image: -o-linear-gradient(top, #2ec977, #27aa65); + background-image: linear-gradient(top, #2ec977, #27aa65); + color: white; + text-shadow: 0 1px 1px rgba(39, 170, 101, 0.75); + border: 1px solid #25a260; +} +/* line 38, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-constructive:hover, .mw-ui-button.mw-ui-constructive.mw-ui-hover { + background-color: #2ec977; + *background-color: #2ec977; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF3ED384', endColorstr='#FF2EC977'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #3ed384), color-stop(100%, #2ec977)); + background-image: -webkit-linear-gradient(top, #3ed384, #2ec977); + background-image: -moz-linear-gradient(top, #3ed384, #2ec977); + background-image: -o-linear-gradient(top, #3ed384, #2ec977); + background-image: linear-gradient(top, #3ed384, #2ec977); + text-decoration: none; +} +/* line 44, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-constructive:active, .mw-ui-button.mw-ui-constructive.mw-ui-active { + background-image: none; + background-color: #249e5e; + text-shadow: none; +} +/* line 54, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-constructive:disabled, .mw-ui-button.mw-ui-constructive.mw-ui-disabled { + background-image: none; + background-color: #27aa65; + opacity: 0.5; + text-shadow: none; +} +/* line 14, sourcefiles/scss/components/vector/_buttons.scss */ +.mw-ui-button.mw-ui-destructive { + background-color: #cc0000; + *background-color: #cc0000; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFF20000', endColorstr='#FFCC0000'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f20000), color-stop(100%, #cc0000)); + background-image: -webkit-linear-gradient(top, #f20000, #cc0000); + background-image: -moz-linear-gradient(top, #f20000, #cc0000); + background-image: -o-linear-gradient(top, #f20000, #cc0000); + background-image: linear-gradient(top, #f20000, #cc0000); + color: white; + text-shadow: 0 1px 1px rgba(204, 0, 0, 0.75); + border: 1px solid #c20000; +} +/* line 38, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-destructive:hover, .mw-ui-button.mw-ui-destructive.mw-ui-hover { + background-color: #f20000; + *background-color: #f20000; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF0D0D', endColorstr='#FFF20000'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ff0d0d), color-stop(100%, #f20000)); + background-image: -webkit-linear-gradient(top, #ff0d0d, #f20000); + background-image: -moz-linear-gradient(top, #ff0d0d, #f20000); + background-image: -o-linear-gradient(top, #ff0d0d, #f20000); + background-image: linear-gradient(top, #ff0d0d, #f20000); + text-decoration: none; +} +/* line 44, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-destructive:active, .mw-ui-button.mw-ui-destructive.mw-ui-active { + background-image: none; + background-color: #bd0000; + text-shadow: none; +} +/* line 54, sourcefiles/scss/mixins/_effects.scss */ +.mw-ui-button.mw-ui-destructive:disabled, .mw-ui-button.mw-ui-destructive.mw-ui-disabled { + background-image: none; + background-color: #cc0000; + opacity: 0.5; + text-shadow: none; +} + +/* line 14, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 290px; +} +/* line 20, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; +} +/* line 28, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input, +.mw-ui-vform > div .mw-ui-button { + display: block; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0; + width: 100%; +} +/* line 37, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]) { + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + color: #252525; + padding: 0.35em 0 0.35em 0.5em; +} +/* line 11, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]):focus { + box-shadow: #4091ed 0px 0px 5px; + border-color: #4091ed; +} +/* line 13, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div input:not([type=button]):not([type=submit]):not([type=file]):focus:not([type=checkbox]):not([type=radio]) { + outline: 0; +} +/* line 41, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div label { + display: block; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + color: #4a4a4a; + width: auto; + margin: 0 0 0.2em 0; + padding: 0; +} +/* line 38, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-vform > div label * { + font-weight: normal; +} +/* line 52, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform > div input[type="checkbox"], +.mw-ui-vform > div input[type="radio"] { + display: inline; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + width: auto; +} +/* line 63, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform .error { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + margin: 0 0 1em 0; + padding: 0.5em; + color: #cc0000; + border: 1px solid #fac5c5; + background-color: #fae3e3; + text-shadow: 0 1px #fae3e3; + word-wrap: break-word; +} + +/* line 86, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-vform-div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; +} + +/* line 96, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-input { + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + color: #252525; + padding: 0.35em 0 0.35em 0.5em; +} +/* line 11, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-input:focus { + box-shadow: #4091ed 0px 0px 5px; + border-color: #4091ed; +} +/* line 13, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-input:focus:not([type=checkbox]):not([type=radio]) { + outline: 0; +} + +/* line 103, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-label { + font-size: 0.9em; + color: #4a4a4a; +} +/* line 38, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-label * { + font-weight: normal; +} + +/* line 112, sourcefiles/scss/components/default/_forms.scss */ +.mw-ui-checkbox-label, .mw-ui-radio-label { + margin-bottom: 0.5em; + cursor: pointer; + vertical-align: bottom; + line-height: normal; + font-weight: normal; +} +/* line 54, sourcefiles/scss/mixins/_forms.scss */ +.mw-ui-checkbox-label > input[type="checkbox"], .mw-ui-checkbox-label > input[type="radio"], .mw-ui-radio-label > input[type="checkbox"], .mw-ui-radio-label > input[type="radio"] { + width: auto; + height: auto; + margin: 0 0.1em 0em 0; + padding: 0; + border-style: solid; + border-width: 1px; + border-color: #c9c9c9; + cursor: pointer; +} + +/* line 5, sourcefiles/scss/components/vector/_forms.scss */ +.mw-ui-vform, +.mw-ui-vform > div input, +.mw-ui-input { + font-size: 1em; + line-height: 1.4em; +} + +/* line 3, sourcefiles/scss/components/vector/_containers.scss */ +.mw-ui-container { + font-size: 1em; + line-height: 1.4em; +} diff --git a/resources/mediawiki.ui/sourcefiles/Makefile b/resources/mediawiki.ui/sourcefiles/Makefile new file mode 100644 index 00000000..dea90139 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/Makefile @@ -0,0 +1,24 @@ +DATE=$(shell date +%I:%M%p) +CHECK=\033[32m✔\033[39m +HR=\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# + +build: + @echo "\n${HR}" + @echo "Building Agora..." + @echo "${HR}\n" + @compass compile + @echo "Compiling Compass project... ${CHECK} Done" + @rm -rf .sass-cache + @echo "Removing .sass-cache... ${CHECK} Done" + @echo "\n${HR}" + @echo "Agora successfully built at ${DATE}." + @echo "${HR}\n" + +all: build + +watch: + @echo "\n${HR}" + @echo "Watching SCSS files for Agora..." + @echo "${HR}\n" + @compass watch + @echo "Started watching modules/scss at ${DATE}..." diff --git a/resources/mediawiki.ui/sourcefiles/config.rb b/resources/mediawiki.ui/sourcefiles/config.rb new file mode 100644 index 00000000..28c65240 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/config.rb @@ -0,0 +1,27 @@ +# Require any additional compass plugins here. + +# Set this to the root of your project when deployed: +# (unused so far): http_path = "/" + +# Output to parent of build directory +css_dir = ".." +sass_dir = "scss" +# (unused so far): images_dir = "modules/img" +# (unused so far): javascripts_dir = "modules/js" + +# You can select your preferred output style here (can be overridden via the command line): +# output_style = :expanded or :nested or :compact or :compressed +output_style = :expanded + +# To enable relative paths to assets via compass helper functions. Uncomment: +relative_assets = true + +# To disable debugging comments that display the original location of your selectors. Uncomment: +line_comments = true + + +# If you prefer the indented syntax, you might want to regenerate this +# project again passing --syntax sass, or you can uncomment this: +# preferred_syntax = :sass +# and then run: +# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/_default.scss b/resources/mediawiki.ui/sourcefiles/scss/components/_default.scss new file mode 100644 index 00000000..e7090ebc --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/_default.scss @@ -0,0 +1,3 @@ +@import "utilities"; +@import "default/buttons"; +@import "default/forms";
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/_utilities.scss b/resources/mediawiki.ui/sourcefiles/scss/components/_utilities.scss new file mode 100644 index 00000000..4f1dba2f --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/_utilities.scss @@ -0,0 +1,17 @@ +// Generic helper classes that could be used in many elements/layouts + +// -------------------------------------------------------------------------- +// Positioning +// -------------------------------------------------------------------------- + +.mw-ui-flush-left { + @include agora-flush-left; +} + +.mw-ui-flush-right { + @include agora-flush-right; +} + +.mw-ui-center-block { + @include agora-center-block; +}
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/_vector.scss b/resources/mediawiki.ui/sourcefiles/scss/components/_vector.scss new file mode 100644 index 00000000..d7cb34ae --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/_vector.scss @@ -0,0 +1,4 @@ +@import "utilities"; +@import "vector/buttons"; +@import "vector/forms"; +@import "vector/containers"; diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/default/_buttons.scss b/resources/mediawiki.ui/sourcefiles/scss/components/default/_buttons.scss new file mode 100644 index 00000000..d67810f7 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/default/_buttons.scss @@ -0,0 +1,69 @@ +$buttonBorderRadius: 3px; + +// Button styling +.mw-ui-button { + // Container layout + @include inline-block; + padding: 0.4em 1em 0.4em 1em; + margin: 0; + + // Container styling + @include buttonColors($agoraGray); + @include border-radius($buttonBorderRadius); + + // Content styling + vertical-align: middle; + + text: { + align: center; + decoration: none; + } + + font: { + weight: bold; + } + + // Interaction styling + cursor: pointer; + + &:disabled, + &.mw-ui-disabled { + cursor: default; + } + + // Button sizes and displays + // ----------------------------------------- + &.mw-ui-big { + font: { + size: $baseFontSize * 1.3; + } + } + &.mw-ui-block { + display: block; + width: 100%; + } +} + +// This overrides an underline declaration on a:hover and a:focus in commonElements.css, which the +// class alone isn't specific enough to do +a.mw-ui-button { + text: { + decoration: none; + } +} + +// Button groups +.mw-ui-button-group > * { + @include border-radius(0); + float: left; + + &:first-child{ + @include border-top-left-radius($buttonBorderRadius); + @include border-bottom-left-radius($buttonBorderRadius); + } + + &:last-child{ + @include border-top-right-radius($buttonBorderRadius); + @include border-bottom-right-radius($buttonBorderRadius); + } +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/default/_forms.scss b/resources/mediawiki.ui/sourcefiles/scss/components/default/_forms.scss new file mode 100644 index 00000000..a9cec39a --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/default/_forms.scss @@ -0,0 +1,114 @@ +// Form elements and layouts + +// -------------------------------------------------------------------------- +// Layouts +// -------------------------------------------------------------------------- + +// The FancyCaptcha image CAPTCHA used on WMF wikis drives the width of the +// 'VForm' design, the form can't be narrower than this. +$captchaContainerWidth: 290px; +$defaultFormWidth: $captchaContainerWidth; + +// Style a compact vertical stacked form ("VForm") and the elements in divs +// within it. +.mw-ui-vform { + @include box-sizing(border-box); + + width: $defaultFormWidth; + + // Immediate divs in a vform are block and spaced-out. + & > div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; + + // MW currently doesn't use the type attribute everywhere on inputs. + input, + .mw-ui-button { + display: block; + @include box-sizing(border-box); + margin: 0; + width: 100%; + } + + // We exclude these because they'll generally use mw-ui-button. + // Otherwise, we'll unintentionally override that. + input:not([type=button]):not([type=submit]):not([type=file]), { + @include agora-field-styling; // mixins/_forms.scss + } + + label { + display: block; + @include box-sizing(border-box); + @include agora-label-styling; + width: auto; + margin: 0 0 0.2em 0; + padding: 0; + } + + // Override input styling just for checkboxes and radio inputs. + input[type="checkbox"], + input[type="radio"] { + display: inline; + @include box-sizing(content-box); + width: auto; + } + + } + + // HTMLForm uses error, SpecialUserlogin (login and create account) uses + // errorbox. + // TODO move errorbox from mediawiki.special.vforms.css into here. + .error { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 0.9em; + margin: 0 0 1em 0; + padding: 0.5em; + color: #cc0000; + border: 1px solid #fac5c5; + background-color: #fae3e3; + text-shadow: 0 1px #fae3e3; + word-wrap: break-word; + } +} + +// -------------------------------------------------------------------------- +// Elements +// -------------------------------------------------------------------------- + +// Apply this to individual elements to style them. +// You generally don't need to use this class on divs within an Agora +// form container such as mw-ui-vform +// XXX DRY: This repeats earlier styling, use an @include agora-div-styling ? +.mw-ui-vform-div { + display: block; + margin: 0 0 15px 0; + padding: 0; + width: 100%; +} + +// Apply mw-ui-input to individual input fields to style them. +// You generally don't need to use this class if <input> is within an Agora +// form container such as mw-ui-vform +.mw-ui-input { + @include agora-field-styling; // mixins/_forms.scss +} + +// Apply mw-ui-label to individual elements to style them. +// You generally don't need to use this class if <label> is within an Agora +// form container such as mw-ui-vform +.mw-ui-label { + @include agora-label-styling; // mixins/_forms.scss +} + +// Nesting an input checkbox or radio button inside a label with this class +// improves alignment, e.g. +// <label class="mw-ui-checkbox-label"> +// <input type="checkbox">The label text +// </label> +.mw-ui-checkbox-label, .mw-ui-radio-label { + @include agora-inline-label-styling; +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/vector/_buttons.scss b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_buttons.scss new file mode 100644 index 00000000..8d5f0b6a --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_buttons.scss @@ -0,0 +1,19 @@ +@import "../default/buttons"; // Layer Vector on top of the default settings. + +.mw-ui-button { + // Button colors determined by function. + // ----------------------------------------- + &.mw-ui-primary { + @include buttonColors($agoraBlue); + } + + &.mw-ui-constructive { + @include buttonColors($agoraGreen); + } + + &.mw-ui-destructive { + @include buttonColors($agoraRed); + } + + @include vector-type; +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/vector/_containers.scss b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_containers.scss new file mode 100644 index 00000000..ed01603d --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_containers.scss @@ -0,0 +1,5 @@ +// No default settings for containers yet. + +.mw-ui-container { + @include vector-type; +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/components/vector/_forms.scss b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_forms.scss new file mode 100644 index 00000000..73ea24e2 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/components/vector/_forms.scss @@ -0,0 +1,7 @@ +@import "../default/forms"; // Layer Vector on top of the default settings. + +.mw-ui-vform, +.mw-ui-vform > div input, +.mw-ui-input { + @include vector-type; +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.default.scss b/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.default.scss new file mode 100644 index 00000000..e6db5237 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.default.scss @@ -0,0 +1,16 @@ +/** + * Provide Agora appearance for mw-ui-* classes when using a skin other than + * Vector. + * Compass builds these Agora styles from source Sass files in + * extensions/Agora/modules/scss + */ + +@charset "UTF-8"; + +@import "compass"; + +@import "settings/all"; + +@import "mixins/all"; + +@import "components/default"; diff --git a/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.vector.scss b/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.vector.scss new file mode 100644 index 00000000..ac113eec --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mediawiki.ui.vector.scss @@ -0,0 +1,15 @@ +/** + * Provide Agora appearance for mw-ui-* classes when using the Vector skin. + * Compass builds these Agora styles from source Sass files in + * extensions/Agora/modules/scss + */ + +@charset "UTF-8"; + +@import "compass"; + +@import "settings/all"; + +@import "mixins/all"; + +@import "components/vector"; diff --git a/resources/mediawiki.ui/sourcefiles/scss/mixins/_all.scss b/resources/mediawiki.ui/sourcefiles/scss/mixins/_all.scss new file mode 100644 index 00000000..adc48cd8 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mixins/_all.scss @@ -0,0 +1,4 @@ +@import "utilities"; +@import "type"; +@import "effects"; +@import "forms";
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/mixins/_effects.scss b/resources/mediawiki.ui/sourcefiles/scss/mixins/_effects.scss new file mode 100644 index 00000000..2efff820 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mixins/_effects.scss @@ -0,0 +1,62 @@ +/* _effects.scss */ + +/* Mixins for visual effects in CSS3 */ + +// ---------------------------------------------------------------------------- +// Gradients +// ---------------------------------------------------------------------------- +@mixin vertical-gradient ($startColor: lighten($agoraGray, 95%), $endColor: $agoraGray) { + // Fallback + background-color: $endColor; + *background-color: $endColor; // IE7 + + // IE6-8 + @include filter-gradient($startColor, $endColor, vertical); + + // IE9+, Opera, Gecko, WebKit + @include background-image(linear-gradient(top, $startColor, $endColor)); +} + +// ---------------------------------------------------------------------------- +// Button styling +// ---------------------------------------------------------------------------- +@mixin buttonColors ($baseColor: $agoraGray) { + // Background color + @include vertical-gradient(lighten($baseColor, 7.5%), $baseColor); + + @if $baseColor == $agoraGray { + color: black; + @include text-shadow(0 1px 1px rgba($baseColor, 0.3)); + } @else { + color: white; + @include text-shadow(0 1px 1px rgba($baseColor, 0.75)); + } + + border: 1px solid darken($baseColor, 2%); + + &:hover, + &.mw-ui-hover { + @include vertical-gradient(lighten($baseColor, 12.5%), lighten($baseColor, 7.5%)); + text-decoration: none; + } + + &:active, + &.mw-ui-active { + background: { + image: none; + color: darken($baseColor, 3%); + } + + text-shadow: none; + } + + &:disabled, + &.mw-ui-disabled { + background: { + image: none; + color: $baseColor; + } + opacity: 0.5; + text-shadow: none; + } +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/mixins/_forms.scss b/resources/mediawiki.ui/sourcefiles/scss/mixins/_forms.scss new file mode 100644 index 00000000..0f3f6ad3 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mixins/_forms.scss @@ -0,0 +1,66 @@ +// Font is not included. +// For Vector, that should be layered on top with vector-type +@mixin agora-field-styling() { + + border: { + style: solid; + width: 1px; + color: $agoraGray; + }; + + &:focus { + // Styling focus of native checkboxes etc on Mac is almost impossible. + &:not([type=checkbox]):not([type=radio]) { + @include reset-focus; // Removes OS field focus + }; + + // @include box-shadow generates unneeded prefixes + // https://github.com/chriseppstein/compass/issues/1054 , so specify + // directly. + box-shadow: $agoraBlueShadow 0px 0px 5px; + + border: { + color: $agoraBlueShadow; + }; + } + + color: $agoraTextColor; + padding: 0.35em 0 0.35em 0.5em; +} + +@mixin agora-label-styling() { + font: { + //weight: bold; + size: 0.9em; + }; + color: darken($agoraGray, 50%); + + & * { + font-weight: normal; + } +} + +@mixin agora-inline-label-styling() { + margin-bottom: 0.5em; + cursor: pointer; + vertical-align: bottom; + line-height: normal; + + font: { + weight: normal; + }; + + & > input[type="checkbox"], + & > input[type="radio"] { + width: auto; + height: auto; + margin: 0 0.1em 0em 0; + padding: 0; + border: { + style: solid; + width: 1px; + color: $agoraGray; + } + cursor: pointer; + } +} diff --git a/resources/mediawiki.ui/sourcefiles/scss/mixins/_type.scss b/resources/mediawiki.ui/sourcefiles/scss/mixins/_type.scss new file mode 100644 index 00000000..8a93a08b --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mixins/_type.scss @@ -0,0 +1,6 @@ +@mixin vector-type { + font: { + size: $baseFontSize; + } + line-height: $baseLineHeight; +}
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/mixins/_utilities.scss b/resources/mediawiki.ui/sourcefiles/scss/mixins/_utilities.scss new file mode 100644 index 00000000..71a93b60 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/mixins/_utilities.scss @@ -0,0 +1,19 @@ +@mixin agora-flush-left() { + float: left; + margin-left: 0; + padding-left: 0; +} + +@mixin agora-flush-right() { + float: right; + margin-right: 0; + padding-right: 0; +} + +@mixin agora-center-block() { + display: block; + margin: { + left: auto; + right: auto; + }; +}
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/settings/_all.scss b/resources/mediawiki.ui/sourcefiles/scss/settings/_all.scss new file mode 100644 index 00000000..21ac292f --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/settings/_all.scss @@ -0,0 +1,2 @@ +@import "colors"; +@import "typography";
\ No newline at end of file diff --git a/resources/mediawiki.ui/sourcefiles/scss/settings/_colors.scss b/resources/mediawiki.ui/sourcefiles/scss/settings/_colors.scss new file mode 100644 index 00000000..0c18bdb4 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/settings/_colors.scss @@ -0,0 +1,17 @@ +// Grays +// ----------------------------------------- +$agoraGray: #c9c9c9; +$agoraTextColor: #252525; + +// Blues +// ----------------------------------------- +$agoraBlue: #3366bb; +$agoraBlueShadow: #4091ed; + +// Greens +// ----------------------------------------- +$agoraGreen: #27aa65; + +// Reds +// ----------------------------------------- +$agoraRed: #cc0000; diff --git a/resources/mediawiki.ui/sourcefiles/scss/settings/_typography.scss b/resources/mediawiki.ui/sourcefiles/scss/settings/_typography.scss new file mode 100644 index 00000000..013d12b3 --- /dev/null +++ b/resources/mediawiki.ui/sourcefiles/scss/settings/_typography.scss @@ -0,0 +1,5 @@ +$baseFontSize: 1em; +$baseLineHeight: 1.4 * $baseFontSize; +$baseFontColor: $agoraTextColor; + +$smallFontSize: 0.75em;
\ No newline at end of file diff --git a/resources/mediawiki/images/arrow-collapsed-ltr.png b/resources/mediawiki/images/arrow-collapsed-ltr.png Binary files differnew file mode 100644 index 00000000..b17e578b --- /dev/null +++ b/resources/mediawiki/images/arrow-collapsed-ltr.png diff --git a/resources/mediawiki/images/arrow-collapsed-rtl.png b/resources/mediawiki/images/arrow-collapsed-rtl.png Binary files differnew file mode 100644 index 00000000..a834548e --- /dev/null +++ b/resources/mediawiki/images/arrow-collapsed-rtl.png diff --git a/resources/mediawiki/images/arrow-expanded.png b/resources/mediawiki/images/arrow-expanded.png Binary files differnew file mode 100644 index 00000000..2bec798e --- /dev/null +++ b/resources/mediawiki/images/arrow-expanded.png diff --git a/resources/mediawiki/mediawiki.Title.js b/resources/mediawiki/mediawiki.Title.js index b86a14ba..5038c515 100644 --- a/resources/mediawiki/mediawiki.Title.js +++ b/resources/mediawiki/mediawiki.Title.js @@ -1,192 +1,368 @@ /*! * @author Neil Kandalgaonkar, 2010 - * @author Timo Tijhof, 2011 + * @author Timo Tijhof, 2011-2013 * @since 1.18 - * - * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink */ ( function ( mw, $ ) { - /* Local space */ - /** * @class mw.Title * + * Parse titles into an object struture. Note that when using the constructor + * directly, passing invalid titles will result in an exception. Use #newFromText to use the + * logic directly and get null for invalid titles which is easier to work with. + * * @constructor * @param {string} title Title of the page. If no second argument given, - * this will be searched for a namespace. - * @param {number} [namespace] Namespace id. If given, title will be taken as-is. + * this will be searched for a namespace + * @param {number} [namespace=NS_MAIN] If given, will used as default namespace for the given title + * @throws {Error} When the title is invalid */ function Title( title, namespace ) { - this.ns = 0; // integer namespace id - this.name = null; // name in canonical 'database' form - this.ext = null; // extension - - if ( arguments.length === 2 ) { - setNameAndExtension( this, title ); - this.ns = fixNsId( namespace ); - } else if ( arguments.length === 1 ) { - setAll( this, title ); + var parsed = parse( title, namespace ); + if ( !parsed ) { + throw new Error( 'Unable to parse title' ); } + + this.namespace = parsed.namespace; + this.title = parsed.title; + this.ext = parsed.ext; + this.fragment = parsed.fragment; + return this; } -var - /* Public methods (defined later) */ - fn, + /* Private members */ + + var /** - * 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.. - * @ignore - * @param {string} s - * @return {string} + * @private + * @static + * @property NS_MAIN */ - clean = function ( s ) { - if ( s !== undefined ) { - return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' ); - } - }, + NS_MAIN = 0, /** - * Convert db-key to readable text. - * @ignore - * @param {string} s - * @return {string} + * @private + * @static + * @property NS_TALK */ - text = function ( s ) { - if ( s !== null && s !== undefined ) { - return s.replace( /_/g, ' ' ); - } else { - return ''; - } - }, + NS_TALK = 1, /** - * Sanitize name. - * @ignore + * @private + * @static + * @property NS_SPECIAL */ - fixName = function ( s ) { - return clean( $.trim( s ) ); - }, + NS_SPECIAL = -1, /** - * Sanitize extension. - * @ignore + * Get the namespace id from a namespace name (either from the localized, canonical or alias + * name). + * + * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or + * even 'Bild'. + * + * @private + * @static + * @method getNsIdByName + * @param {string} ns Namespace name (case insensitive, leading/trailing space ignored) + * @return {number|boolean} Namespace id or boolean false */ - fixExt = function ( s ) { - return clean( s ); + getNsIdByName = function ( ns ) { + var id; + + // Don't cast non-strings to strings, because null or undefined should not result in + // returning the id of a potential namespace called "Null:" (e.g. on null.example.org/wiki) + // Also, toLowerCase throws exception on null/undefined, because it is a String method. + if ( typeof ns !== 'string' ) { + return false; + } + ns = ns.toLowerCase(); + id = mw.config.get( 'wgNamespaceIds' )[ns]; + if ( id === undefined ) { + return false; + } + return id; }, + rUnderscoreTrim = /^_+|_+$/g, + + rSplit = /^(.+?)_*:_*(.*)$/, + + // See Title.php#getTitleInvalidRegex + rInvalid = new RegExp( + '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' + + // URL percent encoding sequences interfere with the ability + // to round-trip titles -- you can't link to them consistently. + '|%[0-9A-Fa-f]{2}' + + // XML/HTML character references produce similar issues. + '|&[A-Za-z0-9\u0080-\uFFFF]+;' + + '|&#[0-9]+;' + + '|&#x[0-9A-Fa-f]+;' + ), + /** - * Sanitize namespace id. - * @ignore - * @param id {Number} Namespace id. - * @return {Number|Boolean} The id as-is or boolean false if invalid. + * Internal helper for #constructor and #newFromtext. + * + * Based on Title.php#secureAndSplit + * + * @private + * @static + * @method parse + * @param {string} title + * @param {number} [defaultNamespace=NS_MAIN] + * @return {Object|boolean} */ - fixNsId = function ( id ) { - // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] ) - var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()]; + parse = function ( title, defaultNamespace ) { + var namespace, m, id, i, fragment, ext; - // Check only undefined (may be false-y, such as '' (main namespace) ). - if ( ns === undefined ) { + namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace; + + title = title + // Normalise whitespace to underscores and remove duplicates + .replace( /[ _\s]+/g, '_' ) + // Trim underscores + .replace( rUnderscoreTrim, '' ); + + if ( title === '' ) { return false; + } + + // Process initial colon + if ( title.charAt( 0 ) === ':' ) { + // Initial colon means main namespace instead of specified default + namespace = NS_MAIN; + title = title + // Strip colon + .substr( 1 ) + // Trim underscores + .replace( rUnderscoreTrim, '' ); + } + + // Process namespace prefix (if any) + m = title.match( rSplit ); + if ( m ) { + id = getNsIdByName( m[1] ); + if ( id !== false ) { + // Ordinary namespace + namespace = id; + title = m[2]; + + // For Talk:X pages, make sure X has no "namespace" prefix + if ( namespace === NS_TALK && ( m = title.match( rSplit ) ) ) { + // Disallow titles like Talk:File:x (subject should roundtrip: talk:file:x -> file:x -> file_talk:x) + if ( getNsIdByName( m[1] ) !== false ) { + return false; + } + } + } + } + + // Process fragment + i = title.indexOf( '#' ); + if ( i === -1 ) { + fragment = null; } else { - return Number( id ); + fragment = title + // Get segment starting after the hash + .substr( i + 1 ) + // Convert to text + // NB: Must not be trimmed ("Example#_foo" is not the same as "Example#foo") + .replace( /_/g, ' ' ); + + title = title + // Strip hash + .substr( 0, i ) + // Trim underscores, again (strips "_" from "bar" in "Foo_bar_#quux") + .replace( rUnderscoreTrim, '' ); } - }, - /** - * 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'. - * @ignore - * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored). - * @return {Number|Boolean} Namespace id or boolean false if unrecognized. - */ - getNsIdByName = function ( ns ) { - // Don't cast non-strings to strings, because null or undefined - // should not result in returning the id of a potential namespace - // called "Null:" (e.g. on nullwiki.example.org) - // Also, toLowerCase throws exception on null/undefined, because - // it is a String.prototype method. - if ( typeof ns !== 'string' ) { + + // Reject illegal characters + if ( title.match( rInvalid ) ) { return false; } - ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize - var id = mw.config.get( 'wgNamespaceIds' )[ns]; - if ( id === undefined ) { - mw.log( 'mw.Title: Unrecognized namespace: ' + ns ); + + // Disallow titles that browsers or servers might resolve as directory navigation + if ( + title.indexOf( '.' ) !== -1 && ( + title === '.' || title === '..' || + title.indexOf( './' ) === 0 || + title.indexOf( '../' ) === 0 || + title.indexOf( '/./' ) !== -1 || + title.indexOf( '/../' ) !== -1 || + title.substr( -2 ) === '/.' || + title.substr( -3 ) === '/..' + ) + ) { + return false; + } + + // Disallow magic tilde sequence + if ( title.indexOf( '~~~' ) !== -1 ) { + return false; + } + + // Disallow titles exceeding the 255 byte size limit (size of underlying database field) + // Except for special pages, e.g. [[Special:Block/Long name]] + // Note: The PHP implementation also asserts that even in NS_SPECIAL, the title should + // be less than 512 bytes. + if ( namespace !== NS_SPECIAL && $.byteLength( title ) > 255 ) { + return false; + } + + // Can't make a link to a namespace alone. + if ( title === '' && namespace !== NS_MAIN ) { + return false; + } + + // Any remaining initial :s are illegal. + if ( title.charAt( 0 ) === ':' ) { return false; } - return fixNsId( id ); + + // For backwards-compatibility with old mw.Title, we separate the extension from the + // rest of the title. + i = title.lastIndexOf( '.' ); + if ( i === -1 || title.length <= i + 1 ) { + // Extensions are the non-empty segment after the last dot + ext = null; + } else { + ext = title.substr( i + 1 ); + title = title.substr( 0, i ); + } + + return { + namespace: namespace, + title: title, + ext: ext, + fragment: fragment + }; }, /** - * Helper to extract namespace, name and extension from a string. + * Convert db-key to readable text. * - * @ignore - * @param {mw.Title} title - * @param {string} raw - * @return {mw.Title} + * @private + * @static + * @method text + * @param {string} s + * @return {string} */ - 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+))?$/ ), - nsMatch = getNsIdByName( matches[1] ); - - // Namespace must be valid, and title must be a non-empty string. - 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] ); - } + text = function ( s ) { + if ( s !== null && s !== undefined ) { + return s.replace( /_/g, ' ' ); } else { - // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace. - title.ns = 0; - setNameAndExtension( title, s ); + return ''; } - return title; }, + // Polyfill for ES5 Object.create + createObject = Object.create || ( function () { + return function ( o ) { + function Title() {} + if ( o !== Object( o ) ) { + throw new Error( 'Cannot inherit from a non-object' ); + } + Title.prototype = o; + return new Title(); + }; + }() ); + + + /* Static members */ + /** - * Helper to extract name and extension from a string. + * Constructor for Title objects with a null return instead of an exception for invalid titles. * - * @ignore - * @param {mw.Title} title - * @param {string} raw - * @return {mw.Title} + * @static + * @method + * @param {string} title + * @param {number} [namespace=NS_MAIN] Default namespace + * @return {mw.Title|null} A valid Title object or null if the title is invalid */ - setNameAndExtension = function ( title, raw ) { - // In normal browsers the match-array contains null/undefined if there's no match, - // IE returns an empty string. - var matches = raw.match( /^(?:)?(.*?)(?:\.(\w+))?$/ ); - - // Title must be a non-empty string. - if ( typeof matches[1] === 'string' && matches[1] !== '' ) { - title.name = fixName( matches[1] ); - if ( typeof matches[2] === 'string' && matches[2] !== '' ) { - title.ext = fixExt( matches[2] ); - } - } else { - throw new Error( 'mw.Title: Could not parse title "' + raw + '"' ); + Title.newFromText = function ( title, namespace ) { + var t, parsed = parse( title, namespace ); + if ( !parsed ) { + return null; } - return title; + + t = createObject( Title.prototype ); + t.namespace = parsed.namespace; + t.title = parsed.title; + t.ext = parsed.ext; + t.fragment = parsed.fragment; + + return t; }; + /** + * Get the file title from an image element + * + * var title = mw.Title.newFromImg( $( 'img:first' ) ); + * + * @static + * @param {HTMLElement|jQuery} img The image to use as a base + * @return {mw.Title|null} The file title or null if unsuccessful + */ + Title.newFromImg = function ( img ) { + var matches, i, regex, src, decodedSrc, + + // thumb.php-generated thumbnails + thumbPhpRegex = /thumb\.php/, + + regexes = [ + // Thumbnails + /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)\/[0-9]+px-\1[^\s\/]*$/, + + // Thumbnails in non-hashed upload directories + /\/([^\s\/]+)\/[0-9]+px-\1[^\s\/]*$/, + + // Full size images + /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)$/, + + // Full-size images in non-hashed upload directories + /\/([^\s\/]+)$/ + ], - /* Static space */ + recount = regexes.length; + + src = img.jquery ? img[0].src : img.src; + + matches = src.match( thumbPhpRegex ); + + if ( matches ) { + return mw.Title.newFromText( 'File:' + mw.util.getParamValue( 'f', src ) ); + } + + decodedSrc = decodeURIComponent( src ); + + for ( i = 0; i < recount; i++ ) { + regex = regexes[i]; + matches = decodedSrc.match( regex ); + + if ( matches && matches[1] ) { + return mw.Title.newFromText( 'File:' + matches[1] ); + } + } + + return null; + }; /** * Whether this title exists on the wiki. + * * @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. + * @param {string|mw.Title} title prefixed db-key name (string) or instance of Title + * @return {boolean|null} Boolean if the information is available, otherwise null */ Title.exists = function ( title ) { - var type = $.type( title ), obj = Title.exist.pages, match; + var match, + type = $.type( title ), + obj = Title.exist.pages; + if ( type === 'string' ) { match = obj[title]; } else if ( type === 'object' && title instanceof Title ) { @@ -194,23 +370,23 @@ var } else { throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' ); } + if ( typeof match === 'boolean' ) { return match; } + return null; }; - /** - * @static - * @property - */ Title.exist = { /** + * Boolean true value indicates page does exist. + * * @static * @property {Object} exist.pages Keyed by PrefixedDb title. - * Boolean true value indicates page does exist. */ pages: {}, + /** * Example to declare existing titles: * Title.exist.set(['User:John_Doe', ...]); @@ -219,8 +395,8 @@ var * * @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. + * @param {string|Array} titles Title(s) in strict prefixedDb title form + * @param {boolean} [state=true] State of the given titles * @return {boolean} */ set: function ( titles, state ) { @@ -234,42 +410,60 @@ var } }; - /* Public methods */ + /* Public members */ - fn = { + Title.prototype = { constructor: Title, /** - * Get the namespace number. + * Get the namespace number + * + * Example: 6 for "File:Example_image.svg". + * * @return {number} */ - getNamespaceId: function (){ - return this.ns; + getNamespaceId: function () { + return this.namespace; }, /** - * Get the namespace prefix (in the content-language). - * In NS_MAIN this is '', otherwise namespace name plus ':' + * Get the namespace prefix (in the content language) + * + * Example: "File:" for "File:Example_image.svg". + * In #NS_MAIN this is '', otherwise namespace name plus ':' + * * @return {string} */ - getNamespacePrefix: function (){ - return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':'); + getNamespacePrefix: function () { + return this.namespace === NS_MAIN ? + '' : + ( mw.config.get( 'wgFormattedNamespaces' )[ this.namespace ].replace( / /g, '_' ) + ':' ); }, /** - * The name, like "Foo_bar" + * Get the page name without extension or namespace prefix + * + * Example: "Example_image" for "File:Example_image.svg". + * + * For the page title (full page name without namespace prefix), see #getMain. + * * @return {string} */ getName: function () { - if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) { - return this.name; + if ( $.inArray( this.namespace, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) { + return this.title; } else { - return $.ucFirst( this.name ); + return $.ucFirst( this.title ); } }, /** - * The name, like "Foo bar" + * Get the page name (transformed by #text) + * + * Example: "Example image" for "File:Example_image.svg". + * + * For the page title (full page name without namespace prefix), see #getMainText. + * * @return {string} */ getNameText: function () { @@ -277,24 +471,30 @@ 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} + * Get the extension of the page name (if any) + * + * @return {string|null} Name extension or null if there is none */ - getPrefixedDb: function () { - return this.getNamespacePrefix() + this.getMain(); + getExtension: function () { + return this.ext; }, /** - * Get full name in text form, like "File:Foo bar.jpg". + * Shortcut for appendable string to form the main page name. + * + * Returns a string like ".json", or "" if no extension. + * * @return {string} */ - getPrefixedText: function () { - return text( this.getPrefixedDb() ); + getDotExtension: function () { + return this.ext === null ? '' : '.' + this.ext; }, /** - * The main title (without namespace), like "Foo_bar.jpg" + * Get the main page name (transformed by #text) + * + * Example: "Example_image.svg" for "File:Example_image.svg". + * * @return {string} */ getMain: function () { @@ -302,7 +502,10 @@ var }, /** - * The "text" form, like "Foo bar.jpg" + * Get the main page name (transformed by #text) + * + * Example: "Example image.svg" for "File:Example_image.svg". + * * @return {string} */ getMainText: function () { @@ -310,46 +513,73 @@ var }, /** - * Get the extension (returns null if there was none) - * @return {string|null} + * Get the full page name + * + * Eaxample: "File:Example_image.svg". + * Most useful for API calls, anything that must identify the "title". + * + * @return {string} */ - getExtension: function () { - return this.ext; + getPrefixedDb: function () { + return this.getNamespacePrefix() + this.getMain(); }, /** - * Convenience method: return string like ".jpg", or "" if no extension + * Get the full page name (transformed by #text) + * + * Example: "File:Example image.svg" for "File:Example_image.svg". + * * @return {string} */ - getDotExtension: function () { - return this.ext === null ? '' : '.' + this.ext; + getPrefixedText: function () { + return text( this.getPrefixedDb() ); + }, + + /** + * Get the fragment (if any). + * + * Note that this method (by design) does not include the hash character and + * the value is not url encoded. + * + * @return {string|null} + */ + getFragment: function () { + return this.fragment; }, /** - * Return the URL to this title - * @see mw.util#wikiGetlink + * Get the URL to this title + * + * @see mw.util#getUrl * @return {string} */ getUrl: function () { - return mw.util.wikiGetlink( this.toString() ); + return mw.util.getUrl( this.toString() ); }, /** * Whether this title exists on the wiki. + * * @see #static-method-exists - * @return {boolean|null} If the information is available. Otherwise null. + * @return {boolean|null} Boolean if the information is available, otherwise null */ exists: function () { return Title.exists( this ); } }; - // Alias - fn.toString = fn.getPrefixedDb; - fn.toText = fn.getPrefixedText; + /** + * @alias #getPrefixedDb + * @method + */ + Title.prototype.toString = Title.prototype.getPrefixedDb; + - // Assign - Title.prototype = fn; + /** + * @alias #getPrefixedText + * @method + */ + Title.prototype.toText = Title.prototype.getPrefixedText; // Expose mw.Title = Title; diff --git a/resources/mediawiki/mediawiki.Uri.js b/resources/mediawiki/mediawiki.Uri.js index 643e5c3e..a2d4d6cb 100644 --- a/resources/mediawiki/mediawiki.Uri.js +++ b/resources/mediawiki/mediawiki.Uri.js @@ -201,7 +201,7 @@ uri = this, matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str ); $.each( properties, function ( i, property ) { - uri[ property ] = matches[ i+1 ]; + uri[ property ] = matches[ i + 1 ]; } ); // uri.query starts out as the query string; we will parse it into key-val pairs then make @@ -210,7 +210,7 @@ q = {}; // using replace to iterate over a string if ( uri.query ) { - uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { + uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ( $0, $1, $2, $3 ) { var k, v; if ( $1 ) { k = Uri.decode( $1 ); diff --git a/resources/mediawiki/mediawiki.debug.js b/resources/mediawiki/mediawiki.debug.js index 88af3c65..986917a1 100644 --- a/resources/mediawiki/mediawiki.debug.js +++ b/resources/mediawiki/mediawiki.debug.js @@ -229,7 +229,7 @@ $( '<colgroup>' ).css( 'width', 350 ).appendTo( $table ); - entryTypeText = function( entryType ) { + entryTypeText = function ( entryType ) { switch ( entryType ) { case 'log': return 'Log'; diff --git a/resources/mediawiki/mediawiki.htmlform.js b/resources/mediawiki/mediawiki.htmlform.js index 83bf2e3a..de068598 100644 --- a/resources/mediawiki/mediawiki.htmlform.js +++ b/resources/mediawiki/mediawiki.htmlform.js @@ -1,7 +1,7 @@ /** * Utility functions for jazzing up HTMLForm elements. */ -( function ( $ ) { +( function ( mw, $ ) { /** * jQuery plugin to fade or snap to visible state. @@ -59,4 +59,70 @@ } ); -}( jQuery ) ); + function addMulti( $oldContainer, $container ) { + var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ), + oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ), + $select = $( '<select>' ), + dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' ); + oldClass = $.trim( oldClass ); + $select.attr( { + name: name, + multiple: 'multiple', + 'data-placeholder': dataPlaceholder.plain(), + 'class': 'htmlform-chzn-select mw-input ' + oldClass + } ); + $oldContainer.find( 'input' ).each( function () { + var $oldInput = $(this), + checked = $oldInput.prop( 'checked' ), + $option = $( '<option>' ); + $option.prop( 'value', $oldInput.prop( 'value' ) ); + if ( checked ) { + $option.prop( 'selected', true ); + } + $option.text( $oldInput.prop( 'value' ) ); + $select.append( $option ); + } ); + $container.append( $select ); + } + + function convertCheckboxesToMulti( $oldContainer, type ) { + var $fieldLabel = $( '<td>' ), + $td = $( '<td>' ), + $fieldLabelText = $( '<label>' ), + $container; + if ( type === 'tr' ) { + addMulti( $oldContainer, $td ); + $container = $( '<tr>' ); + $container.append( $td ); + } else if ( type === 'div' ) { + $fieldLabel = $( '<div>' ); + $container = $( '<div>' ); + addMulti( $oldContainer, $container ); + } + $fieldLabel.attr( 'class', 'mw-label' ); + $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() ); + $fieldLabel.append( $fieldLabelText ); + $container.prepend( $fieldLabel ); + $oldContainer.replaceWith( $container ); + return $container; + } + + if ( $( '.mw-chosen' ).length ) { + mw.loader.using( 'jquery.chosen', function () { + $( '.mw-chosen' ).each( function () { + var type = this.nodeName.toLowerCase(), + $converted = convertCheckboxesToMulti( $( this ), type ); + $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } ); + } ); + } ); + } + + $( function () { + var $matrixTooltips = $( '.mw-htmlform-matrix .mw-htmlform-tooltip' ); + if ( $matrixTooltips.length ) { + mw.loader.using( 'jquery.tipsy', function () { + $matrixTooltips.tipsy( { gravity: 's' } ); + } ); + } + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.icon.css b/resources/mediawiki/mediawiki.icon.css new file mode 100644 index 00000000..f61b7257 --- /dev/null +++ b/resources/mediawiki/mediawiki.icon.css @@ -0,0 +1,15 @@ +/* General-purpose icons via CSS. Classes here should be named "mw-icon-*". */ + +/* For the collapsed and expanded arrows, we also provide selectors to make it + * easy to use them with jquery.makeCollapsible. */ +.mw-icon-arrow-collapsed, +.mw-collapsible-arrow.mw-collapsible-toggle-collapsed { + /* @embed */ + background: url(images/arrow-collapsed-ltr.png) no-repeat left bottom; +} + +.mw-icon-arrow-expanded, +.mw-collapsible-arrow.mw-collapsible-toggle-expanded { + /* @embed */ + background: url(images/arrow-expanded.png) no-repeat left bottom; +} diff --git a/resources/mediawiki/mediawiki.inspect.js b/resources/mediawiki/mediawiki.inspect.js new file mode 100644 index 00000000..2f2ca335 --- /dev/null +++ b/resources/mediawiki/mediawiki.inspect.js @@ -0,0 +1,204 @@ +/*! + * Tools for inspecting page composition and performance. + * + * @author Ori Livneh + * @since 1.22 + */ +/*jshint devel:true */ +( function ( mw, $ ) { + + function sortByProperty( array, prop, descending ) { + var order = descending ? -1 : 1; + return array.sort( function ( a, b ) { + return a[prop] > b[prop] ? order : a[prop] < b[prop] ? -order : 0; + } ); + } + + /** + * @class mw.inspect + * @singleton + */ + var inspect = { + + /** + * Calculate the byte size of a ResourceLoader module. + * + * @param {string} moduleName The name of the module + * @return {number|null} Module size in bytes or null + */ + getModuleSize: function ( moduleName ) { + var module = mw.loader.moduleRegistry[ moduleName ], + payload = 0; + + if ( mw.loader.getState( moduleName ) !== 'ready' ) { + return null; + } + + if ( !module.style && !module.script ) { + return null; + } + + // Tally CSS + if ( module.style && $.isArray( module.style.css ) ) { + $.each( module.style.css, function ( i, stylesheet ) { + payload += $.byteLength( stylesheet ); + } ); + } + + // Tally JavaScript + if ( $.isFunction( module.script ) ) { + payload += $.byteLength( module.script.toString() ); + } + + return payload; + }, + + /** + * Given CSS source, count both the total number of selectors it + * contains and the number which match some element in the current + * document. + * + * @param {string} css CSS source + * @return Selector counts + * @return {number} return.selectors Total number of selectors + * @return {number} return.matched Number of matched selectors + */ + auditSelectors: function ( css ) { + var selectors = { total: 0, matched: 0 }, + style = document.createElement( 'style' ), + sheet, rules; + + style.textContent = css; + document.body.appendChild( style ); + // Standards-compliant browsers use .sheet.cssRules, IE8 uses .styleSheet.rules… + sheet = style.sheet || style.styleSheet; + rules = sheet.cssRules || sheet.rules; + $.each( rules, function ( index, rule ) { + selectors.total++; + if ( document.querySelector( rule.selectorText ) !== null ) { + selectors.matched++; + } + } ); + document.body.removeChild( style ); + return selectors; + }, + + /** + * Get a list of all loaded ResourceLoader modules. + * + * @return {Array} List of module names + */ + getLoadedModules: function () { + return $.grep( mw.loader.getModuleNames(), function ( module ) { + return mw.loader.getState( module ) === 'ready'; + } ); + }, + + /** + * Print tabular data to the console, using console.table, console.log, + * or mw.log (in declining order of preference). + * + * @param {Array} data Tabular data represented as an array of objects + * with common properties. + */ + dumpTable: function ( data ) { + try { + // Bartosz made me put this here. + if ( window.opera ) { throw window.opera; } + // Use Function.prototype#call to force an exception on Firefox, + // which doesn't define console#table but doesn't complain if you + // try to invoke it. + console.table.call( console, data ); + return; + } catch (e) {} + try { + console.log( $.toJSON( data, null, 2 ) ); + return; + } catch (e) {} + mw.log( data ); + }, + + /** + * Generate and print one more reports. When invoked with no arguments, + * print all reports. + * + * @param {string...} [reports] Report names to run, or unset to print + * all available reports. + */ + runReports: function () { + var reports = arguments.length > 0 ? + Array.prototype.slice.call( arguments ) : + $.map( inspect.reports, function ( v, k ) { return k; } ); + + $.each( reports, function ( index, name ) { + inspect.dumpTable( inspect.reports[name]() ); + } ); + }, + + /** + * @class mw.inspect.reports + * @singleton + */ + reports: { + /** + * Generate a breakdown of all loaded modules and their size in + * kilobytes. Modules are ordered from largest to smallest. + */ + size: function () { + // Map each module to a descriptor object. + var modules = $.map( inspect.getLoadedModules(), function ( module ) { + return { + name: module, + size: inspect.getModuleSize( module ) + }; + } ); + + // Sort module descriptors by size, largest first. + sortByProperty( modules, 'size', true ); + + // Convert size to human-readable string. + $.each( modules, function ( i, module ) { + module.size = module.size > 1024 ? + ( module.size / 1024 ).toFixed( 2 ) + ' KB' : + ( module.size !== null ? module.size + ' B' : null ); + } ); + + return modules; + }, + + /** + * For each module with styles, count the number of selectors, and + * count how many match against some element currently in the DOM. + */ + css: function () { + var modules = []; + + $.each( inspect.getLoadedModules(), function ( index, name ) { + var css, stats, module = mw.loader.moduleRegistry[name]; + + try { + css = module.style.css.join(); + } catch (e) { return; } // skip + + stats = inspect.auditSelectors( css ); + modules.push( { + module: name, + allSelectors: stats.total, + matchedSelectors: stats.matched, + percentMatched: stats.total !== 0 ? + ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null + } ); + } ); + sortByProperty( modules, 'allSelectors', true ); + return modules; + }, + } + }; + + if ( mw.config.get( 'debug' ) ) { + mw.log( 'mw.inspect: reports are not available in debug mode.' ); + } + + mw.inspect = inspect; + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.jqueryMsg.js b/resources/mediawiki/mediawiki.jqueryMsg.js index 183b525e..70b9be93 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/mediawiki/mediawiki.jqueryMsg.js @@ -3,6 +3,7 @@ * See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs * * @author neilk@wikimedia.org +* @author mflaschen@wikimedia.org */ ( function ( mw, $ ) { var oldParser, @@ -11,6 +12,31 @@ magic : { 'SITENAME' : mw.config.get( 'wgSiteName' ) }, + // This is a whitelist based on, but simpler than, Sanitizer.php. + // Self-closing tags are not currently supported. + allowedHtmlElements : [ + 'b', + 'i' + ], + // Key tag name, value allowed attributes for that tag. + // See Sanitizer::setupAttributeWhitelist + allowedHtmlCommonAttributes : [ + // HTML + 'id', + 'class', + 'style', + 'lang', + 'dir', + 'title', + + // WAI-ARIA + 'role' + ], + + // Attributes allowed for specific elements. + // Key is element name in lower case + // Value is array of allowed attributes for that element + allowedHtmlAttributesByElement : {}, messages : mw.messages, language : mw.language, @@ -18,7 +44,7 @@ // // Only 'text', 'parse', and 'escaped' are supported, and the // actual escaping for 'escaped' is done by other code (generally - // through jqueryMsg). + // through mediawiki.js). // // However, note that this default only // applies to direct calls to jqueryMsg. The default for mediawiki.js itself @@ -28,6 +54,47 @@ }; /** + * Wrapper around jQuery append that converts all non-objects to TextNode so append will not + * convert what it detects as an htmlString to an element. + * + * Object elements of children (jQuery, HTMLElement, TextNode, etc.) will be left as is. + * + * @param {jQuery} $parent Parent node wrapped by jQuery + * @param {Object|string|Array} children What to append, with the same possible types as jQuery + * @return {jQuery} $parent + */ + function appendWithoutParsing( $parent, children ) { + var i, len; + + if ( !$.isArray( children ) ) { + children = [children]; + } + + for ( i = 0, len = children.length; i < len; i++ ) { + if ( typeof children[i] !== 'object' ) { + children[i] = document.createTextNode( children[i] ); + } + } + + return $parent.append( children ); + } + + /** + * Decodes the main HTML entities, those encoded by mw.html.escape. + * + * @param {string} encode Encoded string + * @return {string} String with those entities decoded + */ + function decodePrimaryHtmlEntities( encoded ) { + return encoded + .replace( /'/g, '\'' ) + .replace( /"/g, '"' ) + .replace( /</g, '<' ) + .replace( />/g, '>' ) + .replace( /&/g, '&' ); + } + + /** * Given parser options, return a function that parses a key and replacements, returning jQuery object * @param {Object} parser options * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery} @@ -48,7 +115,7 @@ try { return parser.parse( key, argsArray ); } catch ( e ) { - return $( '<span>' ).append( key + ': ' + e.message ); + return $( '<span>' ).text( key + ': ' + e.message ); } }; } @@ -125,10 +192,10 @@ */ return function () { var $target = this.empty(); - // TODO: Simply $target.append( failableParserFn( arguments ).contents() ) - // or Simply $target.append( failableParserFn( arguments ) ) + // TODO: Simply appendWithoutParsing( $target, failableParserFn( arguments ).contents() ) + // or Simply appendWithoutParsing( $target, failableParserFn( arguments ) ) $.each( failableParserFn( arguments ).contents(), function ( i, node ) { - $target.append( node ); + appendWithoutParsing( $target, node ); } ); return $target; }; @@ -206,11 +273,13 @@ * @return {Mixed} abstract syntax tree */ wikiTextToAst: function ( input ) { - var pos, + var pos, settings = this.settings, concat = Array.prototype.concat, regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, regularLiteralWithSquareBrackets, - backslash, anyCharacter, escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral, - whitespace, dollar, digits, - openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openLink, closeLink, templateName, pipe, colon, + doubleQuote, singleQuote, backslash, anyCharacter, asciiAlphabetLiteral, + escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral, + whitespace, dollar, digits, htmlDoubleQuoteAttributeValue, htmlSingleQuoteAttributeValue, + htmlAttributeEquals, openHtmlStartTag, optionalForwardSlash, openHtmlEndTag, closeHtmlTag, + openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openWikilink, closeWikilink, templateName, pipe, colon, templateContents, openTemplate, closeTemplate, nonWhitespaceExpression, paramExpression, expression, curlyBraceTransformExpression, result; @@ -289,6 +358,15 @@ return result; }; } + + /** + * Makes a regex parser, given a RegExp object. + * The regex being passed in should start with a ^ to anchor it to the start + * of the string. + * + * @param {RegExp} regex anchored regex + * @return {Function} function to parse input based on the regex + */ function makeRegexParser( regex ) { return function () { var matches = input.substr( pos ).match( regex ); @@ -315,12 +393,23 @@ // 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 - regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ ); + + regularLiteral = makeRegexParser( /^[^{}\[\]$<\\]/ ); regularLiteralWithoutBar = makeRegexParser(/^[^{}\[\]$\\|]/); regularLiteralWithoutSpace = makeRegexParser(/^[^{}\[\]$\s]/); regularLiteralWithSquareBrackets = makeRegexParser( /^[^{}$\\]/ ); + backslash = makeStringParser( '\\' ); + doubleQuote = makeStringParser( '"' ); + singleQuote = makeStringParser( '\'' ); anyCharacter = makeRegexParser( /^./ ); + + openHtmlStartTag = makeStringParser( '<' ); + optionalForwardSlash = makeRegexParser( /^\/?/ ); + openHtmlEndTag = makeStringParser( '</' ); + htmlAttributeEquals = makeRegexParser( /^\s*=\s*/ ); + closeHtmlTag = makeRegexParser( /^\s*>/ ); + function escapedLiteral() { var result = sequence( [ backslash, @@ -369,6 +458,10 @@ return result === null ? null : result.join(''); } + asciiAlphabetLiteral = makeRegexParser( /[A-Za-z]+/ ); + htmlDoubleQuoteAttributeValue = makeRegexParser( /^[^"]*/ ); + htmlSingleQuoteAttributeValue = makeRegexParser( /^[^']*/ ); + whitespace = makeRegexParser( /^\s+/ ); dollar = makeStringParser( '$' ); digits = makeRegexParser( /^\d+/ ); @@ -385,7 +478,7 @@ } openExtlink = makeStringParser( '[' ); closeExtlink = makeStringParser( ']' ); - // this extlink MUST have inner text, e.g. [foo] not allowed; [foo bar] is allowed + // this extlink MUST have inner contents, e.g. [foo] not allowed; [foo bar] [foo <i>bar</i>], etc. are allowed function extlink() { var result, parsedResult; result = null; @@ -393,11 +486,18 @@ openExtlink, nonWhitespaceExpression, whitespace, - expression, + nOrMore( 1, expression ), closeExtlink ] ); if ( parsedResult !== null ) { - result = [ 'LINK', parsedResult[1], parsedResult[3] ]; + result = [ 'EXTLINK', parsedResult[1] ]; + // TODO (mattflaschen, 2013-03-22): Clean this up if possible. + // It's avoiding CONCAT for single nodes, so they at least doesn't get the htmlEmitter span. + if ( parsedResult[3].length === 1 ) { + result.push( parsedResult[3][0] ); + } else { + result.push( ['CONCAT'].concat( parsedResult[3] ) ); + } } return result; } @@ -414,10 +514,10 @@ if ( result === null ) { return null; } - return [ 'LINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ]; + return [ 'EXTLINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ]; } - openLink = makeStringParser( '[[' ); - closeLink = makeStringParser( ']]' ); + openWikilink = makeStringParser( '[[' ); + closeWikilink = makeStringParser( ']]' ); pipe = makeStringParser( '|' ); function template() { @@ -448,21 +548,158 @@ wikilinkPage // unpiped link ] ); - function link() { + function wikilink() { var result, parsedResult, parsedLinkContents; result = null; parsedResult = sequence( [ - openLink, + openWikilink, wikilinkContents, - closeLink + closeWikilink ] ); if ( parsedResult !== null ) { parsedLinkContents = parsedResult[1]; - result = [ 'WLINK' ].concat( parsedLinkContents ); + result = [ 'WIKILINK' ].concat( parsedLinkContents ); + } + return result; + } + + // TODO: Support data- if appropriate + function doubleQuotedHtmlAttributeValue() { + var parsedResult = sequence( [ + doubleQuote, + htmlDoubleQuoteAttributeValue, + doubleQuote + ] ); + return parsedResult === null ? null : parsedResult[1]; + } + + function singleQuotedHtmlAttributeValue() { + var parsedResult = sequence( [ + singleQuote, + htmlSingleQuoteAttributeValue, + singleQuote + ] ); + return parsedResult === null ? null : parsedResult[1]; + } + + function htmlAttribute() { + var parsedResult = sequence( [ + whitespace, + asciiAlphabetLiteral, + htmlAttributeEquals, + choice( [ + doubleQuotedHtmlAttributeValue, + singleQuotedHtmlAttributeValue + ] ) + ] ); + return parsedResult === null ? null : [parsedResult[1], parsedResult[3]]; + } + + /** + * Checks if HTML is allowed + * + * @param {string} startTagName HTML start tag name + * @param {string} endTagName HTML start tag name + * @param {Object} attributes array of consecutive key value pairs, + * with index 2 * n being a name and 2 * n + 1 the associated value + * @return {boolean} true if this is HTML is allowed, false otherwise + */ + function isAllowedHtml( startTagName, endTagName, attributes ) { + var i, len, attributeName; + + startTagName = startTagName.toLowerCase(); + endTagName = endTagName.toLowerCase(); + if ( startTagName !== endTagName || $.inArray( startTagName, settings.allowedHtmlElements ) === -1 ) { + return false; + } + + for ( i = 0, len = attributes.length; i < len; i += 2 ) { + attributeName = attributes[i]; + if ( $.inArray( attributeName, settings.allowedHtmlCommonAttributes ) === -1 && + $.inArray( attributeName, settings.allowedHtmlAttributesByElement[startTagName] || [] ) === -1 ) { + return false; + } + } + + return true; + } + + function htmlAttributes() { + var parsedResult = nOrMore( 0, htmlAttribute )(); + // Un-nest attributes array due to structure of jQueryMsg operations (see emit). + return concat.apply( ['HTMLATTRIBUTES'], parsedResult ); + } + + // Subset of allowed HTML markup. + // Most elements and many attributes allowed on the server are not supported yet. + function html() { + var result = null, parsedOpenTagResult, parsedHtmlContents, + parsedCloseTagResult, wrappedAttributes, attributes, + startTagName, endTagName, startOpenTagPos, startCloseTagPos, + endOpenTagPos, endCloseTagPos; + + // Break into three sequence calls. That should allow accurate reconstruction of the original HTML, and requiring an exact tag name match. + // 1. open through closeHtmlTag + // 2. expression + // 3. openHtmlEnd through close + // This will allow recording the positions to reconstruct if HTML is to be treated as text. + + startOpenTagPos = pos; + parsedOpenTagResult = sequence( [ + openHtmlStartTag, + asciiAlphabetLiteral, + htmlAttributes, + optionalForwardSlash, + closeHtmlTag + ] ); + + if ( parsedOpenTagResult === null ) { + return null; } + + endOpenTagPos = pos; + startTagName = parsedOpenTagResult[1]; + + parsedHtmlContents = nOrMore( 0, expression )(); + + startCloseTagPos = pos; + parsedCloseTagResult = sequence( [ + openHtmlEndTag, + asciiAlphabetLiteral, + closeHtmlTag + ] ); + + if ( parsedCloseTagResult === null ) { + // Closing tag failed. Return the start tag and contents. + return [ 'CONCAT', input.substring( startOpenTagPos, endOpenTagPos ) ].concat( parsedHtmlContents ); + } + + endCloseTagPos = pos; + endTagName = parsedCloseTagResult[1]; + wrappedAttributes = parsedOpenTagResult[2]; + attributes = wrappedAttributes.slice( 1 ); + if ( isAllowedHtml( startTagName, endTagName, attributes) ) { + result = [ 'HTMLELEMENT', startTagName, wrappedAttributes ].concat( parsedHtmlContents ); + } else { + // HTML is not allowed, so contents will remain how + // it was, while HTML markup at this level will be + // treated as text + // E.g. assuming script tags are not allowed: + // + // <script>[[Foo|bar]]</script> + // + // results in '<script>' and '</script>' + // (not treated as an HTML tag), surrounding a fully + // parsed HTML link. + // + // Concatenate everything from the tag, flattening the contents. + result = [ 'CONCAT', input.substring( startOpenTagPos, endOpenTagPos ) ].concat( parsedHtmlContents, input.substring( startCloseTagPos, endCloseTagPos ) ); + } + return result; } + templateName = transform( // see $wgLegalTitleChars // not allowing : due to the need to catch "PLURAL:$1" @@ -525,7 +762,7 @@ closeTemplate = makeStringParser('}}'); nonWhitespaceExpression = choice( [ template, - link, + wikilink, extLinkParam, extlink, replacement, @@ -533,7 +770,7 @@ ] ); paramExpression = choice( [ template, - link, + wikilink, extLinkParam, extlink, replacement, @@ -542,10 +779,11 @@ expression = choice( [ template, - link, + wikilink, extLinkParam, extlink, replacement, + html, literal ] ); @@ -659,12 +897,12 @@ $.each( nodes, function ( i, node ) { if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) { $.each( node.contents(), function ( j, childNode ) { - $span.append( childNode ); + appendWithoutParsing( $span, childNode ); } ); } else { // 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 ) ); + appendWithoutParsing( $span, node ); } } ); return $span; @@ -704,11 +942,11 @@ * * @param nodes */ - wlink: function ( nodes ) { + wikilink: function ( nodes ) { var page, anchor, url; page = nodes[0]; - url = mw.util.wikiGetlink( page ); + url = mw.util.getUrl( page ); // [[Some Page]] or [[Namespace:Some Page]] if ( nodes.length === 1 ) { @@ -730,6 +968,36 @@ }, /** + * Converts array of HTML element key value pairs to object + * + * @param {Array} nodes array of consecutive key value pairs, with index 2 * n being a name and 2 * n + 1 the associated value + * @return {Object} object mapping attribute name to attribute value + */ + htmlattributes: function ( nodes ) { + var i, len, mapping = {}; + for ( i = 0, len = nodes.length; i < len; i += 2 ) { + mapping[nodes[i]] = decodePrimaryHtmlEntities( nodes[i + 1] ); + } + return mapping; + }, + + /** + * Handles an (already-validated) HTML element. + * + * @param {Array} nodes nodes to process when creating element + * @return {jQuery|Array} jQuery node for valid HTML or array for disallowed element + */ + htmlelement: function ( nodes ) { + var tagName, attributes, contents, $element; + + tagName = nodes.shift(); + attributes = nodes.shift(); + contents = nodes; + $element = $( document.createElement( tagName ) ).attr( attributes ); + return appendWithoutParsing( $element, contents ); + }, + + /** * Transform parsed structure into external link * If the href is a jQuery object, treat it as "enclosing" the link text. * ... function, treat it as the click handler @@ -738,7 +1006,7 @@ * @param {Array} of two elements, {jQuery|Function|String} and {String} * @return {jQuery} */ - link: function ( nodes ) { + extlink: function ( nodes ) { var $el, arg = nodes[0], contents = nodes[1]; @@ -752,12 +1020,11 @@ $el.attr( 'href', arg.toString() ); } } - $el.append( contents ); - return $el; + return appendWithoutParsing( $el, contents ); }, /** - * This is basically use a combination of replace + link (link with parameter + * This is basically use a combination of replace + external link (link with parameter * as url), but we don't want to run the regular replace here-on: inserting a * url as href-attribute of a link will automatically escape it already, so * we don't want replace to (manually) escape it as well. @@ -765,7 +1032,7 @@ * @param {Array} of one element, integer, n >= 0 * @return {String} replacement */ - linkparam: function ( nodes, replacements ) { + extlinkparam: function ( nodes, replacements ) { var replacement, index = parseInt( nodes[0], 10 ); if ( index < replacements.length) { @@ -773,7 +1040,7 @@ } else { replacement = '$' + ( index + 1 ); } - return this.link( [ replacement, nodes[1] ] ); + return this.extlink( [ replacement, nodes[1] ] ); }, /** @@ -865,7 +1132,7 @@ // 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.format === 'plain' || !/\{\{|\[/.test(this.map.get( this.key ) ) ) { + if ( this.format === 'plain' || !/\{\{|[\[<>]/.test(this.map.get( this.key ) ) ) { // Fall back to mw.msg's simple parser return oldParser.apply( this ); } diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js index ca987543..80223e5d 100644 --- a/resources/mediawiki/mediawiki.js +++ b/resources/mediawiki/mediawiki.js @@ -1,5 +1,9 @@ -/* - * Core MediaWiki JavaScript Library +/** + * Base library for MediaWiki. + * + * @class mw + * @alternateClassName mediaWiki + * @singleton */ var mw = ( function ( $, undefined ) { @@ -10,15 +14,67 @@ var mw = ( function ( $, undefined ) { var hasOwn = Object.prototype.hasOwnProperty, slice = Array.prototype.slice; + /** + * Log a message to window.console, if possible. Useful to force logging of some + * errors that are otherwise hard to detect (I.e., this logs also in production mode). + * Gets console references in each invocation, so that delayed debugging tools work + * fine. No need for optimization here, which would only result in losing logs. + * + * @private + * @param {string} msg text for the log entry. + * @param {Error} [e] + */ + function log( msg, e ) { + var console = window.console; + if ( console && console.log ) { + console.log( msg ); + // If we have an exception object, log it through .error() to trigger + // proper stacktraces in browsers that support it. There are no (known) + // browsers that don't support .error(), that do support .log() and + // have useful exception handling through .log(). + if ( e && console.error ) { + console.error( String( e ), e ); + } + } + } + /* Object constructors */ /** * Creates an object that can be read from or written to from prototype functions * that allow both single and multiple variables at once. + * + * @example + * + * var addies, wanted, results; + * + * // Create your address book + * addies = new mw.Map(); + * + * // This data could be coming from an external source (eg. API/AJAX) + * addies.set( { + * 'John Doe' : '10 Wall Street, New York, USA', + * 'Jane Jackson' : '21 Oxford St, London, UK', + * 'Dominique van Halen' : 'Kalverstraat 7, Amsterdam, NL' + * } ); + * + * wanted = ['Dominique van Halen', 'George Johnson', 'Jane Jackson']; + * + * // You can detect missing keys first + * if ( !addies.exists( wanted ) ) { + * // One or more are missing (in this case: "George Johnson") + * mw.log( 'One or more names were not found in your address book' ); + * } + * + * // Or just let it give you what it can + * results = addies.get( wanted, 'Middle of Nowhere, Alaska, US' ); + * mw.log( results['Jane Jackson'] ); // "21 Oxford St, London, UK" + * mw.log( results['George Johnson'] ); // "Middle of Nowhere, Alaska, US" + * * @class mw.Map * * @constructor - * @param {boolean} global Whether to store the values in the global window + * @param {boolean} [global=false] Whether to store the values in the global window * object or a exclusively in the object property 'values'. */ function Map( global ) { @@ -32,8 +88,8 @@ var mw = ( function ( $, undefined ) { * * If called with no arguments, all values will be returned. * - * @param selection mixed String key or array of keys to get values for. - * @param fallback mixed Value to use in case key(s) do not exist (optional). + * @param {string|Array} selection String key or array of keys to get values for. + * @param {Mixed} [fallback] Value to use in case key(s) do not exist. * @return mixed If selection was a string returns the value or null, * 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 @@ -73,8 +129,8 @@ var mw = ( function ( $, undefined ) { /** * Sets one or multiple key/value pairs. * - * @param selection {mixed} String key or array of keys to set values for. - * @param value {mixed} Value to set (optional, only in use when key is a string) + * @param {string|Object} selection String key to set value for, or object mapping keys to values. + * @param {Mixed} [value] Value to set (optional, only in use when key is a string) * @return {Boolean} This returns true on success, false on failure. */ set: function ( selection, value ) { @@ -96,7 +152,7 @@ var mw = ( function ( $, undefined ) { /** * Checks if one or multiple keys exist. * - * @param selection {mixed} String key or array of keys to check + * @param {Mixed} selection String key or array of keys to check * @return {boolean} Existence of key(s) */ exists: function ( selection ) { @@ -115,8 +171,12 @@ var mw = ( function ( $, undefined ) { }; /** - * Object constructor for messages, - * similar to the Message class in MediaWiki PHP. + * Object constructor for messages. + * + * Similar to the Message class in MediaWiki PHP. + * + * Format defaults to 'text'. + * * @class mw.Message * * @constructor @@ -134,8 +194,7 @@ var mw = ( function ( $, undefined ) { Message.prototype = { /** - * Simple message parser, does $N replacement, HTML-escaping (only for - * 'escaped' format), and nothing else. + * Simple message parser, does $N replacement and nothing else. * * This may be overridden to provide a more complex message parser. * @@ -259,19 +318,21 @@ var mw = ( function ( $, undefined ) { } }; - /** - * @class mw - * @alternateClassName mediaWiki - * @singleton - */ return { /* Public Members */ /** - * Dummy function which in debug mode can be replaced with a function that - * emulates console.log in console-less environments. + * Dummy placeholder for {@link mw.log} + * @method */ - log: function () { }, + log: ( function () { + var log = function () {}; + log.warn = function () {}; + log.deprecate = function ( obj, key, val ) { + obj[key] = val; + }; + return log; + }() ), // Make the Map constructor publicly available. Map: Map, @@ -280,13 +341,17 @@ var mw = ( function ( $, undefined ) { Message: Message, /** - * List of configuration values + * Map 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 - * in the global window object. - * @property + * Check out [the complete list of configuration values](https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config) + * on MediaWiki.org. + * + * If `$wgLegacyJavaScriptGlobals` is true, this Map will put its values in the + * global window object. + * + * @property {mw.Map} config */ + // Dummy placeholder. Re-assigned in ResourceLoaderStartupModule with an instance of `mw.Map`. config: null, /** @@ -295,9 +360,15 @@ var mw = ( function ( $, undefined ) { */ libs: {}, - /* Extension points */ - /** + * Access container for deprecated functionality that can be moved from + * from their legacy location and attached to this object (e.g. a global + * function that is deprecated and as stop-gap can be exposed through here). + * + * This was reserved for future use but never ended up being used. + * + * @deprecated since 1.22: Let deprecated identifiers keep their original name + * and use mw.log#deprecate to create an access container for tracking. * @property */ legacy: {}, @@ -311,7 +382,9 @@ var mw = ( function ( $, undefined ) { /* Public Methods */ /** - * Gets a message object, similar to wfMessage(). + * Get a message object. + * + * Similar to wfMessage() in MediaWiki PHP. * * @param {string} key Key of message to get * @param {Mixed...} parameters Parameters for the $N replacements in messages. @@ -324,14 +397,16 @@ var mw = ( function ( $, undefined ) { }, /** - * Gets a message string, similar to wfMessage() + * Get a message string using 'text' format. + * + * Similar to wfMsg() in MediaWiki PHP. * - * @see mw.Message#toString + * @see mw.Message * @param {string} key Key of message to get * @param {Mixed...} parameters Parameters for the $N replacements in messages. * @return {string} */ - msg: function ( /* key, parameters... */ ) { + msg: function () { return mw.message.apply( mw.message, arguments ).toString(); }, @@ -420,11 +495,11 @@ var mw = ( function ( $, undefined ) { * * @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. + * @param {HTMLElement|jQuery} [nextnode=document.head] The element where the style tag should be + * inserted before. Otherwise it will be appended to `<head>`. + * @return {HTMLElement} Reference to the created `<style>` element. */ - function addStyleTag( text, nextnode ) { + function newStyleTag( text, nextnode ) { var s = document.createElement( 'style' ); // Insert into document before setting cssText (bug 33305) if ( nextnode ) { @@ -457,7 +532,7 @@ var mw = ( function ( $, undefined ) { /** * 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. @@ -470,8 +545,13 @@ var mw = ( function ( $, undefined ) { } /** + * Add a bit of CSS text to the current browser page. + * + * The CSS will be appended to an existing ResourceLoader-created `<style>` tag + * or create a new one based on whether the given `cssText` is safe for extension. + * * @param {string} [cssText=cssBuffer] If called without cssText, - * the internal buffer will be inserted instead. + * the internal buffer will be inserted instead. * @param {Function} [callback] */ function addEmbeddedCSS( cssText, callback ) { @@ -533,7 +613,7 @@ var mw = ( function ( $, undefined ) { try { styleEl.styleSheet.cssText += cssText; // IE } catch ( e ) { - log( 'addEmbeddedCSS fail\ne.message: ' + e.message, e ); + log( 'addEmbeddedCSS fail', e ); } } else { styleEl.appendChild( document.createTextNode( String( cssText ) ) ); @@ -543,7 +623,7 @@ var mw = ( function ( $, undefined ) { } } - $( addStyleTag( cssText, getMarker() ) ).data( 'ResourceLoaderDynamicStyleTag', true ); + $( newStyleTag( cssText, getMarker() ) ).data( 'ResourceLoaderDynamicStyleTag', true ); cssCallbacks.fire().empty(); } @@ -659,7 +739,7 @@ var mw = ( function ( $, undefined ) { * * @private * @param {string|string[]} states Module states to filter by - * @param {Array} modules List of module names to filter (optional, by default the entire + * @param {Array} [modules] List of module names to filter (optional, by default the entire * registry is used) * @return {Array} List of filtered module names */ @@ -712,30 +792,6 @@ var mw = ( function ( $, undefined ) { } /** - * Log a message to window.console, if possible. Useful to force logging of some - * errors that are otherwise hard to detect (I.e., this logs also in production mode). - * Gets console references in each invocation, so that delayed debugging tools work - * fine. No need for optimization here, which would only result in losing logs. - * - * @private - * @param {string} msg text for the log entry. - * @param {Error} [e] - */ - function log( msg, e ) { - var console = window.console; - if ( console && console.log ) { - console.log( msg ); - // If we have an exception object, log it through .error() to trigger - // proper stacktraces in browsers that support it. There are no (known) - // browsers that don't support .error(), that do support .log() and - // have useful exception handling through .log(). - if ( e && console.error ) { - console.error( e ); - } - } - } - - /** * A module has entered state 'ready', 'error', or 'missing'. Automatically update pending jobs * and modules that depend upon this module. if the given module failed, propagate the 'error' * state up the dependency tree; otherwise, execute all jobs/modules that now have all their @@ -775,22 +831,18 @@ var mw = ( function ( $, undefined ) { j -= 1; try { if ( hasErrors ) { - throw new Error( 'Module ' + module + ' failed.'); + if ( $.isFunction( job.error ) ) { + job.error( new Error( 'Module ' + module + ' has failed dependencies' ), [module] ); + } } else { if ( $.isFunction( job.ready ) ) { job.ready(); } } } catch ( e ) { - if ( $.isFunction( job.error ) ) { - try { - job.error( e, [module] ); - } catch ( ex ) { - // A user-defined operation raised an exception. Swallow to protect - // our state machine! - log( 'Exception thrown by job.error()', ex ); - } - } + // A user-defined callback raised an exception. + // Swallow it to protect our state machine! + log( 'Exception thrown by job.error', e ); } } } @@ -816,8 +868,7 @@ var mw = ( function ( $, undefined ) { */ function addScript( src, callback, async ) { /*jshint evil:true */ - var script, head, - done = false; + var script, head, done; // Using isReady directly instead of storing it locally from // a $.fn.ready callback (bug 31895). @@ -829,6 +880,7 @@ var mw = ( function ( $, undefined ) { // IE-safe way of getting the <head>. document.head isn't supported // in old IE, and doesn't work when in the <head>. + done = false; head = document.getElementsByTagName( 'head' )[0] || document.body; script = document.createElement( 'script' ); @@ -848,12 +900,12 @@ var mw = ( function ( $, undefined ) { // Handle memory leak in IE script.onload = script.onreadystatechange = null; - // Remove the script + // Detach the element from the document if ( script.parentNode ) { script.parentNode.removeChild( script ); } - // Dereference the script + // Dereference the element from javascript script = undefined; callback(); @@ -950,7 +1002,7 @@ var mw = ( function ( $, undefined ) { } 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 ); + log( 'Exception thrown by ' + module, e ); registry[module].state = 'error'; handlePending( module ); } @@ -967,30 +1019,37 @@ var mw = ( function ( $, undefined ) { 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 + if ( $.isReady || registry[module].async ) { + // 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 + } + }; + }; + }() ); + } else { + // We are in blocking mode, and so we can't afford to wait for CSS + cssHandle = function () {}; + // Run immediately + checkCssHandles = runScript; + } // Process styles (see also mw.loader.implement) // * back-compat: { <media>: css } @@ -1131,7 +1190,7 @@ var mw = ( function ( $, undefined ) { * @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 + * @param {boolean} async If true, use an asynchronous request even if document ready has not yet occurred */ function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) { var request = $.extend( @@ -1146,10 +1205,24 @@ var mw = ( function ( $, undefined ) { /* Public Methods */ return { - addStyleTag: addStyleTag, + /** + * The module registry is exposed as an aid for debugging and inspecting page + * state; it is not a public interface for modifying the registry. + * + * @see #registry + * @property + * @private + */ + moduleRegistry: registry, /** - * Requests dependencies from server, loading and executing when things when ready. + * @inheritdoc #newStyleTag + * @method + */ + addStyleTag: newStyleTag, + + /** + * Batch-request queued dependencies from the server. */ work: function () { var reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup, @@ -1311,15 +1384,15 @@ var mw = ( function ( $, undefined ) { }, /** - * Registers a module, letting the system know about it and its + * Register a module, letting the system know about it and its * properties. Startup modules contain calls to this function. * - * @param module {String}: Module name - * @param version {Number}: Module version number as a timestamp (falls backs to 0) - * @param dependencies {String|Array|Function}: One string or array of strings of module + * @param {string} module Module name + * @param {number} version Module version number as a timestamp (falls backs to 0) + * @param {string|Array|Function} dependencies One string or array of strings of module * names on which this module depends, or a function that returns that array. - * @param group {String}: Group which the module is in (optional, defaults to null) - * @param source {String}: Name of the source. Defaults to local. + * @param {string} [group=null] Group which the module is in + * @param {string} [source='local'] Name of the source */ register: function ( module, version, dependencies, group, source ) { var m; @@ -1362,9 +1435,10 @@ var mw = ( function ( $, undefined ) { }, /** - * Implements a module, giving the system a course of action to take - * upon loading. Results of a request for one or more modules contain - * calls to this function. + * Implement a module given the components that make up the module. + * + * When #load or #using requests one or more modules, the server + * response contain calls to this function. * * All arguments are required. * @@ -1419,12 +1493,12 @@ var mw = ( function ( $, undefined ) { }, /** - * Executes a function as soon as one or more required modules are ready + * Execute a function as soon as one or more required modules are ready. * - * @param dependencies {String|Array} Module name or array of modules names the callback + * @param {string|Array} dependencies Module name or array of modules names the callback * dependends on to be ready before executing - * @param ready {Function} callback to execute when all dependencies are ready (optional) - * @param error {Function} callback to execute when if dependencies have a errors (optional) + * @param {Function} [ready] callback to execute when all dependencies are ready + * @param {Function} [error] callback to execute when if dependencies have a errors */ using: function ( dependencies, ready, error ) { var tod = typeof dependencies; @@ -1456,17 +1530,17 @@ var mw = ( function ( $, undefined ) { }, /** - * Loads an external script or one or more modules for future use + * Load an external script or one or more modules. * - * @param modules {mixed} Either the name of a module, array of modules, + * @param {string|Array} modules Either the name of a module, array of modules, * or a URL of an external script or style - * @param type {String} mime-type to use if calling with a URL of an + * @param {string} [type='text/javascript'] mime-type to use if calling with a URL of an * external script or style; acceptable values are "text/css" and * "text/javascript"; if no type is provided, text/javascript is assumed. - * @param async {Boolean} (optional) If true, load modules asynchronously - * even if document ready has not yet occurred. If false (default), - * block before document ready and load async after. If not set, true will - * be assumed if loading a URL, and false will be assumed otherwise. + * @param {boolean} [async] If true, load modules asynchronously + * even if document ready has not yet occurred. If false, block before + * document ready and load async after. If not set, true will be + * assumed if loading a URL, and false will be assumed otherwise. */ load: function ( modules, type, async ) { var filtered, m, module, l; @@ -1536,10 +1610,10 @@ var mw = ( function ( $, undefined ) { }, /** - * Changes the state of a module + * Change the state of one or more modules. * - * @param module {String|Object} module name or object of module name/state pairs - * @param state {String} state name + * @param {string|Object} module module name or object of module name/state pairs + * @param {string} state state name */ state: function ( module, state ) { var m; @@ -1565,9 +1639,9 @@ var mw = ( function ( $, undefined ) { }, /** - * Gets the version of a module + * Get the version of a module. * - * @param module string name of module to get version for + * @param {string} module Name of module to get version for */ getVersion: function ( module ) { if ( registry[module] !== undefined && registry[module].version !== undefined ) { @@ -1577,16 +1651,17 @@ var mw = ( function ( $, undefined ) { }, /** - * @deprecated since 1.18 use mw.loader.getVersion() instead + * @inheritdoc #getVersion + * @deprecated since 1.18 use #getVersion instead */ version: function () { return mw.loader.getVersion.apply( mw.loader, arguments ); }, /** - * Gets the state of a module + * Get the state of a module. * - * @param module string name of module to get state for + * @param {string} module name of module to get state for */ getState: function ( module ) { if ( registry[module] !== undefined && registry[module].state !== undefined ) { @@ -1607,16 +1682,45 @@ var mw = ( function ( $, undefined ) { }, /** - * For backwards-compatibility with Squid-cached pages. Loads mw.user + * Load the `mediawiki.user` module. + * + * For backwards-compatibility with cached pages from before 2013 where: + * + * - the `mediawiki.user` module didn't exist yet + * - `mw.user` was still part of mediawiki.js + * - `mw.loader.go` still existed and called after `mw.loader.load()` */ go: function () { mw.loader.load( 'mediawiki.user' ); + }, + + /** + * @inheritdoc mw.inspect#runReports + * @method + */ + inspect: function () { + var args = slice.call( arguments ); + mw.loader.using( 'mediawiki.inspect', function () { + mw.inspect.runReports.apply( mw.inspect, args ); + } ); } + }; }() ), /** * HTML construction helper functions + * + * @example + * + * var Html, output; + * + * Html = mw.html; + * output = Html.element( 'div', {}, new Html.Raw( + * Html.element( 'img', { src: '<' } ) + * ) ); + * mw.log( output ); // <div><img src="<"/></div> + * * @class mw.html * @singleton */ @@ -1646,39 +1750,17 @@ var mw = ( function ( $, undefined ) { }, /** - * Wrapper object for raw HTML passed to mw.html.element(). - * @class mw.html.Raw - */ - Raw: function ( value ) { - this.value = value; - }, - - /** - * Wrapper object for CDATA element contents passed to mw.html.element() - * @class mw.html.Cdata - */ - Cdata: function ( value ) { - this.value = value; - }, - - /** * Create an HTML element string, with safe escaping. * - * @param name The tag name. - * @param attrs An object with members mapping element names to values - * @param contents The contents of the element. May be either: + * @param {string} name The tag name. + * @param {Object} attrs An object with members mapping element names to values + * @param {Mixed} contents The contents of the element. May be either: * - string: The string is escaped. * - null or undefined: The short closing form is used, e.g. <br/>. * - this.Raw: The value attribute is included without escaping. * - this.Cdata: The value attribute is included, and an exception is * thrown if it contains an illegal ETAGO delimiter. * See http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.3.2 - * - * Example: - * var h = mw.html; - * return h.element( 'div', {}, - * new h.Raw( h.element( 'img', {src: '<'} ) ) ); - * Returns <div><img src="<"/></div> */ element: function ( name, attrs, contents ) { var v, attrName, s = '<' + name; @@ -1727,6 +1809,22 @@ var mw = ( function ( $, undefined ) { } s += '</' + name + '>'; return s; + }, + + /** + * Wrapper object for raw HTML passed to mw.html.element(). + * @class mw.html.Raw + */ + Raw: function ( value ) { + this.value = value; + }, + + /** + * Wrapper object for CDATA element contents passed to mw.html.element() + * @class mw.html.Cdata + */ + Cdata: function ( value ) { + this.value = value; } }; }() ), @@ -1735,7 +1833,87 @@ var mw = ( function ( $, undefined ) { user: { options: new Map(), tokens: new Map() - } + }, + + /** + * Registry and firing of events. + * + * MediaWiki has various interface components that are extended, enhanced + * or manipulated in some other way by extensions, gadgets and even + * in core itself. + * + * This framework helps streamlining the timing of when these other + * code paths fire their plugins (instead of using document-ready, + * which can and should be limited to firing only once). + * + * Features like navigating to other wiki pages, previewing an edit + * and editing itself – without a refresh – can then retrigger these + * hooks accordingly to ensure everything still works as expected. + * + * Example usage: + * + * mw.hook( 'wikipage.content' ).add( fn ).remove( fn ); + * mw.hook( 'wikipage.content' ).fire( $content ); + * + * Handlers can be added and fired for arbitrary event names at any time. The same + * event can be fired multiple times. The last run of an event is memorized + * (similar to `$(document).ready` and `$.Deferred().done`). + * This means if an event is fired, and a handler added afterwards, the added + * function will be fired right away with the last given event data. + * + * Like Deferreds and Promises, the mw.hook object is both detachable and chainable. + * Thus allowing flexible use and optimal maintainability and authority control. + * You can pass around the `add` and/or `fire` method to another piece of code + * without it having to know the event name (or `mw.hook` for that matter). + * + * var h = mw.hook( 'bar.ready' ); + * new mw.Foo( .. ).fetch( { callback: h.fire } ); + * + * Note: Events are documented with an underscore instead of a dot in the event + * name due to jsduck not supporting dots in that position. + * + * @class mw.hook + */ + hook: ( function () { + var lists = {}; + + /** + * Create an instance of mw.hook. + * + * @method hook + * @member mw + * @param {string} name Name of hook. + * @return {mw.hook} + */ + return function ( name ) { + var list = lists[name] || ( lists[name] = $.Callbacks( 'memory' ) ); + + return { + /** + * Register a hook handler + * @param {Function...} handler Function to bind. + * @chainable + */ + add: list.add, + + /** + * Unregister a hook handler + * @param {Function...} handler Function to unbind. + * @chainable + */ + remove: list.remove, + + /** + * Run a hook. + * @param {Mixed...} data + * @chainable + */ + fire: function () { + return list.fireWith( null, slice.call( arguments ) ); + } + }; + }; + }() ) }; }( jQuery ) ); diff --git a/resources/mediawiki/mediawiki.log.js b/resources/mediawiki/mediawiki.log.js index ee08b12b..75e4c961 100644 --- a/resources/mediawiki/mediawiki.log.js +++ b/resources/mediawiki/mediawiki.log.js @@ -1,4 +1,4 @@ -/** +/*! * Logger for MediaWiki javascript. * Implements the stub left by the main 'mediawiki' module. * @@ -9,15 +9,20 @@ ( function ( mw, $ ) { /** + * @class mw.log + * @singleton + */ + + /** * Logs a message to the console. * * In the case the browser does not have a console API, a console is created on-the-fly by appending - * a <div id="mw-log-console"> element to the bottom of the body and then appending this and future + * a `<div id="mw-log-console">` element to the bottom of the body and then appending this and future * messages to that, instead of the console. * - * @param {String} First in list of variadic messages to output to console. + * @param {string...} msg Messages to output to console. */ - mw.log = function ( /* logmsg, logmsg, */ ) { + mw.log = function () { // Turn arguments into an array var args = Array.prototype.slice.call( arguments ), // Allow log messages to use a configured prefix to identify the source window (ie. frame) @@ -54,7 +59,7 @@ hovzer.update(); } $log.append( - $( '<div></div>' ) + $( '<div>' ) .css( { borderBottom: 'solid 1px #DDDDDD', fontSize: 'small', @@ -68,4 +73,54 @@ } ); }; + /** + * Write a message the console's warning channel. + * Also logs a stacktrace for easier debugging. + * Each action is silently ignored if the browser doesn't support it. + * + * @param {string...} msg Messages to output to console + */ + mw.log.warn = function () { + var console = window.console; + if ( console && console.warn ) { + console.warn.apply( console, arguments ); + if ( console.trace ) { + console.trace(); + } + } + }; + + /** + * Create a property in a host object that, when accessed, will produce + * a deprecation warning in the console with backtrace. + * + * @param {Object} obj Host object of deprecated property + * @param {string} key Name of property to create in `obj` + * @param {Mixed} val The value this property should return when accessed + * @param {string} [msg] Optional text to include in the deprecation message. + */ + mw.log.deprecate = !Object.defineProperty ? function ( obj, key, val ) { + obj[key] = val; + } : function ( obj, key, val, msg ) { + msg = 'MWDeprecationWarning: Use of "' + key + '" property is deprecated.' + + ( msg ? ( ' ' + msg ) : '' ); + try { + Object.defineProperty( obj, key, { + configurable: true, + enumerable: true, + get: function () { + mw.log.warn( msg ); + return val; + }, + set: function ( newVal ) { + mw.log.warn( msg ); + val = newVal; + } + } ); + } catch ( err ) { + // IE8 can throw on Object.defineProperty + obj[key] = val; + } + }; + }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.notification.css b/resources/mediawiki/mediawiki.notification.css index 9a7b651d..3aa358ac 100644 --- a/resources/mediawiki/mediawiki.notification.css +++ b/resources/mediawiki/mediawiki.notification.css @@ -2,15 +2,25 @@ * Stylesheet for mediawiki.notification module */ -#mw-notification-area { +.mw-notification-area { position: absolute; - top: 1em; - right: 1em; + top: 0; + right: 0; + padding: 1em 1em 0 0; width: 20em; line-height: 1.35; z-index: 10000; } +.mw-notification-area-floating { + position: fixed; +} + +* html .mw-notification-area-floating { + /* Make it at least 'absolute' in IE6 since 'fixed' is not supported */ + position: absolute; +} + .mw-notification { padding: 0.25em 1em; margin-bottom: 0.5em; diff --git a/resources/mediawiki/mediawiki.notification.js b/resources/mediawiki/mediawiki.notification.js index fd34e7ee..4ede8096 100644 --- a/resources/mediawiki/mediawiki.notification.js +++ b/resources/mediawiki/mediawiki.notification.js @@ -2,10 +2,10 @@ 'use strict'; var notification, - isPageReady = false, - preReadyNotifQueue = [], // The #mw-notification-area div that all notifications are contained inside. - $area = null; + $area, + isPageReady = false, + preReadyNotifQueue = []; /** * Creates a Notification object for 1 message. @@ -350,7 +350,9 @@ * @ignore */ function init() { - $area = $( '<div id="mw-notification-area"></div>' ) + var offset, $window = $( window ); + + $area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' ) // Pause auto-hide timers when the mouse is in the notification area. .on( { mouseenter: notification.pause, @@ -371,6 +373,19 @@ // Prepend the notification area to the content area and save it's object. mw.util.$content.prepend( $area ); + offset = $area.offset(); + + function updateAreaMode() { + var isFloating = $window.scrollTop() > offset.top; + $area + .toggleClass( 'mw-notification-area-floating', isFloating ) + .toggleClass( 'mw-notification-area-layout', !isFloating ); + } + + $window.on( 'scroll', updateAreaMode ); + + // Initial mode + updateAreaMode(); } /** @@ -411,6 +426,7 @@ * @param {HTMLElement|jQuery|mw.Message|string} message * @param {Object} options The options to use for the notification. * See #defaults for details. + * @return {Object} Object with a close function to close the notification */ notify: function ( message, options ) { var notif; @@ -423,6 +439,7 @@ } else { preReadyNotifQueue.push( notif ); } + return { close: $.proxy( notif.close, notif ) }; }, /** diff --git a/resources/mediawiki/mediawiki.notify.js b/resources/mediawiki/mediawiki.notify.js index 83d95b61..743d6517 100644 --- a/resources/mediawiki/mediawiki.notify.js +++ b/resources/mediawiki/mediawiki.notify.js @@ -1,22 +1,23 @@ /** * @class mw.plugin.notify */ -( function ( mw ) { +( function ( mw, $ ) { 'use strict'; /** * @see mw.notification#notify * @param message * @param options + * @return {jQuery.Promise} */ mw.notify = function ( message, options ) { + var d = $.Deferred(); // Don't bother loading the whole notification system if we never use it. mw.loader.using( 'mediawiki.notification', function () { - // Don't bother calling mw.loader.using a second time after we've already loaded mw.notification. - mw.notify = mw.notification.notify; // Call notify with the notification the user requested of us. - mw.notify( message, options ); - } ); + d.resolve( mw.notification.notify( message, options ) ); + }, d.reject ); + return d.promise(); }; /** @@ -24,4 +25,4 @@ * @mixins mw.plugin.notify */ -}( mediaWiki ) ); +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.searchSuggest.js b/resources/mediawiki/mediawiki.searchSuggest.js index 2bc7cea9..7f078626 100644 --- a/resources/mediawiki/mediawiki.searchSuggest.js +++ b/resources/mediawiki/mediawiki.searchSuggest.js @@ -2,7 +2,7 @@ * Add search suggestions to the search form. */ ( function ( mw, $ ) { - $( document ).ready( function ( $ ) { + $( function () { 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 @@ -130,8 +130,6 @@ searchboxesSelectors = [ // Primary searchbox on every page in standard skins '#searchInput', - // Secondary searchbox in legacy skins (LegacyTemplate::searchForm uses id "searchInput + unique id") - '#searchInput2', // Special:Search '#powerSearchText', '#searchText', @@ -141,36 +139,27 @@ $( searchboxesSelectors.join(', ') ) .suggestions( { fetch: function ( query ) { - var $el, jqXhr; + var $el; if ( query.length !== 0 ) { - $el = $(this); - jqXhr = $.ajax( { - url: mw.util.wikiScript( 'api' ), - data: { - format: 'json', - action: 'opensearch', - search: query, - namespace: 0, - suggest: '' - }, - dataType: 'json', - success: function ( data ) { - if ( $.isArray( data ) && data.length ) { - $el.suggestions( 'suggestions', data[1] ); - } - } - }); - $el.data( 'request', jqXhr ); + $el = $( this ); + $el.data( 'request', ( new mw.Api() ).get( { + action: 'opensearch', + search: query, + namespace: 0, + suggest: '' + } ).done( function ( data ) { + $el.suggestions( 'suggestions', data[1] ); + } ) ); } }, cancel: function () { - var jqXhr = $(this).data( 'request' ); + var apiPromise = $( this ).data( 'request' ); // If the delay setting has caused the fetch to have not even happened - // yet, the jqXHR object will have never been set. - if ( jqXhr && $.isFunction( jqXhr.abort ) ) { - jqXhr.abort(); - $(this).removeData( 'request' ); + // yet, the apiPromise object will have never been set. + if ( apiPromise && $.isFunction( apiPromise.abort ) ) { + apiPromise.abort(); + $( this ).removeData( 'request' ); } }, result: { @@ -196,11 +185,6 @@ return; } - // Placeholder text for search box - $searchInput - .attr( 'placeholder', mw.msg( 'searchsuggest-search' ) ) - .placeholder(); - // Special suggestions functionality for skin-provided search box $searchInput.suggestions( { result: { diff --git a/resources/mediawiki/mediawiki.user.js b/resources/mediawiki/mediawiki.user.js index e0329597..3e375fb6 100644 --- a/resources/mediawiki/mediawiki.user.js +++ b/resources/mediawiki/mediawiki.user.js @@ -1,67 +1,60 @@ -/* - * Implementation for mediaWiki.user +/** + * @class mw.user + * @singleton */ - ( function ( mw, $ ) { + var user, + callbacks = {}, + // Extend the skeleton mw.user from mediawiki.js + // This is kind of ugly but we're stuck with this for b/c reasons + options = mw.user.options || new mw.Map(), + tokens = mw.user.tokens || new mw.Map(); /** - * User object + * Get the current user's groups or rights + * + * @private + * @param {string} info One of 'groups' or 'rights' + * @param {Function} callback */ - function User( options, tokens ) { - var user, callbacks; - - /* Private Members */ - - user = this; - callbacks = {}; - - /** - * Gets the current user's groups or rights. - * @param {String} info: One of 'groups' or 'rights'. - * @param {Function} callback - */ - function getUserInfo( info, callback ) { - var api; - if ( callbacks[info] ) { - callbacks[info].add( callback ); - return; - } - callbacks.rights = $.Callbacks('once memory'); - callbacks.groups = $.Callbacks('once memory'); + function getUserInfo( info, callback ) { + var api; + if ( callbacks[info] ) { callbacks[info].add( callback ); - api = new mw.Api(); - api.get( { - action: 'query', - meta: 'userinfo', - uiprop: 'rights|groups' - } ).always( function ( data ) { - var rights, groups; - if ( data.query && data.query.userinfo ) { - rights = data.query.userinfo.rights; - groups = data.query.userinfo.groups; - } - callbacks.rights.fire( rights || [] ); - callbacks.groups.fire( groups || [] ); - } ); + return; } + callbacks.rights = $.Callbacks('once memory'); + callbacks.groups = $.Callbacks('once memory'); + callbacks[info].add( callback ); + api = new mw.Api(); + api.get( { + action: 'query', + meta: 'userinfo', + uiprop: 'rights|groups' + } ).always( function ( data ) { + var rights, groups; + if ( data.query && data.query.userinfo ) { + rights = data.query.userinfo.rights; + groups = data.query.userinfo.groups; + } + callbacks.rights.fire( rights || [] ); + callbacks.groups.fire( groups || [] ); + } ); + } - /* Public Members */ - - this.options = options || new mw.Map(); - - this.tokens = tokens || new mw.Map(); - - /* Public Methods */ + mw.user = user = { + options: options, + tokens: tokens, /** - * Generates a random user session ID (32 alpha-numeric characters). + * Generate a random user session ID (32 alpha-numeric characters) * * This information would potentially be stored in a cookie to identify a user during a * session or series of sessions. Its uniqueness should not be depended on. * - * @return String: Random set of 32 alpha-numeric characters + * @return {string} Random set of 32 alpha-numeric characters */ - this.generateRandomSessionId = function () { + generateRandomSessionId: function () { var i, r, id = '', seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; @@ -70,33 +63,45 @@ id += seed.substring( r, r + 1 ); } return id; - }; + }, + + /** + * Get the current user's database id + * + * Not to be confused with #id. + * + * @return {number} Current user's id, or 0 if user is anonymous + */ + getId: function () { + return mw.config.get( 'wgUserId', 0 ); + }, /** - * Gets the current user's name. + * Get the current user's name * - * @return Mixed: User name string or null if users is anonymous + * @return {string|null} User name string or null if user is anonymous */ - this.getName = function () { + getName: function () { return mw.config.get( 'wgUserName' ); - }; + }, /** - * @deprecated since 1.20 use mw.user.getName() instead + * @inheritdoc #getName + * @deprecated since 1.20 use #getName instead */ - this.name = function () { - return this.getName(); - }; + name: function () { + return user.getName(); + }, /** - * Get date user registered, if available. + * Get date user registered, if available * - * @return {Date|false|null} date user registered, or false for anonymous users, or + * @return {Date|boolean|null} Date user registered, or false for anonymous users, or * null when data is not available */ - this.getRegistration = function () { + getRegistration: function () { var registration = mw.config.get( 'wgUserRegistration' ); - if ( this.isAnon() ) { + if ( user.isAnon() ) { return false; } else if ( registration === null ) { // Information may not be available if they signed up before @@ -105,110 +110,109 @@ } else { return new Date( registration ); } - }; + }, /** - * Checks if the current user is anonymous. + * Whether the current user is anonymous * - * @return Boolean + * @return {boolean} */ - this.isAnon = function () { + isAnon: function () { return user.getName() === null; - }; + }, /** - * @deprecated since 1.20 use mw.user.isAnon() instead + * @inheritdoc #isAnon + * @deprecated since 1.20 use #isAnon instead */ - this.anonymous = function () { + anonymous: function () { return user.isAnon(); - }; + }, /** - * Gets a random session ID automatically generated and kept in a cookie. + * Get an automatically generated random ID (stored in a session cookie) * * This ID is ephemeral for everyone, staying in their browser only until they close * their browser. * - * @return String: User name or random session ID + * @return {string} Random session ID */ - this.sessionId = function () { + sessionId: function () { var sessionId = $.cookie( 'mediaWiki.user.sessionId' ); - if ( typeof sessionId === 'undefined' || sessionId === null ) { + if ( sessionId === undefined || sessionId === null ) { sessionId = user.generateRandomSessionId(); - $.cookie( 'mediaWiki.user.sessionId', sessionId, { 'expires': null, 'path': '/' } ); + $.cookie( 'mediaWiki.user.sessionId', sessionId, { expires: null, path: '/' } ); } return sessionId; - }; + }, /** - * Gets the current user's name or the session ID + * Get the current user's name or the session ID * - * @return String: User name or random session ID + * Not to be confused with #getId. + * + * @return {string} User name or random session ID */ - this.id = function() { - var name = user.getName(); - if ( name ) { - return name; - } - return user.sessionId(); - }; + id: function () { + return user.getName() || user.sessionId(); + }, /** - * Gets the user's bucket, placing them in one at random based on set odds if needed. - * - * @param key String: Name of bucket - * @param options Object: Bucket configuration options - * @param options.buckets Object: List of bucket-name/relative-probability pairs (required, - * must have at least one pair) - * @param options.version Number: Version of bucket test, changing this forces rebucketing - * (optional, default: 0) - * @param options.tracked Boolean: Track the event of bucketing through the API module of - * the ClickTracking extension (optional, default: false) - * @param options.expires Number: Length of time (in days) until the user gets rebucketed - * (optional, default: 30) - * @return String: Bucket name - the randomly chosen key of the options.buckets object + * Get the user's bucket (place them in one if not done already) * - * @example * mw.user.bucket( 'test', { - * 'buckets': { 'ignored': 50, 'control': 25, 'test': 25 }, - * 'version': 1, - * 'tracked': true, - * 'expires': 7 + * buckets: { ignored: 50, control: 25, test: 25 }, + * version: 1, + * expires: 7 * } ); + * + * @param {string} key Name of bucket + * @param {Object} options Bucket configuration options + * @param {Object} options.buckets List of bucket-name/relative-probability pairs (required, + * must have at least one pair) + * @param {number} [options.version=0] Version of bucket test, changing this forces + * rebucketing + * @param {number} [options.expires=30] Length of time (in days) until the user gets + * rebucketed + * @return {string} Bucket name - the randomly chosen key of the `options.buckets` object */ - this.bucket = function ( key, options ) { + bucket: function ( key, options ) { var cookie, parts, version, bucket, range, k, rand, total; options = $.extend( { buckets: {}, version: 0, - tracked: false, expires: 30 }, options || {} ); cookie = $.cookie( 'mediaWiki.user.bucket:' + key ); // Bucket information is stored as 2 integers, together as version:bucket like: "1:2" - if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) > 0 ) { + if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) !== -1 ) { parts = cookie.split( ':' ); if ( parts.length > 1 && Number( parts[0] ) === options.version ) { version = Number( parts[0] ); bucket = String( parts[1] ); } } + if ( bucket === undefined ) { if ( !$.isPlainObject( options.buckets ) ) { - throw 'Invalid buckets error. Object expected for options.buckets.'; + throw new Error( 'Invalid bucket. Object expected for options.buckets.' ); } + version = Number( options.version ); + // Find range range = 0; for ( k in options.buckets ) { range += options.buckets[k]; } + // Select random value within range rand = Math.random() * range; + // Determine which bucket the value landed in total = 0; for ( k in options.buckets ) { @@ -218,39 +222,34 @@ break; } } - if ( options.tracked ) { - mw.loader.using( 'jquery.clickTracking', function () { - $.trackAction( - 'mediaWiki.user.bucket:' + key + '@' + version + ':' + bucket - ); - } ); - } + $.cookie( 'mediaWiki.user.bucket:' + key, version + ':' + bucket, - { 'path': '/', 'expires': Number( options.expires ) } + { path: '/', expires: Number( options.expires ) } ); } + return bucket; - }; + }, /** - * Gets the current user's groups. + * Get the current user's groups + * + * @param {Function} callback */ - this.getGroups = function ( callback ) { + getGroups: function ( callback ) { getUserInfo( 'groups', callback ); - }; + }, /** - * Gets the current user's rights. + * Get the current user's rights + * + * @param {Function} callback */ - this.getRights = function ( callback ) { + getRights: function ( callback ) { getUserInfo( 'rights', callback ); - }; - } - - // Extend the skeleton mw.user from mediawiki.js - // This is kind of ugly but we're stuck with this for b/c reasons - mw.user = new User( mw.user.options, mw.user.tokens ); + } + }; }( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.util.js b/resources/mediawiki/mediawiki.util.js index 5211b0d0..7383df2d 100644 --- a/resources/mediawiki/mediawiki.util.js +++ b/resources/mediawiki/mediawiki.util.js @@ -13,7 +13,7 @@ * (don't call before document ready) */ init: function () { - var profile, $tocTitle, $tocToggleLink, hideTocCookie; + var profile; /* Set tooltipAccessKeyPrefix */ profile = $.client.profile(); @@ -53,8 +53,9 @@ || profile.name === 'konqueror' ) ) { util.tooltipAccessKeyPrefix = 'ctrl-'; - // Firefox 2.x and later - } else if ( profile.name === 'firefox' && profile.versionBase > '1' ) { + // Firefox/Iceweasel 2.x and later + } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' ) + && profile.versionBase > '1' ) { util.tooltipAccessKeyPrefix = 'alt-shift-'; } @@ -105,29 +106,32 @@ } )(); // Table of contents toggle - $tocTitle = $( '#toctitle' ); - $tocToggleLink = $( '#togglelink' ); - // Only add it if there is a TOC and there is no toggle added already - if ( $( '#toc' ).length && $tocTitle.length && !$tocToggleLink.length ) { - hideTocCookie = $.cookie( 'mw_hidetoc' ); + mw.hook( 'wikipage.content' ).add( function () { + var $tocTitle, $tocToggleLink, hideTocCookie; + $tocTitle = $( '#toctitle' ); + $tocToggleLink = $( '#togglelink' ); + // Only add it if there is a TOC and there is no toggle added already + if ( $( '#toc' ).length && $tocTitle.length && !$tocToggleLink.length ) { + hideTocCookie = $.cookie( 'mw_hidetoc' ); $tocToggleLink = $( '<a href="#" class="internal" id="togglelink"></a>' ) .text( mw.msg( 'hidetoc' ) ) .click( function ( e ) { e.preventDefault(); util.toggleToc( $(this) ); } ); - $tocTitle.append( - $tocToggleLink - .wrap( '<span class="toctoggle"></span>' ) - .parent() - .prepend( ' [' ) - .append( '] ' ) - ); - - if ( hideTocCookie === '1' ) { - util.toggleToc( $tocToggleLink ); + $tocTitle.append( + $tocToggleLink + .wrap( '<span class="toctoggle"></span>' ) + .parent() + .prepend( ' [' ) + .append( '] ' ) + ); + + if ( hideTocCookie === '1' ) { + util.toggleToc( $tocToggleLink ); + } } - } + } ); }, /* Main body */ @@ -160,11 +164,18 @@ * Get the link to a page name (relative to `wgServer`), * * @param {string} str Page name to get the link for. + * @param {Object} params A mapping of query parameter names to values, + * e.g. { action: 'edit' }. Optional. * @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', + getUrl: function ( str, params ) { + var url = mw.config.get( 'wgArticlePath' ).replace( '$1', util.wikiUrlencode( typeof str === 'string' ? str : mw.config.get( 'wgPageName' ) ) ); + if ( params && !$.isEmptyObject( params ) ) { + url += url.indexOf( '?' ) !== -1 ? '&' : '?'; + url += $.param( params ); + } + return url; }, /** @@ -251,7 +262,7 @@ * Returns null if not found. * * @param {string} param The parameter name. - * @param {string} [url] URL to search through. + * @param {string} [url=document.location.href] URL to search through, defaulting to the current document's URL. * @return {Mixed} Parameter value or null. */ getParamValue: function ( param, url ) { @@ -279,8 +290,17 @@ /** * @property {RegExp} * Regex to match accesskey tooltips. + * + * Should match: + * + * - "ctrl-option-" + * - "alt-shift-" + * - "ctrl-alt-" + * - "ctrl-" + * + * The accesskey is matched in group $6. */ - tooltipAccessKeyRegexp: /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/, + tooltipAccessKeyRegexp: /\[(ctrl-)?(option-)?(alt-)?(shift-)?(esc-)?(.)\]$/, /** * Add the appropriate prefix to the accesskey shown in the tooltip. @@ -301,9 +321,9 @@ } $nodes.attr( 'title', function ( i, val ) { - if ( val && util.tooltipAccessKeyRegexp.exec( val ) ) { + if ( val && util.tooltipAccessKeyRegexp.test( val ) ) { return val.replace( util.tooltipAccessKeyRegexp, - '[' + util.tooltipAccessKeyPrefix + '$5]' ); + '[' + util.tooltipAccessKeyPrefix + '$6]' ); } return val; } ); @@ -364,87 +384,86 @@ $link.attr( 'title', tooltip ); } - // Some skins don't have any portlets - // just add it to the bottom of their 'sidebar' element as a fallback - switch ( mw.config.get( 'skin' ) ) { - case 'standard': - $( '#quickbar' ).append( $link.after( '<br/>' ) ); - return $link[0]; - case 'nostalgia': - $( '#searchform' ).before( $link ).before( ' | ' ); - return $link[0]; - default: // Skins like chick, modern, monobook, myskin, simple, vector... - - // Select the specified portlet - $portlet = $( '#' + portlet ); - if ( $portlet.length === 0 ) { - return null; - } - // Select the first (most likely only) unordered list inside the portlet - $ul = $portlet.find( 'ul' ).eq( 0 ); + // Select the specified portlet + $portlet = $( '#' + portlet ); + if ( $portlet.length === 0 ) { + return null; + } + // Select the first (most likely only) unordered list inside the portlet + $ul = $portlet.find( 'ul' ).eq( 0 ); - // If it didn't have an unordered list yet, create it - if ( $ul.length === 0 ) { + // If it didn't have an unordered list yet, create it + if ( $ul.length === 0 ) { - $ul = $( '<ul>' ); + $ul = $( '<ul>' ); - // If there's no <div> inside, append it to the portlet directly - if ( $portlet.find( 'div:first' ).length === 0 ) { - $portlet.append( $ul ); - } else { - // otherwise if there's a div (such as div.body or div.pBody) - // append the <ul> to last (most likely only) div - $portlet.find( 'div' ).eq( -1 ).append( $ul ); - } - } - // Just in case.. - if ( $ul.length === 0 ) { - return null; + // If there's no <div> inside, append it to the portlet directly + if ( $portlet.find( 'div:first' ).length === 0 ) { + $portlet.append( $ul ); + } else { + // otherwise if there's a div (such as div.body or div.pBody) + // append the <ul> to last (most likely only) div + $portlet.find( 'div' ).eq( -1 ).append( $ul ); } + } + // Just in case.. + if ( $ul.length === 0 ) { + return null; + } - // Unhide portlet if it was hidden before - $portlet.removeClass( 'emptyPortlet' ); + // Unhide portlet if it was hidden before + $portlet.removeClass( 'emptyPortlet' ); - // Wrap the anchor tag in a list item (and a span if $portlet is a Vector tab) - // and back up the selector to the list item - if ( $portlet.hasClass( 'vectorTabs' ) ) { - $item = $link.wrap( '<li><span></span></li>' ).parent().parent(); - } else { - $item = $link.wrap( '<li></li>' ).parent(); - } + // Wrap the anchor tag in a list item (and a span if $portlet is a Vector tab) + // and back up the selector to the list item + if ( $portlet.hasClass( 'vectorTabs' ) ) { + $item = $link.wrap( '<li><span></span></li>' ).parent().parent(); + } else { + $item = $link.wrap( '<li></li>' ).parent(); + } - // Implement the properties passed to the function - if ( id ) { - $item.attr( 'id', id ); - } + // Implement the properties passed to the function + if ( id ) { + $item.attr( 'id', id ); + } + + if ( tooltip ) { + // Trim any existing accesskey hint and the trailing space + tooltip = $.trim( tooltip.replace( util.tooltipAccessKeyRegexp, '' ) ); if ( accesskey ) { - $link.attr( 'accesskey', accesskey ); tooltip += ' [' + accesskey + ']'; - $link.attr( 'title', tooltip ); } - if ( accesskey && tooltip ) { + $link.attr( 'title', tooltip ); + if ( accesskey ) { util.updateTooltipAccessKeys( $link ); } + } - // Where to put our node ? - // - nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js) - if ( nextnode && nextnode.parentNode === $ul[0] ) { - $(nextnode).before( $item ); - - // - nextnode is a CSS selector for jQuery - } else if ( typeof nextnode === 'string' && $ul.find( nextnode ).length !== 0 ) { - $ul.find( nextnode ).eq( 0 ).before( $item ); + if ( accesskey ) { + $link.attr( 'accesskey', accesskey ); + } - // 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 behavior) - } else { + if ( nextnode ) { + if ( nextnode.nodeType || typeof nextnode === 'string' ) { + // nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js) + // or nextnode is a CSS selector for jQuery + nextnode = $ul.find( nextnode ); + } else if ( !nextnode.jquery || ( nextnode.length && nextnode[0].parentNode !== $ul[0] ) ) { + // Fallback $ul.append( $item ); + return $item[0]; } + if ( nextnode.length === 1 ) { + // nextnode is a jQuery object that represents exactly one element + nextnode.before( $item ); + return $item[0]; + } + } + // Fallback (this is the default behavior) + $ul.append( $item ); + return $item[0]; - return $item[0]; - } }, /** @@ -454,7 +473,7 @@ * * @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. - * @deprecated Use mw#notify + * @deprecated since 1.20 Use mw#notify */ jsMessage: function ( message ) { if ( !arguments.length || message === '' || message === null ) { @@ -593,6 +612,13 @@ } }; + /** + * @method wikiGetlink + * @inheritdoc #getUrl + * @deprecated since 1.23 Use #getUrl instead. + */ + mw.log.deprecate( util, 'wikiGetlink', util.getUrl, 'Use mw.util.getUrl instead.' ); + mw.util = util; }( mediaWiki, jQuery ) ); diff --git a/resources/startup.js b/resources/startup.js index deff7e6e..b6a27d2d 100644 --- a/resources/startup.js +++ b/resources/startup.js @@ -10,23 +10,39 @@ * This function will be deleted after it's used, so do not expand it to be * generally useful beyond startup. * - * MediaWiki & jQuery compatibility: - * - Internet Explorer 6.0+ - * - Firefox 10+ - * - Safari 5.0+ - * - Opera 11+ - * - Chrome + * See also: + * - https://www.mediawiki.org/wiki/Compatibility#Browser + * - http://jquerymobile.com/gbs/ + * - http://jquery.com/browser-support/ */ /*jshint unused: false */ -function isCompatible() { - // IE < 6.0 - if ( navigator.appVersion.indexOf( 'MSIE' ) !== -1 - && parseFloat( navigator.appVersion.split( 'MSIE' )[1] ) < 6 ) - { - return false; +function isCompatible( ua ) { + if ( ua === undefined ) { + ua = navigator.userAgent; } - return true; + + // MediaWiki JS or jQuery is known to have issues with: + return !( + // Internet Explorer < 6 + ( ua.indexOf( 'MSIE' ) !== -1 && parseFloat( ua.split( 'MSIE' )[1] ) < 6 ) || + // Firefox < 3 + ( ua.indexOf( 'Firefox/' ) !== -1 && parseFloat( ua.split( 'Firefox/' )[1] ) < 3 ) || + // BlackBerry < 6 + ua.match( /BlackBerry[^\/]*\/[1-5]\./ ) || + // Open WebOS < 1.5 + ua.match( /webOS\/1\.[0-4]/ ) || + // Anything PlayStation based. + ua.match( /PlayStation/i ) || + // Any Symbian based browsers + ua.match( /SymbianOS|Series60/ ) || + // Any NetFront based browser + ua.match( /NetFront/ ) || + // Opera Mini, all versions + ua.match( /Opera Mini/ ) || + // Nokia's Ovi Browser + ua.match( /S40OviBrowser/ ) + ); } /** |