'HTMLTextFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLTextFieldWithButton.php',
'HTMLTitleTextField' => __DIR__ . '/includes/htmlform/fields/HTMLTitleTextField.php',
'HTMLUserTextField' => __DIR__ . '/includes/htmlform/fields/HTMLUserTextField.php',
+ 'HTMLUsersMultiselectField' => __DIR__ . '/includes/htmlform/fields/HTMLUsersMultiselectField.php',
'HTTPFileStreamer' => __DIR__ . '/includes/libs/filebackend/HTTPFileStreamer.php',
'HWLDFWordAccumulator' => __DIR__ . '/includes/diff/DairikiDiff.php',
'HashBagOStuff' => __DIR__ . '/includes/libs/objectcache/HashBagOStuff.php',
'MediaWiki\\Widget\\Search\\SimpleSearchResultWidget' => __DIR__ . '/includes/widget/search/SimpleSearchResultWidget.php',
'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
+ 'MediaWiki\\Widget\\UsersMultiselectWidget' => __DIR__ . '/includes/widget/UsersMultiselectWidget.php',
'MemCachedClientforWiki' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
'url' => 'HTMLTextField',
'title' => 'HTMLTitleTextField',
'user' => 'HTMLUserTextField',
+ 'usersmultiselect' => 'HTMLUsersMultiselectField',
];
public $mFieldData;
--- /dev/null
+<?php
+
+use MediaWiki\Widget\UsersMultiselectWidget;
+
+/**
+ * Implements a capsule multiselect input field for user names.
+ *
+ * Besides the parameters recognized by HTMLUserTextField, additional recognized
+ * parameters are:
+ * default - (optional) Array of usernames to use as preset data
+ * placeholder - (optional) Custom placeholder message for input
+ *
+ * The result is the array of usernames
+ *
+ * @note This widget is not likely to remain functional in non-OOUI forms.
+ */
+class HTMLUsersMultiselectField extends HTMLUserTextField {
+
+ public function loadDataFromRequest( $request ) {
+ if ( !$request->getCheck( $this->mName ) ) {
+ return $this->getDefault();
+ }
+
+ $usersArray = explode( "\n", $request->getText( $this->mName ) );
+ // Remove empty lines
+ $usersArray = array_values( array_filter( $usersArray, function( $username ) {
+ return trim( $username ) !== '';
+ } ) );
+ return $usersArray;
+ }
+
+ public function validate( $value, $alldata ) {
+ if ( !$this->mParams['exists'] ) {
+ return true;
+ }
+
+ if ( is_null( $value ) ) {
+ return false;
+ }
+
+ foreach ( $value as $username ) {
+ $result = parent::validate( $username, $alldata );
+ if ( $result !== true ) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ public function getInputHTML( $values ) {
+ $this->mParent->getOutput()->enableOOUI();
+ return $this->getInputOOUI( $values );
+ }
+
+ public function getInputOOUI( $values ) {
+ $params = [ 'name' => $this->mName ];
+
+ if ( isset( $this->mParams['default'] ) ) {
+ $params['default'] = $this->mParams['default'];
+ }
+
+ if ( isset( $this->mParams['placeholder'] ) ) {
+ $params['placeholder'] = $this->mParams['placeholder'];
+ } else {
+ $params['placeholder'] = $this->msg( 'mw-widgets-usersmultiselect-placeholder' )
+ ->inContentLanguage()
+ ->plain();
+ }
+
+ if ( !is_null( $values ) ) {
+ $params['default'] = $values;
+ }
+
+ return new UsersMultiselectWidget( $params );
+ }
+
+ protected function shouldInfuseOOUI() {
+ return true;
+ }
+
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.UsersMultiselectWidget' ];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * MediaWiki Widgets – UsersMultiselectWidget class.
+ *
+ * @copyright 2017 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+namespace MediaWiki\Widget;
+
+use \OOUI\TextInputWidget;
+
+/**
+ * Widget to select multiple users.
+ */
+class UsersMultiselectWidget extends \OOUI\Widget {
+
+ protected $usersArray = [];
+ protected $inputName = null;
+ protected $inputPlaceholder = null;
+
+ /**
+ * @param array $config Configuration options
+ * @param array $config['users'] Array of usernames to use as preset data
+ * @param array $config['placeholder'] Placeholder message for input
+ * @param array $config['name'] Name attribute (used in forms)
+ */
+ public function __construct( array $config = [] ) {
+ parent::__construct( $config );
+
+ // Properties
+ if ( isset( $config['default'] ) ) {
+ $this->usersArray = $config['default'];
+ }
+ if ( isset( $config['name'] ) ) {
+ $this->inputName = $config['name'];
+ }
+ if ( isset( $config['placeholder'] ) ) {
+ $this->inputPlaceholder = $config['placeholder'];
+ }
+
+ $textarea = new TextInputWidget( [
+ 'name' => $this->inputName,
+ 'multiline' => true,
+ 'value' => implode( "\n", $this->usersArray ),
+ 'rows' => 25,
+ ] );
+ $this->prependContent( $textarea );
+ }
+
+ protected function getJavaScriptClassName() {
+ return 'mw.widgets.UsersMultiselectWidget';
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->usersArray !== null ) {
+ $config['data'] = $this->usersArray;
+ }
+ if ( $this->inputName !== null ) {
+ $config['name'] = $this->inputName;
+ }
+ if ( $this->inputPlaceholder !== null ) {
+ $config['placeholder'] = $this->inputPlaceholder;
+ }
+
+ return parent::getConfig( $config );
+ }
+
+}
"mw-widgets-titleinput-description-new-page": "page does not exist yet",
"mw-widgets-titleinput-description-redirect": "redirect to $1",
"mw-widgets-categoryselector-add-category-placeholder": "Add a category...",
+ "mw-widgets-usersmultiselect-placeholder": "Add more...",
"sessionmanager-tie": "Cannot combine multiple request authentication types: $1.",
"sessionprovider-generic": "$1 sessions",
"sessionprovider-mediawiki-session-cookiesessionprovider": "cookie-based sessions",
"mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.",
"mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget.",
"mw-widgets-categoryselector-add-category-placeholder": "Placeholder displayed in the category selector widget after the capsules of already added categories.",
+ "mw-widgets-usersmultiselect-placeholder": "Placeholder displayed in the input field, where new usernames are entered",
"sessionmanager-tie": "Used as an error message when multiple session sources are tied in priority.\n\nParameters:\n* $1 - List of dession type descriptions, from messages like {{msg-mw|sessionprovider-mediawiki-session-cookiesessionprovider}}.",
"sessionprovider-generic": "Used to create a generic session type description when one isn't provided via the proper message. Should be phrased to make sense when added to a message such as {{msg-mw|cannotloginnow-text}}.\n\nParameters:\n* $1 - PHP classname.",
"sessionprovider-mediawiki-session-cookiesessionprovider": "Description of the sessions provided by the CookieSessionProvider class, which use HTTP cookies. Should be phrased to make sense when added to a message such as {{msg-mw|cannotloginnow-text}}.",
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.widgets.UsersMultiselectWidget' => [
+ 'scripts' => [
+ 'resources/src/mediawiki.widgets/mw.widgets.UsersMultiselectWidget.js',
+ ],
+ 'dependencies' => [
+ 'oojs-ui-widgets',
+ ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.widgets.SearchInputWidget' => [
'scripts' => [
'resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js',
--- /dev/null
+/*!
+ * MediaWiki Widgets - UsersMultiselectWidget class.
+ *
+ * @copyright 2017 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+ /**
+ * UsersMultiselectWidget can be used to input list of users in a single
+ * line.
+ *
+ * If used inside HTML form the results will be sent as the list of
+ * newline-separated usernames.
+ *
+ * @class
+ * @extends OO.ui.CapsuleMultiselectWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {mw.Api} [api] Instance of mw.Api (or subclass thereof) to use for queries
+ * @cfg {number} [limit=10] Number of results to show in autocomplete menu
+ * @cfg {string} [name] Name of input to submit results (when used in HTML forms)
+ */
+ mw.widgets.UsersMultiselectWidget = function MwWidgetsUsersMultiselectWidget( config ) {
+ // Config initialization
+ config = $.extend( {
+ limit: 10
+ }, config, {
+ // Because of using autocomplete (constantly changing menu), we need to
+ // allow adding usernames, which do not present in the menu.
+ allowArbitrary: true
+ } );
+
+ // Parent constructor
+ mw.widgets.UsersMultiselectWidget.parent.call( this, $.extend( {}, config, {} ) );
+
+ // Mixin constructors
+ OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$handle } ) );
+
+ // Properties
+ this.limit = config.limit;
+
+ if ( 'name' in config ) {
+ // If used inside HTML form, then create hidden input, which will store
+ // the results.
+ this.hiddenInput = $( '<input>' )
+ .attr( 'type', 'hidden' )
+ .attr( 'name', config.name )
+ .appendTo( this.$element );
+
+ // Update with preset values
+ this.updateHiddenInput();
+ }
+
+ this.menu = this.getMenu();
+
+ // Events
+ // Update contents of autocomplete menu as user types letters
+ this.$input.on( {
+ keyup: this.updateMenuItems.bind( this )
+ } );
+ // When option is selected from autocomplete menu, update the menu
+ this.menu.connect( this, {
+ select: 'updateMenuItems'
+ } );
+ // When list of selected usernames changes, update hidden input
+ this.connect( this, {
+ change: 'updateHiddenInput'
+ } );
+
+ // API init
+ this.api = config.api || new mw.Api();
+ };
+
+ /* Setup */
+
+ OO.inheritClass( mw.widgets.UsersMultiselectWidget, OO.ui.CapsuleMultiselectWidget );
+ OO.mixinClass( mw.widgets.UsersMultiselectWidget, OO.ui.mixin.PendingElement );
+
+ /* Methods */
+
+ /**
+ * Get currently selected usernames
+ *
+ * @return {Array} usernames
+ */
+ mw.widgets.UsersMultiselectWidget.prototype.getSelectedUsernames = function() {
+ return this.getItemsData();
+ };
+
+ /**
+ * Update autocomplete menu with items
+ *
+ * @private
+ */
+ mw.widgets.UsersMultiselectWidget.prototype.updateMenuItems = function() {
+ var inputValue = this.$input.val();
+
+ if ( inputValue === this.inputValue ) {
+ // Do not restart api query if nothing has changed in the input
+ return;
+ } else {
+ this.inputValue = inputValue;
+ }
+
+ this.api.abort(); // Abort all unfinished api requests
+
+ if ( inputValue.length > 0 ) {
+ this.pushPending();
+
+ this.api.get( {
+ action: 'query',
+ list: 'allusers',
+ // Prefix of list=allusers is case sensitive. Normalise first
+ // character to uppercase so that "fo" may yield "Foo".
+ auprefix: inputValue[ 0 ].toUpperCase() + inputValue.slice( 1 ),
+ aulimit: this.limit
+ } ).done( function( response ) {
+ var suggestions = response.query.allusers,
+ selected = this.getSelectedUsernames();
+
+ // Remove usernames, which are already selected from suggestions
+ suggestions = suggestions.map( function ( user ) {
+ if ( selected.indexOf( user.name ) === -1 ) {
+ return new OO.ui.MenuOptionWidget( {
+ data: user.name,
+ label: user.name
+ } );
+ }
+ } ).filter( function( item ) {
+ return item !== undefined;
+ } );
+
+ // Remove all items from menu add fill it with new
+ this.menu.clearItems();
+
+ // Additional check to prevent bug of autoinserting first suggestion
+ // while removing user from the list
+ if ( inputValue.length > 1 || suggestions.length > 1 ) {
+ this.menu.addItems( suggestions );
+ }
+
+ this.popPending();
+ }.bind( this ) ).fail( this.popPending.bind( this ) );
+ } else {
+ this.menu.clearItems();
+ }
+ };
+
+ /**
+ * If used inside HTML form, then update hiddenInput with list o
+ * newline-separated usernames.
+ *
+ * @private
+ */
+ mw.widgets.UsersMultiselectWidget.prototype.updateHiddenInput = function() {
+ if ( 'hiddenInput' in this ) {
+ this.hiddenInput.val( this.getSelectedUsernames().join( '\n' ) );
+ }
+ };
+
+}( jQuery, mediaWiki ) );