From ccbe9f3590c052cb7f1ad5a7a873fe67c540b2eb Mon Sep 17 00:00:00 2001 From: Thalia Date: Fri, 14 Dec 2018 17:55:01 +0000 Subject: [PATCH] Introduce multiselect widgets for namespaces Bug: T204986 Change-Id: Ie3916e2322d8b1a7effe9ba4604b596b568004e6 --- autoload.php | 2 + includes/htmlform/HTMLForm.php | 1 + .../fields/HTMLNamespacesMultiselectField.php | 113 ++++++++++++++++++ .../widget/NamespacesMultiselectWidget.php | 30 +++++ resources/Resources.php | 10 ++ .../mw.widgets.NamespaceInputWidget.js | 9 +- .../mw.widgets.NamespacesMultiselectWidget.js | 87 ++++++++++++++ 7 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 includes/htmlform/fields/HTMLNamespacesMultiselectField.php create mode 100644 includes/widget/NamespacesMultiselectWidget.php create mode 100644 resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js diff --git a/autoload.php b/autoload.php index afc187fd85..cb373e1a8f 100644 --- a/autoload.php +++ b/autoload.php @@ -599,6 +599,7 @@ $wgAutoloadLocalClasses = [ 'HTMLInfoField' => __DIR__ . '/includes/htmlform/fields/HTMLInfoField.php', 'HTMLIntField' => __DIR__ . '/includes/htmlform/fields/HTMLIntField.php', 'HTMLMultiSelectField' => __DIR__ . '/includes/htmlform/fields/HTMLMultiSelectField.php', + 'HTMLNamespacesMultiselectField' => __DIR__ . '/includes/htmlform/fields/HTMLNamespacesMultiselectField.php', 'HTMLNestedFilterable' => __DIR__ . '/includes/htmlform/HTMLNestedFilterable.php', 'HTMLRadioField' => __DIR__ . '/includes/htmlform/fields/HTMLRadioField.php', 'HTMLRestrictionsField' => __DIR__ . '/includes/htmlform/fields/HTMLRestrictionsField.php', @@ -936,6 +937,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php', 'MediaWiki\\Widget\\ExpiryInputWidget' => __DIR__ . '/includes/widget/ExpiryInputWidget.php', 'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php', + 'MediaWiki\\Widget\\NamespacesMultiselectWidget' => __DIR__ . '/includes/widget/NamespacesMultiselectWidget.php', 'MediaWiki\\Widget\\PendingTextInputWidget' => __DIR__ . '/includes/widget/PendingTextInputWidget.php', 'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php', 'MediaWiki\\Widget\\Search\\BasicSearchResultSetWidget' => __DIR__ . '/includes/widget/search/BasicSearchResultSetWidget.php', diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 44e703b7a8..82cbb40d0c 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -174,6 +174,7 @@ class HTMLForm extends ContextSource { 'user' => HTMLUserTextField::class, 'usersmultiselect' => HTMLUsersMultiselectField::class, 'titlesmultiselect' => HTMLTitlesMultiselectField::class, + 'namespacesmultiselect' => HTMLNamespacesMultiselectField::class, ]; public $mFieldData; diff --git a/includes/htmlform/fields/HTMLNamespacesMultiselectField.php b/includes/htmlform/fields/HTMLNamespacesMultiselectField.php new file mode 100644 index 0000000000..5ad1a4d979 --- /dev/null +++ b/includes/htmlform/fields/HTMLNamespacesMultiselectField.php @@ -0,0 +1,113 @@ +getText( $this->mName, $this->getDefault() ); + + $namespaces = explode( "\n", $value ); + // Remove empty lines + $namespaces = array_values( array_filter( $namespaces, function ( $namespace ) { + return trim( $namespace ) !== ''; + } ) ); + // This function is expected to return a string + return implode( "\n", $namespaces ); + } + + 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 + $namespaces = explode( "\n", $value ); + + if ( isset( $this->mParams['max'] ) ) { + if ( count( $namespaces ) > $this->mParams['max'] ) { + return $this->msg( 'htmlform-int-toohigh', $this->mParams['max'] ); + } + } + + foreach ( $namespaces as $namespace ) { + $result = parent::validate( $namespace, $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 ( isset( $this->mParams['max'] ) ) { + $params['tagLimit'] = $this->mParams['max']; + } + + if ( isset( $this->mParams['input'] ) ) { + $params['input'] = $this->mParams['input']; + } + + 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 NamespacesMultiselectWidget( $params ); + $widget->setAttributes( [ 'data-mw-modules' => implode( ',', $this->getOOUIModules() ) ] ); + + return $widget; + } + + protected function shouldInfuseOOUI() { + return true; + } + + protected function getOOUIModules() { + return [ 'mediawiki.widgets.NamespacesMultiselectWidget' ]; + } + +} diff --git a/includes/widget/NamespacesMultiselectWidget.php b/includes/widget/NamespacesMultiselectWidget.php new file mode 100644 index 0000000000..ffa781f453 --- /dev/null +++ b/includes/widget/NamespacesMultiselectWidget.php @@ -0,0 +1,30 @@ +addClasses( [ 'mw-widgets-namespacesMultiselectWidget' ] ); + } + + protected function getJavaScriptClassName() { + return 'mw.widgets.NamespacesMultiselectWidget'; + } + + public function getConfig( &$config ) { + return parent::getConfig( $config ); + } + +} diff --git a/resources/Resources.php b/resources/Resources.php index 1971001333..453da79387 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2072,6 +2072,7 @@ return [ 'oojs-ui.styles.icons-editing-advanced', 'mediawiki.widgets.DateInputWidget', 'mediawiki.widgets.SelectWithInputWidget', + 'mediawiki.widgets.NamespacesMultiselectWidget', 'mediawiki.widgets.TitlesMultiselectWidget', 'mediawiki.widgets.UserInputWidget', 'mediawiki.util', @@ -2723,6 +2724,15 @@ return [ ], 'targets' => [ 'desktop', 'mobile' ], ], + 'mediawiki.widgets.NamespacesMultiselectWidget' => [ + 'scripts' => [ + 'resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js', + ], + 'dependencies' => [ + 'oojs-ui-widgets', + ], + 'targets' => [ 'desktop', 'mobile' ], + ], 'mediawiki.widgets.TitlesMultiselectWidget' => [ 'scripts' => [ 'resources/src/mediawiki.widgets/mw.widgets.TitlesMultiselectWidget.js', diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js index ccfc726493..7b9f71b6aa 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js @@ -19,7 +19,7 @@ */ mw.widgets.NamespaceInputWidget = function MwWidgetsNamespaceInputWidget( config ) { // Configuration initialization - config = $.extend( {}, config, { options: this.getNamespaceDropdownOptions( config ) } ); + config = $.extend( {}, config, { options: this.constructor.static.getNamespaceDropdownOptions( config ) } ); // Parent constructor mw.widgets.NamespaceInputWidget.parent.call( this, config ); @@ -32,14 +32,15 @@ OO.inheritClass( mw.widgets.NamespaceInputWidget, OO.ui.DropdownInputWidget ); - /* Methods */ + /* Static methods */ /** - * @private + * Get a list of namespace options, sorted by ID. + * * @param {Object} [config] Configuration options * @return {Object[]} Dropdown options */ - mw.widgets.NamespaceInputWidget.prototype.getNamespaceDropdownOptions = function ( config ) { + mw.widgets.NamespaceInputWidget.static.getNamespaceDropdownOptions = function ( config ) { var options, exclude = config.exclude || [], mainNamespace = mw.config.get( 'wgNamespaceIds' )[ '' ]; diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js b/resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js new file mode 100644 index 0000000000..e5adc292ed --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js @@ -0,0 +1,87 @@ +/*! + * MediaWiki Widgets - NamespacesMultiselectWidget 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.NamespacesMultiselectWidget object + * + * TODO: A lot of this is duplicated in mw.widgets.UsersMultiselectWidget + * and mw.widgets.TitlesMultiselectWidget. These classes should be + * refactored. + * + * @class + * @extends OO.ui.MenuTagMultiselectWidget + * + * @constructor + * @param {Object} [config] Configuration options + */ + mw.widgets.NamespacesMultiselectWidget = function MwWidgetsNamespacesMultiselectWidget( config ) { + var i, ilen, option, + namespaces = {}, + options = mw.widgets.NamespaceInputWidget.static.getNamespaceDropdownOptions( {} ); + + for ( i = 0, ilen = options.length; i < ilen; i++ ) { + option = options[ i ]; + namespaces[ option.data ] = option.label; + } + + config = $.extend( true, { + options: mw.widgets.NamespaceInputWidget.static.getNamespaceDropdownOptions( {} ) + }, config ); + + // Parent constructor + mw.widgets.NamespacesMultiselectWidget.parent.call( this, $.extend( true, + { + clearInputOnChoose: true, + inputPosition: 'inline', + allowEditTags: false + }, + config, + { + selected: config && config.selected ? config.selected.map( function ( id ) { + return { + data: id, + label: namespaces[ id ] + }; + } ) : undefined + } + ) ); + + // Initialization + this.$element + .addClass( 'mw-widgets-namespacesMultiselectWidget' ); + + 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 = $( '