rather than consume everything until the end of the page.
* New maintenance script resetUserEmail.php allows sysadmins to reset user emails in case
a user forgot password/account was stolen.
+* wfCheckEntropy() was removed (deprecated in 1.27).
== Compatibility ==
'LinksDeletionUpdate' => __DIR__ . '/includes/deferred/LinksDeletionUpdate.php',
'LinksUpdate' => __DIR__ . '/includes/deferred/LinksUpdate.php',
'ListDuplicatedFilesPage' => __DIR__ . '/includes/specials/SpecialListDuplicatedFiles.php',
+ 'ListToggle' => __DIR__ . '/includes/ListToggle.php',
'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
'LoadBalancer' => __DIR__ . '/includes/db/loadbalancer/LoadBalancer.php',
"ext-iconv": "*",
"liuggio/statsd-php-client": "1.0.18",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.15.4",
+ "oojs/oojs-ui": "0.16.0",
"oyejorge/less.php": "1.7.0.10",
"php": ">=5.5.9",
"psr/log": "1.0.0",
"monolog/monolog": "~1.17.2",
"nikic/php-parser": "1.4.1",
"nmred/kafka-php": "0.1.5",
- "phpunit/phpunit": "3.7.37",
+ "phpunit/phpunit": "4.8.23",
"wikimedia/avro": "1.7.7"
},
"suggest": {
/**
* Whether to use PHP session handling ($_SESSION and session_*() functions)
+ *
+ * If the constant MW_NO_SESSION is defined, this is forced to 'disable'.
+ *
+ * If the constant MW_NO_SESSION_HANDLER is defined, this is ignored and PHP
+ * session handling will function independently of SessionHandler.
+ * SessionHandler and PHP's session handling may attempt to override each
+ * others' cookies.
+ *
* @since 1.27
* @var string
* - 'enable': Integrate with PHP's session handling as much as possible.
return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
}
-/**
- * Check if there is sufficient entropy in php's built-in session generation
- *
- * @deprecated since 1.27, PHP's session generation isn't used with
- * MediaWiki\\Session\\SessionManager
- * @return bool True = there is sufficient entropy
- */
-function wfCheckEntropy() {
- wfDeprecated( __FUNCTION__, '1.27' );
- return (
- ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) )
- || ini_get( 'session.entropy_file' )
- )
- && intval( ini_get( 'session.entropy_length' ) ) >= 32;
-}
-
/**
* @deprecated since 1.27, PHP's session generation isn't used with
* MediaWiki\\Session\\SessionManager
function wfSetupSession( $sessionId = false ) {
wfDeprecated( __FUNCTION__, '1.27' );
- // If they're calling this, they probably want our session management even
- // if NO_SESSION was set for Setup.php.
- if ( !MediaWiki\Session\PHPSessionHandler::isInstalled() ) {
- MediaWiki\Session\PHPSessionHandler::install( SessionManager::singleton() );
- }
-
if ( $sessionId ) {
session_id( $sessionId );
}
--- /dev/null
+<?php
+/**
+ * Class for generating clickable toggle links for a list of checkboxes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class for generating clickable toggle links for a list of checkboxes.
+ *
+ * This is only supported on clients that have JavaScript enabled; it is hidden
+ * for clients that have it disabled.
+ *
+ * @since 1.27
+ */
+class ListToggle {
+ /** @var OutputPage */
+ private $output;
+
+ public function __construct( OutputPage $output ) {
+ $this->output = $output;
+
+ $output->addModules( 'mediawiki.checkboxtoggle' );
+ $output->addModuleStyles( 'mediawiki.checkboxtoggle.styles' );
+ }
+
+ private function checkboxLink( $checkboxType ) {
+ return Html::element(
+ 'a', [ 'href' => '#', 'class' => 'mw-checkbox-' . $checkboxType ],
+ $this->output->msg( 'checkbox-' . $checkboxType )->text()
+ );
+ }
+
+ /**
+ * @return string
+ */
+ public function getHTML() {
+ // Select: All, None, Invert
+ $links = [];
+ $links[] = $this->checkboxLink( 'all' );
+ $links[] = $this->checkboxLink( 'none' );
+ $links[] = $this->checkboxLink( 'invert' );
+
+ return Html::rawElement( 'div',
+ [
+ 'class' => 'mw-checkbox-toggle-controls'
+ ],
+ $this->output->msg( 'checkbox-select' )
+ ->rawParams( $this->output->getLanguage()->commaList( $links ) )->escaped()
+ );
+ }
+}
*/
public static function transformResourcePath( Config $config, $path ) {
global $IP;
- $remotePath = $config->get( 'ResourceBasePath' );
+ $remotePathPrefix = $config->get( 'ResourceBasePath' );
+ if ( $remotePathPrefix === '' ) {
+ // The configured base path is required to be empty string for
+ // wikis in the domain root
+ $remotePath = '/';
+ } else {
+ $remotePath = $remotePathPrefix;
+ }
if ( strpos( $path, $remotePath ) !== 0 ) {
// Path is outside wgResourceBasePath, ignore.
return $path;
}
$path = RelPath\getRelativePath( $path, $remotePath );
- return self::transformFilePath( $remotePath, $IP, $path );
+ return self::transformFilePath( $remotePathPrefix, $IP, $path );
}
/**
* Caller is responsible for ensuring the file exists. Emits a PHP warning otherwise.
*
* @since 1.27
- * @param string $remotePath URL path that points to $localPath
+ * @param string $remotePath URL path prefix that points to $localPath
* @param string $localPath File directory exposed at $remotePath
* @param string $file Path to target file relative to $localPath
* @return string URL
*/
- public static function transformFilePath( $remotePath, $localPath, $file ) {
+ public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
$hash = md5_file( "$localPath/$file" );
if ( $hash === false ) {
wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
$hash = '';
}
- return "$remotePath/$file?" . substr( $hash, 0, 5 );
+ return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
}
/**
) {
$wgPHPSessionHandling = 'warn';
}
+if ( defined( 'MW_NO_SESSION' ) ) {
+ // If the entry point wants no session, force 'disable' here unless they
+ // specifically set it to the (undocumented) 'warn'.
+ $wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
+}
Profiler::instance()->scopedProfileOut( $ps_default );
session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
}
- // Create the SessionManager singleton and set up our session handler
- MediaWiki\Session\PHPSessionHandler::install(
- MediaWiki\Session\SessionManager::singleton()
- );
+ // Create the SessionManager singleton and set up our session handler,
+ // unless we're specifically asked not to.
+ if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
+ MediaWiki\Session\PHPSessionHandler::install(
+ MediaWiki\Session\SessionManager::singleton()
+ );
+ }
// Initialize the session
try {
session_id( $session->getId() );
MediaWiki\quietCall( 'session_start' );
}
+
+ unset( $session );
+} else {
+ // Even if we didn't set up a global Session, still install our session
+ // handler unless specifically requested not to.
+ if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
+ MediaWiki\Session\PHPSessionHandler::install(
+ MediaWiki\Session\SessionManager::singleton()
+ );
+ }
}
Profiler::instance()->scopedProfileOut( $ps_session );
$this->buttons .= Xml::tags( 'div', [ 'class' =>
'mw-history-revisionactions' ], $actionButtons );
}
+
+ if ( $user->isAllowed( 'deleterevision' ) || $this->showTagEditUI ) {
+ $this->buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
+ }
+
$this->buttons .= '</div>';
$s .= $this->buttons;
* Override the necessary bits of the config to run an installation.
*/
public static function overrideConfig() {
- define( 'MW_NO_SESSION', 1 );
+ // Use PHP's built-in session handling, since MediaWiki's
+ // SessionHandler can't work before we have an object cache set up.
+ define( 'MW_NO_SESSION_HANDLER', 1 );
// Don't access the database
$GLOBALS['wgUseDatabaseMessages'] = false;
// Some of the environment checks make shell requests, remove limits
$GLOBALS['wgMaxShellMemory'] = 0;
+ // Override the default CookieSessionProvider with a dummy
+ // implementation that won't stomp on PHP's cookies.
$GLOBALS['wgSessionProviders'] = [
[
'class' => 'InstallerSessionProvider',
] ]
]
];
+
+ // Don't try to use any object cache for SessionManager either.
+ $GLOBALS['wgSessionCacheType'] = CACHE_NONE;
}
/**
protected function doGet( $key, $flags = 0 ) {
$ret = parent::doGet( $key, $flags );
- if ( $ret === false ) {
+ if ( $ret === false && !$this->hasKey( $key ) ) {
$ret = $this->backend->doGet( $key, $flags );
- if ( $ret !== false ) {
- $this->set( $key, $ret, 0, self::WRITE_CACHE_ONLY );
- }
+ $this->set( $key, $ret, 0, self::WRITE_CACHE_ONLY );
}
return $ret;
}
return true;
}
+ /**
+ * Does this bag have a non-null value for the given key?
+ *
+ * @param string $key
+ * @return bool
+ * @since 1.27
+ */
+ protected function hasKey( $key ) {
+ return isset( $this->bag[$key] );
+ }
+
protected function doGet( $key, $flags = 0 ) {
- if ( !isset( $this->bag[$key] ) ) {
+ if ( !$this->hasKey( $key ) ) {
return false;
}
return;
}
+ if ( defined( 'MW_NO_SESSION_HANDLER' ) ) {
+ throw new \BadMethodCallException( 'MW_NO_SESSION_HANDLER is defined' );
+ }
+
self::$instance = new self( $manager );
// Close any auto-started session, before we replace it
* @return Session
*/
public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
+ if ( defined( 'MW_NO_SESSION' ) ) {
+ if ( MW_NO_SESSION === 'warn' ) {
+ // Undocumented safety case for converting existing entry points
+ $this->logger->error( 'Sessions are supposed to be disabled for this entry point' );
+ } else {
+ throw new \BadMethodCallException( 'Sessions are disabled for this entry point' );
+ }
+ }
+
$id = $info->getId();
if ( !isset( $this->allSessionBackends[$id] ) ) {
) . "\n";
}
- // Select: All, None, Invert
- $links = [];
- $links[] = Html::element(
- 'a', [ 'href' => '#', 'class' => 'mw-checkbox-all' ],
- $this->msg( 'checkbox-all' )->text()
- );
- $links[] = Html::element(
- 'a', [ 'href' => '#', 'class' => 'mw-checkbox-none' ],
- $this->msg( 'checkbox-none' )->text()
- );
- $links[] = Html::element(
- 'a', [ 'href' => '#', 'class' => 'mw-checkbox-invert' ],
- $this->msg( 'checkbox-invert' )->text()
- );
-
- $buttons .= Html::rawElement( 'p',
- [
- 'class' => "mw-checkbox-toggle-controls"
- ],
- $this->msg( 'checkbox-select' )
- ->rawParams( $this->getLanguage()->commaList( $links ) )->escaped()
- );
-
- $this->getOutput()->addModules( 'mediawiki.checkboxtoggle' );
- $this->getOutput()->addModuleStyles( 'mediawiki.checkboxtoggle.styles' );
+ $buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
$s .= $buttons . $formcontents . $buttons;
$s .= Html::closeElement( 'form' );
$this->mOptionOverrides = null;
$this->mOptionsLoaded = false;
- $loggedOut = $this->mRequest ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
+ $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
+ ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
if ( $loggedOut !== 0 ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
if ( is_null( $this->mRights ) ) {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
- $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
- if ( $allowedRights !== null ) {
- $this->mRights = array_intersect( $this->mRights, $allowedRights );
+ // Deny any rights denied by the user's session, unless this
+ // endpoint has no sessions.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+ $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
+ if ( $allowedRights !== null ) {
+ $this->mRights = array_intersect( $this->mRights, $allowedRights );
+ }
}
Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
}
}
- // Remove any rights that aren't allowed to the global-session user
- $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
- if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
- $cache[$right] = false;
- return false;
+ // Remove any rights that aren't allowed to the global-session user,
+ // unless there are no sessions for this endpoint.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+ $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
+ if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
+ $cache[$right] = false;
+ return false;
+ }
}
// Allow extensions to say false
"grunt-cli": "0.1.13",
"grunt-banana-checker": "0.4.0",
"grunt-contrib-copy": "0.8.2",
- "grunt-contrib-jshint": "0.12.0",
+ "grunt-contrib-jshint": "1.0.0",
"grunt-contrib-watch": "0.6.1",
"grunt-jscs": "2.7.0",
"grunt-jsonlint": "1.0.7",
--- /dev/null
+{
+ "@metadata": {
+ "authors": [
+ "Luuva"
+ ]
+ },
+ "ooui-dialog-message-accept": "Liáu-kái",
+ "ooui-dialog-message-reject": "Chhú-siau",
+ "ooui-dialog-process-error": "Ū mi̍h bô hó-sè",
+ "ooui-dialog-process-dismiss": "Koaiⁿ tiāu",
+ "ooui-dialog-process-retry": "Koh chhì khòaⁿ-māi",
+ "ooui-dialog-process-continue": "Kè-sio̍k",
+ "ooui-selectfile-button-select": "Soán-tek 1-ê tóng-àn",
+ "ooui-selectfile-not-supported": "Só͘ soán ê tóng-àn bô siū chi-chhî",
+ "ooui-selectfile-placeholder": "Iáu-bē soán tóng-àn",
+ "ooui-selectfile-dragdrop-placeholder": "Kā tóng-àn tàn chia"
+}
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-element-hidden {
display: none !important;
.oo-ui-indicatorElement.oo-ui-indicatorElement-indicator {
opacity: 0.8;
}
+.oo-ui-labelElement .oo-ui-labelElement-label-highlight {
+ font-weight: bold;
+}
.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
}
width: 100%;
max-width: 50em;
}
+.oo-ui-dropdownInputWidget .oo-ui-dropdownWidget,
+.oo-ui-dropdownInputWidget select {
+ display: block;
+}
.oo-ui-dropdownInputWidget select {
- display: inline-block;
width: 100%;
resize: none;
-webkit-box-sizing: border-box;
}
.oo-ui-textInputWidget input,
.oo-ui-textInputWidget textarea {
- display: inline-block;
+ display: block;
width: 100%;
resize: none;
-webkit-box-sizing: border-box;
}
.oo-ui-dropdownWidget-handle {
width: 100%;
- display: inline-block;
+ display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: default;
opacity: 0.2;
}
-.oo-ui-comboBoxInputWidget > .oo-ui-selectWidget {
- margin-top: -3px;
-}
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-element-hidden {
display: none !important;
background-position: center center;
background-repeat: no-repeat;
}
+.oo-ui-labelElement .oo-ui-labelElement-label-highlight {
+ font-weight: bold;
+}
.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/mediawiki/images/textures/pending.gif);
}
width: 100%;
max-width: 50em;
}
+.oo-ui-dropdownInputWidget .oo-ui-dropdownWidget,
+.oo-ui-dropdownInputWidget select {
+ display: block;
+}
.oo-ui-dropdownInputWidget select {
- display: inline-block;
width: 100%;
resize: none;
-webkit-box-sizing: border-box;
}
.oo-ui-textInputWidget input,
.oo-ui-textInputWidget textarea {
- display: inline-block;
+ display: block;
width: 100%;
resize: none;
-webkit-box-sizing: border-box;
}
.oo-ui-dropdownWidget-handle {
width: 100%;
- display: inline-block;
+ display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
margin-right: 2em;
}
-.oo-ui-dropdownWidget .oo-ui-selectWidget {
- border-top-color: #ffffff;
-}
.oo-ui-comboBoxInputWidget {
display: inline-block;
position: relative;
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
if ( immediate && !timeout ) {
func.apply( context, args );
}
- clearTimeout( timeout );
- timeout = setTimeout( later, wait );
+ if ( !timeout || wait ) {
+ clearTimeout( timeout );
+ timeout = setTimeout( later, wait );
+ }
};
};
* @param {HTMLElement} node
* @param {string} eventName
* @param {Function} handler
- * @deprecated
+ * @deprecated since 0.15.0
*/
OO.ui.addCaptureEventListener = function ( node, eventName, handler ) {
node.addEventListener( eventName, handler, true );
* @param {HTMLElement} node
* @param {string} eventName
* @param {Function} handler
- * @deprecated
+ * @deprecated since 0.15.0
*/
OO.ui.removeCaptureEventListener = function ( node, eventName, handler ) {
node.removeEventListener( eventName, handler, true );
* as a plaintext string, a jQuery selection of elements, or a function that will produce a string
* in the future. See the [OOjs UI documentation on MediaWiki] [2] for examples.
* [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
- * @cfg {boolean} [autoFitLabel=true] Fit the label to the width of the parent element.
- * The label will be truncated to fit if necessary.
*/
OO.ui.mixin.LabelElement = function OoUiMixinLabelElement( config ) {
// Configuration initialization
// Properties
this.$label = null;
this.label = null;
- this.autoFitLabel = config.autoFitLabel === undefined || !!config.autoFitLabel;
// Initialization
this.setLabel( config.label || this.constructor.static.label );
*/
OO.ui.mixin.LabelElement.static.label = null;
+/* Static methods */
+
+/**
+ * Highlight the first occurrence of the query in the given text
+ *
+ * @param {string} text Text
+ * @param {string} query Query to find
+ * @return {jQuery} Text with the first match of the query
+ * sub-string wrapped in highlighted span
+ */
+OO.ui.mixin.LabelElement.static.highlightQuery = function ( text, query ) {
+ var $result = $( '<span>' ),
+ offset = text.toLowerCase().indexOf( query.toLowerCase() );
+
+ if ( !query.length || offset === -1 ) {
+ return $result.text( text );
+ }
+ $result.append(
+ document.createTextNode( text.slice( 0, offset ) ),
+ $( '<span>' )
+ .addClass( 'oo-ui-labelElement-label-highlight' )
+ .text( text.slice( offset, offset + query.length ) ),
+ document.createTextNode( text.slice( offset + query.length ) )
+ );
+ return $result.contents();
+};
+
/* Methods */
/**
return this;
};
+/**
+ * Set the label as plain text with a highlighted query
+ *
+ * @param {string} text Text label to set
+ * @param {string} query Substring of text to highlight
+ * @chainable
+ */
+OO.ui.mixin.LabelElement.prototype.setHighlightedQuery = function ( text, query ) {
+ return this.setLabel( this.constructor.static.highlightQuery( text, query ) );
+};
+
/**
* Get the label.
*
* Fit the label.
*
* @chainable
+ * @deprecated since 0.16.0
*/
OO.ui.mixin.LabelElement.prototype.fitLabel = function () {
- if ( this.$label && this.$label.autoEllipsis && this.autoFitLabel ) {
- this.$label.autoEllipsis( { hasSpan: false, tooltip: true } );
- }
-
return this;
};
OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
// Properties
- this.newItems = null;
this.autoHide = config.autoHide === undefined || !!config.autoHide;
this.filterFromInput = !!config.filterFromInput;
this.$input = config.$input ? config.$input : config.input ? config.input.$input : null;
* @inheritdoc
*/
OO.ui.MenuSelectWidget.prototype.addItems = function ( items, index ) {
- var i, len, item;
-
// Parent method
OO.ui.MenuSelectWidget.parent.prototype.addItems.call( this, items, index );
- // Auto-initialize
- if ( !this.newItems ) {
- this.newItems = [];
- }
-
- for ( i = 0, len = items.length; i < len; i++ ) {
- item = items[ i ];
- if ( this.isVisible() ) {
- // Defer fitting label until item has been attached
- item.fitLabel();
- } else {
- this.newItems.push( item );
- }
- }
-
// Reevaluate clipping
this.clip();
* @inheritdoc
*/
OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
- var i, len, change;
+ var change;
visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length;
change = visible !== this.isVisible();
this.bindKeyDownListener();
this.bindKeyPressListener();
- if ( this.newItems && this.newItems.length ) {
- for ( i = 0, len = this.newItems.length; i < len; i++ ) {
- this.newItems[ i ].fitLabel();
- }
- this.newItems = null;
- }
this.toggleClipping( true );
if ( this.getSelectedItem() ) {
/**
* Set the directionality of the input, either RTL (right-to-left) or LTR (left-to-right).
*
- * @deprecated since v0.13.1, use #setDir directly
+ * @deprecated since v0.13.1; use #setDir directly
* @param {boolean} isRTL Directionality is right-to-left
* @chainable
*/
* @protected
*/
OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
- return $( '<input type="checkbox" />' );
+ return $( '<input>' ).attr( 'type', 'checkbox' );
};
/**
if ( config.$input ) {
return config.$input.addClass( 'oo-ui-element-hidden' );
}
- return $( '<input type="hidden">' );
+ return $( '<input>' ).attr( 'type', 'hidden' );
};
/**
* @protected
*/
OO.ui.RadioInputWidget.prototype.getInputElement = function () {
- return $( '<input type="radio" />' );
+ return $( '<input>' ).attr( 'type', 'radio' );
};
/**
* @protected
*/
OO.ui.RadioSelectInputWidget.prototype.getInputElement = function () {
- return $( '<input type="hidden">' );
+ return $( '<input>' ).attr( 'type', 'hidden' );
};
/**
OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) {
return config.multiline ?
$( '<textarea>' ) :
- $( '<input type="' + this.getSaneType( config ) + '" />' );
+ $( '<input>' ).attr( 'type', this.getSaneType( config ) );
};
/**
* This method returns a promise that resolves with a boolean `true` if the current value is
* considered valid according to the supplied {@link #validate validation pattern}.
*
- * @deprecated
+ * @deprecated since v0.12.3
* @return {jQuery.Promise} A promise that resolves to a boolean `true` if the value is valid.
*/
OO.ui.TextInputWidget.prototype.isValid = function () {
*/
OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
this.labelPosition = labelPosition;
- this.updatePosition();
+ if ( this.label ) {
+ // If there is no label and we only change the position, #updatePosition is a no-op,
+ // but it takes really a lot of work to do nothing.
+ this.updatePosition();
+ }
return this;
};
/**
* @class
- * @deprecated Use OO.ui.ComboBoxInputWidget instead.
+ * @deprecated since 0.13.2; use OO.ui.ComboBoxInputWidget instead
*/
OO.ui.ComboBoxWidget = OO.ui.ComboBoxInputWidget;
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
-.oo-ui-draggableElement {
- cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
+.oo-ui-draggableElement-handle.oo-ui-widget-enabled {
+ cursor: move;
+ cursor: url(images/grab.cur );
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+}
+.oo-ui-draggableElement-placeholder {
+ opacity: 0.2;
}
-.oo-ui-draggableElement-dragging {
- cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
- background: rgba(0, 0, 0, 0.2);
- opacity: 0.4;
+.oo-ui-draggableElement.oo-ui-widget-enabled:active {
+ cursor: move;
+ cursor: url(images/grabbing.cur );
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ cursor: grabbing;
}
.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
display: inline-block;
}
-.oo-ui-draggableGroupElement-placeholder {
- position: absolute;
- display: block;
- background: rgba(0, 0, 0, 0.4);
-}
.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
z-index: 1;
width: 100%;
}
.oo-ui-capsuleMultiSelectWidget-handle {
width: 100%;
- display: inline-block;
+ display: block;
position: relative;
}
.oo-ui-capsuleMultiSelectWidget-content {
.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
opacity: 0.2;
}
-.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
- border-top-color: #ffffff;
-}
.oo-ui-capsuleItemWidget {
position: relative;
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
}
-.oo-ui-capsuleItemWidget .oo-ui-buttonElement {
- margin-top: -1.6em;
- padding-left: 0.3em;
-}
.oo-ui-capsuleItemWidget:focus {
outline: none;
border-color: #087ecc;
}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
- padding-right: 1.3375em;
-}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
- position: absolute;
- right: 0.4em;
- top: 0;
- width: 0.9375em;
- height: 100%;
- background-repeat: no-repeat;
-}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
- cursor: pointer;
-}
.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
opacity: 0.5;
-webkit-transform: translate3d(0, 0, 0);
background: #eeeeee;
border-color: #cccccc;
}
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+.oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
+ float: right;
+ margin-top: -0.1em;
+ margin-right: -0.4em;
}
.oo-ui-searchWidget-query {
position: absolute;
.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
width: 2.25em;
}
-.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right-width: 0;
}
-.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left-width: 0;
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
-.oo-ui-draggableElement {
- cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
+.oo-ui-draggableElement-handle.oo-ui-widget-enabled {
+ cursor: move;
+ cursor: url(images/grab.cur );
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+}
+.oo-ui-draggableElement-placeholder {
+ opacity: 0.2;
}
-.oo-ui-draggableElement-dragging {
- cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
- background: rgba(0, 0, 0, 0.2);
- opacity: 0.4;
+.oo-ui-draggableElement.oo-ui-widget-enabled:active {
+ cursor: move;
+ cursor: url(images/grabbing.cur );
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ cursor: grabbing;
}
.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
display: inline-block;
}
-.oo-ui-draggableGroupElement-placeholder {
- position: absolute;
- display: block;
- background: rgba(0, 0, 0, 0.4);
-}
.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
z-index: 1;
width: 100%;
}
.oo-ui-capsuleMultiSelectWidget-handle {
width: 100%;
- display: inline-block;
+ display: block;
position: relative;
}
.oo-ui-capsuleMultiSelectWidget-content {
.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
opacity: 0.2;
}
-.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
- border-top-color: #ffffff;
-}
.oo-ui-capsuleItemWidget {
position: relative;
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
}
-.oo-ui-capsuleItemWidget .oo-ui-buttonElement {
- margin-top: -1.6em;
- padding-left: 0.3em;
-}
.oo-ui-capsuleItemWidget:focus {
outline: none;
border-color: #347bff;
}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
- padding-right: 1.3375em;
-}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
- position: absolute;
- right: 0.4em;
- top: 0;
- width: 0.9375em;
- height: 100%;
- background-repeat: no-repeat;
-}
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
- cursor: pointer;
-}
.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
color: #cccccc;
text-shadow: 0 1px 1px #ffffff;
border-color: #dddddd;
background-color: #f3f3f3;
}
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+.oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
+ float: right;
+ margin-top: -0.2em;
+ margin-left: 0.4em;
}
.oo-ui-searchWidget-query {
position: absolute;
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
* @class
*
* @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$handle] The part of the element which can be used for dragging, defaults to the whole element
*/
-OO.ui.mixin.DraggableElement = function OoUiMixinDraggableElement() {
+OO.ui.mixin.DraggableElement = function OoUiMixinDraggableElement( config ) {
+ config = config || {};
+
// Properties
this.index = null;
+ this.$handle = config.$handle || this.$element;
+ this.wasHandleUsed = null;
// Initialize and events
- this.$element
+ this.$element.addClass( 'oo-ui-draggableElement' )
+ // We make the entire element draggable, not just the handle, so that
+ // the whole element appears to move. wasHandleUsed prevents drags from
+ // starting outside the handle
.attr( 'draggable', true )
- .addClass( 'oo-ui-draggableElement' )
.on( {
+ mousedown: this.onDragMouseDown.bind( this ),
dragstart: this.onDragStart.bind( this ),
dragover: this.onDragOver.bind( this ),
dragend: this.onDragEnd.bind( this ),
drop: this.onDrop.bind( this )
} );
+ this.$handle.addClass( 'oo-ui-draggableElement-handle' );
};
OO.initClass( OO.ui.mixin.DraggableElement );
/* Methods */
+/**
+ * Respond to mousedown event.
+ *
+ * @private
+ * @param {jQuery.Event} event jQuery event
+ */
+OO.ui.mixin.DraggableElement.prototype.onDragMouseDown = function ( e ) {
+ this.wasHandleUsed =
+ // Optimization: if the handle is the whole element this is always true
+ this.$handle[ 0 ] === this.$element[ 0 ] ||
+ // Check the mousedown occurred inside the handle
+ OO.ui.contains( this.$handle[ 0 ], e.target, true );
+};
+
/**
* Respond to dragstart event.
*
* @fires dragstart
*/
OO.ui.mixin.DraggableElement.prototype.onDragStart = function ( e ) {
- var dataTransfer = e.originalEvent.dataTransfer;
+ var element = this,
+ dataTransfer = e.originalEvent.dataTransfer;
+
+ if ( !this.wasHandleUsed ) {
+ return false;
+ }
+
// Define drop effect
dataTransfer.dropEffect = 'none';
dataTransfer.effectAllowed = 'move';
} catch ( err ) {
// The above is only for Firefox. Move on if it fails.
}
- // Add dragging class
- this.$element.addClass( 'oo-ui-draggableElement-dragging' );
+ // Briefly add a 'clone' class to style the browser's native drag image
+ this.$element.addClass( 'oo-ui-draggableElement-clone' );
+ // Add placeholder class after the browser has rendered the clone
+ setTimeout( function () {
+ element.$element
+ .removeClass( 'oo-ui-draggableElement-clone' )
+ .addClass( 'oo-ui-draggableElement-placeholder' );
+ } );
// Emit event
this.emit( 'dragstart', this );
return true;
* @fires dragend
*/
OO.ui.mixin.DraggableElement.prototype.onDragEnd = function () {
- this.$element.removeClass( 'oo-ui-draggableElement-dragging' );
+ this.$element.removeClass( 'oo-ui-draggableElement-placeholder' );
this.emit( 'dragend' );
};
// Properties
this.orientation = config.orientation || 'vertical';
this.dragItem = null;
- this.itemDragOver = null;
this.itemKeys = {};
- this.sideInsertion = '';
+ this.dir = null;
+ this.itemsOrder = null;
// Events
this.aggregate( {
} );
this.connect( this, {
itemDragStart: 'onItemDragStart',
- itemDrop: 'onItemDrop',
- itemDragEnd: 'onItemDragEnd'
- } );
- this.$element.on( {
- dragover: this.onDragOver.bind( this ),
- dragleave: this.onDragLeave.bind( this )
+ itemDrop: 'onItemDropOrDragEnd',
+ itemDragEnd: 'onItemDropOrDragEnd'
} );
// Initialize
if ( Array.isArray( config.items ) ) {
this.addItems( config.items );
}
- this.$placeholder = $( '<div>' )
- .addClass( 'oo-ui-draggableGroupElement-placeholder' );
this.$element
.addClass( 'oo-ui-draggableGroupElement' )
.append( this.$status )
- .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' )
- .prepend( this.$placeholder );
+ .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' );
};
/* Setup */
/* Events */
/**
- * A 'reorder' event is emitted when the order of items in the group changes.
+ * An item has been dragged to a new position, but not yet dropped.
+ *
+ * @event drag
+ * @param {OO.ui.mixin.DraggableElement} item Dragged item
+ * @param {number} [newIndex] New index for the item
+ */
+
+/**
+ * And item has been dropped at a new position.
*
* @event reorder
* @param {OO.ui.mixin.DraggableElement} item Reordered item
* @param {OO.ui.mixin.DraggableElement} item Dragged item
*/
OO.ui.mixin.DraggableGroupElement.prototype.onItemDragStart = function ( item ) {
- var i, len;
-
- // Map the index of each object
- for ( i = 0, len = this.items.length; i < len; i++ ) {
- this.items[ i ].setIndex( i );
- }
-
+ // Make a shallow copy of this.items so we can re-order it during previews
+ // without affecting the original array.
+ this.itemsOrder = this.items.slice();
+ this.updateIndexes();
if ( this.orientation === 'horizontal' ) {
- // Set the height of the indicator
- this.$placeholder.css( {
- height: item.$element.outerHeight(),
- width: 2
- } );
- } else {
- // Set the width of the indicator
- this.$placeholder.css( {
- height: 2,
- width: item.$element.outerWidth()
- } );
+ // Calculate and cache directionality on drag start - it's a little
+ // expensive and it shouldn't change while dragging.
+ this.dir = this.$element.css( 'direction' );
}
this.setDragItem( item );
};
/**
- * Respond to item drag end event
- *
- * @private
+ * Update the index properties of the items
*/
-OO.ui.mixin.DraggableGroupElement.prototype.onItemDragEnd = function () {
- this.unsetDragItem();
- return false;
+OO.ui.mixin.DraggableGroupElement.prototype.updateIndexes = function () {
+ var i, len;
+
+ // Map the index of each object
+ for ( i = 0, len = this.itemsOrder.length; i < len; i++ ) {
+ this.itemsOrder[ i ].setIndex( i );
+ }
};
/**
- * Handle drop event and switch the order of the items accordingly
+ * Handle drop or dragend event and switch the order of the items accordingly
*
* @private
* @param {OO.ui.mixin.DraggableElement} item Dropped item
- * @fires reorder
*/
-OO.ui.mixin.DraggableGroupElement.prototype.onItemDrop = function ( item ) {
- var toIndex = item.getIndex();
- // Check if the dropped item is from the current group
+OO.ui.mixin.DraggableGroupElement.prototype.onItemDropOrDragEnd = function () {
+ var targetIndex, originalIndex,
+ item = this.getDragItem();
+
// TODO: Figure out a way to configure a list of legally droppable
// elements even if they are not yet in the list
- if ( this.getDragItem() ) {
- // If the insertion point is 'after', the insertion index
- // is shifted to the right (or to the left in RTL, hence 'after')
- if ( this.sideInsertion === 'after' ) {
- toIndex++;
- }
- // Emit change event
- this.emit( 'reorder', this.getDragItem(), toIndex );
+ if ( item ) {
+ originalIndex = this.items.indexOf( item );
+ // If the item has moved forward, add one to the index to account for the left shift
+ targetIndex = item.getIndex() + ( item.getIndex() > originalIndex ? 1 : 0 );
+ this.reorder( this.getDragItem(), targetIndex );
+ this.emit( 'reorder', this.getDragItem(), targetIndex );
+ this.updateIndexes();
}
this.unsetDragItem();
// Return false to prevent propogation
return false;
};
-/**
- * Handle dragleave event.
- *
- * @private
- */
-OO.ui.mixin.DraggableGroupElement.prototype.onDragLeave = function () {
- // This means the item was dragged outside the widget
- this.$placeholder
- .css( 'left', 0 )
- .addClass( 'oo-ui-element-hidden' );
-};
-
/**
* Respond to dragover event
*
* @private
- * @param {jQuery.Event} event Event details
+ * @param {jQuery.Event} event Dragover event
+ * @fires reorder
*/
OO.ui.mixin.DraggableGroupElement.prototype.onDragOver = function ( e ) {
var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect,
- itemSize, cssOutput, dragPosition, itemIndex, itemPosition,
+ itemSize, cssOutput, dragPosition, overIndex, itemPosition, after,
+ targetIndex = null,
+ item = this.getDragItem(),
+ dragItemIndex = item.getIndex(),
clientX = e.originalEvent.clientX,
clientY = e.originalEvent.clientY;
itemOffset = $optionWidget.offset();
itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect();
itemPosition = $optionWidget.position();
- itemIndex = $optionWidget.data( 'index' );
+ overIndex = $optionWidget.data( 'index' );
}
if (
itemOffset &&
- this.isDragging() &&
- itemIndex !== this.getDragItem().getIndex()
+ overIndex !== dragItemIndex
) {
if ( this.orientation === 'horizontal' ) {
// Calculate where the mouse is relative to the item width
itemMidpoint = itemBoundingRect.left + itemSize / 2;
dragPosition = clientX;
// Which side of the item we hover over will dictate
- // where the placeholder will appear, on the left or
+ // where to drop the selected item, on the left or
// on the right
cssOutput = {
left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize,
itemMidpoint = itemBoundingRect.top + itemSize / 2;
dragPosition = clientY;
// Which side of the item we hover over will dictate
- // where the placeholder will appear, on the top or
+ // where to drop the selected item, on the top or
// on the bottom
cssOutput = {
top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize,
}
// Store whether we are before or after an item to rearrange
// For horizontal layout, we need to account for RTL, as this is flipped
- if ( this.orientation === 'horizontal' && this.$element.css( 'direction' ) === 'rtl' ) {
- this.sideInsertion = dragPosition < itemMidpoint ? 'after' : 'before';
+ if ( this.orientation === 'horizontal' && this.dir === 'rtl' ) {
+ after = dragPosition < itemMidpoint;
} else {
- this.sideInsertion = dragPosition < itemMidpoint ? 'before' : 'after';
+ after = dragPosition > itemMidpoint;
+ }
+ targetIndex = overIndex + ( after ? 1 : 0 );
+ // Check the targetIndex isn't immediately to the left or right of the current item (a no-op)
+ if ( targetIndex === dragItemIndex || targetIndex === dragItemIndex + 1 ) {
+ targetIndex = null;
}
- // Add drop indicator between objects
- this.$placeholder
- .css( cssOutput )
- .removeClass( 'oo-ui-element-hidden' );
- } else {
- // This means the item was dragged outside the widget
- this.$placeholder
- .css( 'left', 0 )
- .addClass( 'oo-ui-element-hidden' );
+ }
+ if ( targetIndex !== null ) {
+ if ( targetIndex > 0 ) {
+ this.$group.children().eq( targetIndex - 1 ).after( item.$element );
+ } else {
+ this.$group.prepend( item.$element );
+ }
+ // Move item in itemsOrder array. Needs to account for left shift if the item is moved forward.
+ this.itemsOrder.splice( targetIndex - ( targetIndex > dragItemIndex ? 1 : 0 ), 0,
+ this.itemsOrder.splice( dragItemIndex, 1 )[ 0 ]
+ );
+ this.updateIndexes();
+ this.emit( 'drag', item, targetIndex );
}
// Prevent default
e.preventDefault();
};
+/**
+ * Reorder the items in the group
+ *
+ * @param {OO.ui.mixin.DraggableElement} item Reordered item
+ * @param {number} newIndex New index
+ */
+OO.ui.mixin.DraggableGroupElement.prototype.reorder = function ( item, newIndex ) {
+ this.addItems( [ item ], newIndex );
+};
+
/**
* Set a dragged item
*
*/
OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) {
this.dragItem = item;
+ this.$element.on( 'dragover', this.onDragOver.bind( this ) );
+ this.$element.addClass( 'oo-ui-draggableGroupElement-dragging' );
};
/**
*/
OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () {
this.dragItem = null;
- this.itemDragOver = null;
- this.$placeholder.addClass( 'oo-ui-element-hidden' );
- this.sideInsertion = '';
+ this.$element.off( 'dragover' );
+ this.$element.removeClass( 'oo-ui-draggableGroupElement-dragging' );
};
/**
return this.dragItem;
};
-/**
- * Check if an item in the group is currently being dragged.
- *
- * @return {Boolean} Item is being dragged
- */
-OO.ui.mixin.DraggableGroupElement.prototype.isDragging = function () {
- return this.getDragItem() !== null;
-};
-
/**
* RequestManager is a mixin that manages the lifecycle of a promise-backed request for a widget, such as
* the {@link OO.ui.mixin.LookupElement}.
* @cfg {string} [notsupported] Text to display when file support is missing in the browser.
* @cfg {boolean} [droppable=true] Whether to accept files by drag and drop.
* @cfg {boolean} [showDropTarget=false] Whether to show a drop target. Requires droppable to be true.
- * @cfg {boolean} [dragDropUI=false] Deprecated alias for showDropTarget
* @cfg {Number} [thumbnailSizeLimit=20] File size limit in MiB above which to not try and show a
* preview (for performance)
*/
OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
var dragHandler;
- // TODO: Remove in next release
- if ( config && config.dragDropUI ) {
- config.showDropTarget = true;
- }
-
// Configuration initialization
config = $.extend( {
accept: null,
OO.ui.mixin.IconElement.call( this, config );
OO.ui.mixin.IndicatorElement.call( this, config );
OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$info } ) );
- OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { autoFitLabel: true } ) );
+ OO.ui.mixin.LabelElement.call( this, config );
// Properties
this.$info = $( '<span>' );
OO.ui.SelectFileWidget.static.isSupported = function () {
var $input;
if ( OO.ui.SelectFileWidget.static.isSupportedCache === null ) {
- $input = $( '<input type="file">' );
+ $input = $( '<input>' ).attr( 'type', 'file' );
OO.ui.SelectFileWidget.static.isSupportedCache = $input[ 0 ].files !== undefined;
}
return OO.ui.SelectFileWidget.static.isSupportedCache;
return;
}
- this.$input = $( '<input type="file">' );
+ this.$input = $( '<input>' ).attr( 'type', 'file' );
this.$input.on( 'change', this.onFileSelectedHandler );
this.$input.on( 'click', function ( e ) {
// Prevents dropTarget to get clicked which calls
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-actionWidget.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:27Z
+ * Date: 2016-02-22T22:33:37Z
*/
.oo-ui-window {
background: transparent;
/*!
- * OOjs UI v0.15.4
+ * OOjs UI v0.16.0
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-02-17T02:03:23Z
+ * Date: 2016-02-22T22:33:33Z
*/
( function ( OO ) {
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
- <path d="M12 8C7 8 1 14 1 14s6 6 11 6l11-6s-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+ <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
<circle cx="12" cy="14" r="2"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M12 8C7 8 1 14 1 14s6 6 11 6l11-6s-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+ <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
<circle cx="12" cy="14" r="2"/>
</svg>
$wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
$wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
$wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
- $parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
+ $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
$wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
$wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
+ $wgScriptPath = '';
$wgScript = '/index.php';
- $wgScriptPath = '/';
- $wgArticlePath = '/wiki/$1';
$wgStylePath = '/skins';
+ $wgResourceBasePath = '';
$wgExtensionAssetsPath = '/extensions';
+ $wgArticlePath = '/wiki/$1';
$wgThumbnailScriptPath = false;
$wgLockManagers = [ [
'name' => 'fsLockManager',
!! wikitext
{{SCRIPTPATH}}
!! html
-<p>/
-</p>
+
!! end
!! test
* @ingroup Testing
*/
-$otions = [ 'quick', 'color', 'quiet', 'help', 'show-output',
+$options = [ 'quick', 'color', 'quiet', 'help', 'show-output',
'record', 'run-disabled', 'run-parsoid' ];
-$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion' ];
+$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion', 'file' ];
require_once __DIR__ . '/../maintenance/commandLine.inc';
require_once __DIR__ . '/TestsAutoLoader.php';
class LegacyLoggerTest extends MediaWikiTestCase {
/**
- * @covers LegacyLogger::interpolate
+ * @covers MediaWiki\Logger\LegacyLogger::interpolate
* @dataProvider provideInterpolate
*/
public function testInterpolate( $message, $context, $expect ) {
}
/**
- * @covers LegacyLogger::shouldEmit
+ * @covers MediaWiki\Logger\LegacyLogger::shouldEmit
* @dataProvider provideShouldEmit
*/
public function testShouldEmit( $level, $config, $expected ) {
class MonologSpiTest extends MediaWikiTestCase {
/**
- * @covers MonologSpi::mergeConfig
+ * @covers MediaWiki\Logger\MonologSpi::mergeConfig
*/
public function testMergeConfig() {
$base = [
}
/**
- * @covers LineFormatter::normalizeException
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
*/
public function testNormalizeExceptionNoTrace() {
$fixture = new LineFormatter();
}
/**
- * @covers LineFormatter::normalizeException
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
*/
public function testNormalizeExceptionTrace() {
$fixture = new LineFormatter();
}
/**
- * @dataProvider provider_testSanitzeHdrs
- * @covers SwiftFileBackend::sanitzeHdrs
+ * @dataProvider provider_testSanitizeHdrs
+ * @covers SwiftFileBackend::sanitizeHdrs
* @covers SwiftFileBackend::getCustomHeaders
*/
- public function testSanitzeHdrs( $raw, $sanitized ) {
+ public function testSanitizeHdrs( $raw, $sanitized ) {
$hdrs = $this->backend->sanitizeHdrs( [ 'headers' => $raw ] );
$this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' );
}
- public static function provider_testSanitzeHdrs() {
+ public static function provider_testSanitizeHdrs() {
return [
[
[
/**
* @covers ProcessCacheLRU::get
* @covers ProcessCacheLRU::set
- * @covers ProcessCacheLRU::het
+ * @covers ProcessCacheLRU::has
*/
public function testAddAndGetAKey() {
$oneCache = new ProcessCacheLRUTestable( 1 );
/**
* @covers ProcessCacheLRU::get
* @covers ProcessCacheLRU::set
- * @covers ProcessCacheLRU::het
+ * @covers ProcessCacheLRU::has
*/
public function testRecentlyAccessedKeyStickIn() {
$cache = new ProcessCacheLRUTestable( 2 );
$cache->delete( 'foo', CachedBagOStuff::WRITE_CACHE_ONLY );
$this->assertEquals( 'old', $cache->get( 'foo' ) ); // Reloaded from backend
}
+
+ public function testCacheBackendMisses() {
+ $backend = new HashBagOStuff;
+ $cache = new CachedBagOStuff( $backend );
+
+ // First hit primes the cache with miss from the backend
+ $this->assertEquals( false, $cache->get( 'foo' ) );
+
+ // Change the value in the backend
+ $backend->set( 'foo', true );
+
+ // Second hit returns the cached miss
+ $this->assertEquals( false, $cache->get( 'foo' ) );
+
+ // But a fresh value is read from the backend
+ $backend->set( 'bar', true );
+ $this->assertEquals( true, $cache->get( 'bar' ) );
+ }
}
/**
* @dataProvider provideSwappingICCProfile
- * @covers BitmapHandler::swapICCProfile
+ * @covers ExifBitmapHandler::swapICCProfile
*/
public function testSwappingICCProfile(
$sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName
/**
* Test for multi-section, hostile XML
- * @covers checkParseSafety
+ * @covers XMPReader::checkParseSafety
*/
public function testCheckParseSafety() {
$this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
}
- /**
- * @depends testImplementsSetMagic
- * @covers Article::__call
- */
- public function testImplementsCallMagic() {
- $this->article->mLatest = 33;
- $this->article->mDataLoaded = true;
- $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" );
- }
-
/**
* @covers Article::__get
* @covers Article::__set
$tmpGlobals['wgSitename'] = 'MediaWiki';
$tmpGlobals['wgServer'] = 'http://example.org';
$tmpGlobals['wgServerName'] = 'example.org';
+ $tmpGlobals['wgScriptPath'] = '';
$tmpGlobals['wgScript'] = '/index.php';
- $tmpGlobals['wgScriptPath'] = '/';
+ $tmpGlobals['wgResourceBasePath'] = '';
+ $tmpGlobals['wgStylePath'] = '/skins';
+ $tmpGlobals['wgExtensionAssetsPath'] = '/extensions';
$tmpGlobals['wgArticlePath'] = '/wiki/$1';
$tmpGlobals['wgActionPaths'] = [];
$tmpGlobals['wgVariantArticlePath'] = false;
- $tmpGlobals['wgExtensionAssetsPath'] = '/extensions';
- $tmpGlobals['wgStylePath'] = '/skins';
$tmpGlobals['wgEnableUploads'] = true;
$tmpGlobals['wgUploadNavigationUrl'] = false;
$tmpGlobals['wgThumbnailScriptPath'] = false;
/**
* @group large
*/
-class BcryptPasswordTestCase extends PasswordTestCase {
+class BcryptPasswordTest extends PasswordTestCase {
protected function getTypeConfigs() {
return [ 'bcrypt' => [
'class' => 'BcryptPassword',
$this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
}
- public static function provideExtracttExtensionMessagesFiles() {
+ public static function provideExtractExtensionMessagesFiles() {
$dir = __DIR__ . '/FooBar/';
return [
[
}
/**
- * @covers ExtensionProcessor::extracttExtensionMessagesFiles
- * @dataProvider provideExtracttExtensionMessagesFiles
+ * @covers ExtensionProcessor::extractExtensionMessagesFiles
+ * @dataProvider provideExtractExtensionMessagesFiles
*/
- public function testExtracttExtensionMessagesFiles( $input, $expected ) {
+ public function testExtractExtensionMessagesFiles( $input, $expected ) {
$processor = new ExtensionProcessor();
$processor->extractInfo( $this->dir, $input + self::$default, 1 );
$out = $processor->getExtractedInfo();
}
/**
- * @covers FileContentsHasher::getFileContentHash
+ * @covers FileContentsHasher::getFileContentsHash
* @covers FileContentsHasher::getFileContentsHashInternal
* @dataProvider provideSingleFile
*/
}
/**
- * @covers FileContentsHasher::getFileContentHash
+ * @covers FileContentsHasher::getFileContentsHash
* @covers FileContentsHasher::getFileContentsHashInternal
* @dataProvider provideMultipleFiles
*/
$_SERVER['argv'] = array_values( $_SERVER['argv'] );
}
- if ( !wfIsWindows() ) {
- # If we are not running on windows then we can enable phpunit colors
- # Windows does not come anymore with ANSI.SYS loaded by default
- # PHPUnit uses the suite.xml parameters to enable/disable colors
- # which can be then forced to be enabled with --colors.
- # The below code injects a parameter just like if the user called
- # Probably fix bug 29226
- $key = array_search( '--colors', $_SERVER['argv'] );
- if ( $key === false ) {
- array_splice( $_SERVER['argv'], 1, 0, '--colors' );
- }
- }
-
# Makes MediaWiki PHPUnit directory includable so the PHPUnit will
# be able to resolve relative files inclusion such as suites/*
# PHPUnit uses stream_resolve_include_path() internally
<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-Colors don't work on Windows!
-phpunit.php enables colors for other OSs at runtime
--->
<phpunit bootstrap="./bootstrap.php"
- colors="false"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
+
+ colors="true"
backupGlobals="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
timeoutForSmallTests="10"
timeoutForMediumTests="30"
timeoutForLargeTests="60"
- strict="true"
+ beStrictAboutTestsThatDoNotTestAnything="true"
+ beStrictAboutOutputDuringTests="true"
+ beStrictAboutTestSize="true"
verbose="true">
<testsuites>
<testsuite name="includes">