From: Moriel Schottlender Date: Thu, 30 Aug 2018 00:33:32 +0000 (-0700) Subject: TitlesMultiselectWidget: Add a widget that allows selection of multiple titles X-Git-Tag: 1.34.0-rc.0~3682^2 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/banques/ajouter.php?a=commitdiff_plain;h=07a5c71646a120a41a51b254499c3364b11a68d3;p=lhc%2Fweb%2Fwiklou.git TitlesMultiselectWidget: Add a widget that allows selection of multiple titles Add the widget in both PHP and JS for OOUI, and into HTMLForm definitions. In JS, the widget uses the engine from mw.widgets.TitleWidget with the async support from OO.ui.mixin.RequestManager. The PHP version provides a textarea, like UsersMultiselectWidget.php which is then infused if JS is available. Also, add highlightSearchQuery option for TitleWidget to allow for not highlighting the partial search query the user typed in, if the UI requires it. This option (highlighting partial result) is already optional in the TitleOptionWidget, so this config exposes that optionality in the TitleWidget widget for its menu children. Notes: HTMLTitlesMultiselectField 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 TitlesMultiselectWidget is a duplication of UsersMultiselectWidget except for: - $usersArray was renamed to $titlesArray - getJavascriptClassName returns the correct js class for mw.widgets.TitlesMultiselectWidget for infusion. Bug: T197109 Depends-On: I675316dddf272fd0d6172ecad3882160752bf780 Change-Id: Ie96947a35f70b76731e16ae5b85de815dfa4a8ce --- diff --git a/autoload.php b/autoload.php index 3e6b4a20b7..94e6fa1941 100644 --- a/autoload.php +++ b/autoload.php @@ -613,6 +613,7 @@ $wgAutoloadLocalClasses = [ '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', @@ -937,6 +938,7 @@ $wgAutoloadLocalClasses = [ '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', diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 52a18eb7b8..d3b5db7f40 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -175,6 +175,7 @@ class HTMLForm extends ContextSource { 'title' => HTMLTitleTextField::class, 'user' => HTMLUserTextField::class, 'usersmultiselect' => HTMLUsersMultiselectField::class, + 'titlesmultiselect' => HTMLTitlesMultiselectField::class, ]; public $mFieldData; diff --git a/includes/htmlform/fields/HTMLTitlesMultiselectField.php b/includes/htmlform/fields/HTMLTitlesMultiselectField.php new file mode 100644 index 0000000000..c93c940bdf --- /dev/null +++ b/includes/htmlform/fields/HTMLTitlesMultiselectField.php @@ -0,0 +1,120 @@ + 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' ]; + } + +} diff --git a/includes/widget/TitlesMultiselectWidget.php b/includes/widget/TitlesMultiselectWidget.php new file mode 100644 index 0000000000..95304b0f9f --- /dev/null +++ b/includes/widget/TitlesMultiselectWidget.php @@ -0,0 +1,67 @@ +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 ); + } + +} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 5283a5bb06..0580b0c8ee 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -4353,6 +4353,7 @@ "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.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index a17cfca049..8dae3da6e4 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -4555,6 +4555,7 @@ "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}}.", diff --git a/resources/Resources.php b/resources/Resources.php index 9603830b84..49a3a845f5 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2706,6 +2706,18 @@ return [ ], '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', diff --git a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js index c256f1f8b1..cb1281d42c 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js @@ -28,6 +28,7 @@ * @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 */ @@ -51,6 +52,7 @@ 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 @@ -344,7 +346,7 @@ missing: data.missing, redirect: data.redirect, disambiguation: data.disambiguation, - query: this.getQueryValue(), + query: this.highlightSearchQuery ? this.getQueryValue() : null, compare: this.compare }; }; diff --git a/resources/src/mediawiki.widgets/mw.widgets.TitlesMultiselectWidget.js b/resources/src/mediawiki.widgets/mw.widgets.TitlesMultiselectWidget.js new file mode 100644 index 0000000000..71ba33f89b --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.TitlesMultiselectWidget.js @@ -0,0 +1,145 @@ +/*! + * 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 , 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 = $( '