'HTMLTextField' => __DIR__ . '/includes/htmlform/fields/HTMLTextField.php',
'HTMLTextFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLTextFieldWithButton.php',
'HTMLTitleTextField' => __DIR__ . '/includes/htmlform/fields/HTMLTitleTextField.php',
+ 'HTMLTitlesMultiselectField' => __DIR__ . '/includes/htmlform/fields/HTMLTitlesMultiselectField.php',
'HTMLUserTextField' => __DIR__ . '/includes/htmlform/fields/HTMLUserTextField.php',
'HTMLUsersMultiselectField' => __DIR__ . '/includes/htmlform/fields/HTMLUsersMultiselectField.php',
'HTTPFileStreamer' => __DIR__ . '/includes/libs/filebackend/HTTPFileStreamer.php',
'MediaWiki\\Widget\\SelectWithInputWidget' => __DIR__ . '/includes/widget/SelectWithInputWidget.php',
'MediaWiki\\Widget\\SizeFilterWidget' => __DIR__ . '/includes/widget/SizeFilterWidget.php',
'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
+ 'MediaWiki\\Widget\\TitlesMultiselectWidget' => __DIR__ . '/includes/widget/TitlesMultiselectWidget.php',
'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
'MediaWiki\\Widget\\UsersMultiselectWidget' => __DIR__ . '/includes/widget/UsersMultiselectWidget.php',
'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
'title' => HTMLTitleTextField::class,
'user' => HTMLUserTextField::class,
'usersmultiselect' => HTMLUsersMultiselectField::class,
+ 'titlesmultiselect' => HTMLTitlesMultiselectField::class,
];
public $mFieldData;
--- /dev/null
+<?php
+
+use MediaWiki\Widget\TitlesMultiselectWidget;
+
+/**
+ * Implements a tag multiselect input field for titles.
+ *
+ * Besides the parameters recognized by HTMLTitleTextField, 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 titles
+ *
+ * This widget is a duplication of HTMLUsersMultiselectField, except for:
+ * - The configuration variable changed to 'titles' (from 'users')
+ * - OOUI modules were adjusted for the TitlesMultiselectWidget
+ * - The PHP version instantiates a MediaWiki\Widget\TitlesMultiselectWidget
+ *
+ * @note This widget is not likely to remain functional in non-OOUI forms.
+ */
+class HTMLTitlesMultiselectField extends HTMLTitleTextField {
+ public function __construct( $params ) {
+ $params += [
+ // This overrides the default from HTMLTitleTextField
+ 'required' => false,
+ ];
+
+ parent::__construct( $params );
+ }
+
+ public function loadDataFromRequest( $request ) {
+ $value = $request->getText( $this->mName, $this->getDefault() );
+
+ $titlesArray = explode( "\n", $value );
+ // Remove empty lines
+ $titlesArray = array_values( array_filter( $titlesArray, function ( $title ) {
+ return trim( $title ) !== '';
+ } ) );
+ // This function is expected to return a string
+ return implode( "\n", $titlesArray );
+ }
+
+ public function validate( $value, $alldata ) {
+ if ( !$this->mParams['exists'] ) {
+ return true;
+ }
+
+ if ( is_null( $value ) ) {
+ return false;
+ }
+
+ // $value is a string, because HTMLForm fields store their values as strings
+ $titlesArray = explode( "\n", $value );
+
+ if ( isset( $this->mParams['max'] ) ) {
+ if ( count( $titlesArray ) > $this->mParams['max'] ) {
+ return $this->msg( 'htmlform-int-toohigh', $this->mParams['max'] );
+ }
+ }
+
+ foreach ( $titlesArray as $title ) {
+ $result = parent::validate( $title, $alldata );
+ if ( $result !== true ) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ public function getInputHTML( $value ) {
+ $this->mParent->getOutput()->enableOOUI();
+ return $this->getInputOOUI( $value );
+ }
+
+ public function getInputOOUI( $value ) {
+ $params = [
+ 'id' => $this->mID,
+ 'name' => $this->mName,
+ 'dir' => $this->mDir,
+ ];
+
+ if ( isset( $this->mParams['disabled'] ) ) {
+ $params['disabled'] = $this->mParams['disabled'];
+ }
+
+ 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-titlesmultiselect-placeholder' )->plain();
+ }
+
+ if ( !is_null( $value ) ) {
+ // $value is a string, but the widget expects an array
+ $params['default'] = $value === '' ? [] : explode( "\n", $value );
+ }
+
+ // Make the field auto-infusable when it's used inside a legacy HTMLForm rather than OOUIHTMLForm
+ $params['infusable'] = true;
+ $params['classes'] = [ 'mw-htmlform-field-autoinfuse' ];
+ $widget = new TitlesMultiselectWidget( $params );
+ $widget->setAttributes( [ 'data-mw-modules' => implode( ',', $this->getOOUIModules() ) ] );
+
+ return $widget;
+ }
+
+ protected function shouldInfuseOOUI() {
+ return true;
+ }
+
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.TitlesMultiselectWidget' ];
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Widget;
+
+use OOUI\MultilineTextInputWidget;
+
+/**
+ * Widget to select multiple titles.
+ *
+ * @copyright 2017 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license MIT
+ */
+class TitlesMultiselectWidget extends \OOUI\Widget {
+
+ protected $titlesArray = [];
+ protected $inputName = null;
+ protected $inputPlaceholder = null;
+
+ /**
+ * @param array $config Configuration options
+ * - array $config['titles'] Array of titles to use as preset data
+ * - array $config['placeholder'] Placeholder message for input
+ * - array $config['name'] Name attribute (used in forms)
+ */
+ public function __construct( array $config = [] ) {
+ parent::__construct( $config );
+
+ // Properties
+ if ( isset( $config['default'] ) ) {
+ $this->titlesArray = $config['default'];
+ }
+ if ( isset( $config['name'] ) ) {
+ $this->inputName = $config['name'];
+ }
+ if ( isset( $config['placeholder'] ) ) {
+ $this->inputPlaceholder = $config['placeholder'];
+ }
+
+ $textarea = new MultilineTextInputWidget( [
+ 'name' => $this->inputName,
+ 'value' => implode( "\n", $this->titlesArray ),
+ 'rows' => 10,
+ ] );
+ $this->appendContent( $textarea );
+ $this->addClasses( [ 'mw-widgets-titlesMultiselectWidget' ] );
+ }
+
+ protected function getJavaScriptClassName() {
+ return 'mw.widgets.TitlesMultiselectWidget';
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->titlesArray !== null ) {
+ $config['selected'] = $this->titlesArray;
+ }
+ if ( $this->inputName !== null ) {
+ $config['name'] = $this->inputName;
+ }
+ if ( $this->inputPlaceholder !== null ) {
+ $config['placeholder'] = $this->inputPlaceholder;
+ }
+
+ $config['$overlay'] = true;
+ return parent::getConfig( $config );
+ }
+
+}
"mw-widgets-titleinput-description-redirect": "redirect to $1",
"mw-widgets-categoryselector-add-category-placeholder": "Add a category...",
"mw-widgets-usersmultiselect-placeholder": "Add more...",
+ "mw-widgets-titlesmultiselect-placeholder": "Add more...",
"date-range-from": "From date:",
"date-range-to": "To date:",
"sessionmanager-tie": "Cannot combine multiple request authentication types: $1.",
"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",
+ "mw-widgets-titlesmultiselect-placeholder": "Placeholder displayed in the input field, where new titles are entered",
"date-range-from": "Label for an input field that specifies the start date of a date range filter.",
"date-range-to": "Label for an input field that specifies the end date of a date range filter.",
"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}}.",
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.widgets.TitlesMultiselectWidget' => [
+ 'scripts' => [
+ 'resources/src/mediawiki.widgets/mw.widgets.TitlesMultiselectWidget.js',
+ ],
+ 'dependencies' => [
+ 'mediawiki.api',
+ 'oojs-ui-widgets',
+ // FIXME: Needs TitleInputWidget only
+ 'mediawiki.widgets',
+ ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.widgets.SearchInputWidget' => [
'scripts' => [
'resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js',
* @cfg {boolean} [excludeCurrentPage] Exclude the current page from suggestions
* @cfg {boolean} [validateTitle=true] Whether the input must be a valid title
* @cfg {boolean} [required=false] Whether the input must not be empty
+ * @cfg {boolean} [highlightSearchQuery=true] Highlight the partial query the user used for this title
* @cfg {Object} [cache] Result cache which implements a 'set' method, taking keyed values as an argument
* @cfg {mw.Api} [api] API object to use, creates a default mw.Api instance if not specified
*/
this.addQueryInput = config.addQueryInput !== false;
this.excludeCurrentPage = !!config.excludeCurrentPage;
this.validateTitle = config.validateTitle !== undefined ? config.validateTitle : true;
+ this.highlightSearchQuery = config.highlightSearchQuery === undefined ? true : !!config.highlightSearchQuery;
this.cache = config.cache;
this.api = config.api || new mw.Api();
// Supports: IE10, FF28, Chrome23
missing: data.missing,
redirect: data.redirect,
disambiguation: data.disambiguation,
- query: this.getQueryValue(),
+ query: this.highlightSearchQuery ? this.getQueryValue() : null,
compare: this.compare
};
};
--- /dev/null
+/*!
+ * MediaWiki Widgets - TitlesMultiselectWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function () {
+
+ /**
+ * Creates an mw.widgets.TitlesMultiselectWidget object
+ *
+ * @class
+ * @extends OO.ui.MenuTagMultiselectWidget
+ * @mixins OO.ui.mixin.RequestManager
+ * @mixins OO.ui.mixin.PendingElement
+ * @mixins mw.widgets.TitleWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ mw.widgets.TitlesMultiselectWidget = function MwWidgetsTitlesMultiselectWidget( config ) {
+ config = $.extend( true, {
+ // Shouldn't this be handled by MenuTagMultiselectWidget?
+ options: config.selected ? config.selected.map( function ( title ) {
+ return {
+ data: title,
+ label: title
+ };
+ } ) : []
+ }, config );
+
+ // Parent constructor
+ mw.widgets.TitlesMultiselectWidget.parent.call( this, $.extend( true,
+ {
+ clearInputOnChoose: true,
+ inputPosition: 'inline',
+ allowEditTags: false
+ },
+ config
+ ) );
+
+ // Mixin constructors
+ mw.widgets.TitleWidget.call( this, $.extend( true, {
+ addQueryInput: true,
+ highlightSearchQuery: false
+ }, config ) );
+ OO.ui.mixin.RequestManager.call( this, config );
+ OO.ui.mixin.PendingElement.call( this, $.extend( true, {}, config, {
+ $pending: this.$handle
+ } ) );
+
+ // Validate from mw.widgets.TitleWidget
+ this.input.setValidation( this.isQueryValid.bind( this ) );
+
+ if ( this.maxLength !== undefined ) {
+ // maxLength is defined through TitleWidget parent
+ this.input.$input.attr( 'maxlength', this.maxLength );
+ }
+
+ // Initialization
+ this.$element
+ .addClass( 'mw-widgets-titlesMultiselectWidget' );
+
+ this.menu.$element
+ // For consistency, use the same classes as TitleWidget
+ // expects for menu results
+ .addClass( 'mw-widget-titleWidget-menu' )
+ .toggleClass( 'mw-widget-titleWidget-menu-withImages', this.showImages )
+ .toggleClass( 'mw-widget-titleWidget-menu-withDescriptions', this.showDescriptions );
+
+ if ( 'name' in config ) {
+ // Use this instead of <input type="hidden">, because hidden inputs do not have separate
+ // 'value' and 'defaultValue' properties. The script on Special:Preferences
+ // (mw.special.preferences.confirmClose) checks this property to see if a field was changed.
+ this.hiddenInput = $( '<textarea>' )
+ .addClass( 'oo-ui-element-hidden' )
+ .attr( 'name', config.name )
+ .appendTo( this.$element );
+ // Update with preset values
+ // Set the default value (it might be different from just being empty)
+ this.hiddenInput.prop( 'defaultValue', this.getItems().map( function ( item ) {
+ return item.getData();
+ } ).join( '\n' ) );
+ this.on( 'change', function ( items ) {
+ this.hiddenInput.val( items.map( function ( item ) {
+ return item.getData();
+ } ).join( '\n' ) );
+ // Trigger a 'change' event as if a user edited the text
+ // (it is not triggered when changing the value from JS code).
+ this.hiddenInput.trigger( 'change' );
+ }.bind( this ) );
+ }
+
+ };
+
+ /* Setup */
+
+ OO.inheritClass( mw.widgets.TitlesMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
+ OO.mixinClass( mw.widgets.TitlesMultiselectWidget, OO.ui.mixin.RequestManager );
+ OO.mixinClass( mw.widgets.TitlesMultiselectWidget, OO.ui.mixin.PendingElement );
+ OO.mixinClass( mw.widgets.TitlesMultiselectWidget, mw.widgets.TitleWidget );
+
+ /* Methods */
+
+ mw.widgets.TitlesMultiselectWidget.prototype.getQueryValue = function () {
+ return this.input.getValue();
+ };
+
+ /**
+ * @inheritdoc OO.ui.MenuTagMultiselectWidget
+ */
+ mw.widgets.TitlesMultiselectWidget.prototype.onInputChange = function () {
+ var widget = this;
+
+ this.getRequestData()
+ .then( function ( data ) {
+ // Reset
+ widget.menu.clearItems();
+ widget.menu.addItems( widget.getOptionsFromData( data ) );
+ } );
+
+ mw.widgets.TitlesMultiselectWidget.parent.prototype.onInputChange.call( this );
+ };
+
+ /**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+ mw.widgets.TitlesMultiselectWidget.prototype.getRequestQuery = function () {
+ return this.getQueryValue();
+ };
+
+ /**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+ mw.widgets.TitlesMultiselectWidget.prototype.getRequest = function () {
+ return this.getSuggestionsPromise();
+ };
+
+ /**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+ mw.widgets.TitlesMultiselectWidget.prototype.getRequestCacheDataFromResponse = function ( response ) {
+ return response.query || {};
+ };
+}() );