Introduce multiselect widgets for namespaces
authorThalia <thalia.e.chan@googlemail.com>
Fri, 14 Dec 2018 17:55:01 +0000 (17:55 +0000)
committerThalia <thalia.e.chan@googlemail.com>
Tue, 22 Jan 2019 12:48:42 +0000 (12:48 +0000)
Bug: T204986
Change-Id: Ie3916e2322d8b1a7effe9ba4604b596b568004e6

autoload.php
includes/htmlform/HTMLForm.php
includes/htmlform/fields/HTMLNamespacesMultiselectField.php [new file with mode: 0644]
includes/widget/NamespacesMultiselectWidget.php [new file with mode: 0644]
resources/Resources.php
resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.NamespacesMultiselectWidget.js [new file with mode: 0644]

index afc187f..cb373e1 100644 (file)
@@ -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',
index 44e703b..82cbb40 100644 (file)
@@ -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 (file)
index 0000000..5ad1a4d
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+
+use MediaWiki\Widget\NamespacesMultiselectWidget;
+
+/**
+ * Implements a tag multiselect input field for namespaces.
+ *
+ * The result is the array of namespaces
+ *
+ * TODO: This widget duplicates a lot from HTMLTitlesMultiselectField,
+ * which itself duplicates HTMLUsersMultiselectField. These classes
+ * should be refactored.
+ *
+ * @note This widget is not likely to remain functional in non-OOUI forms.
+ */
+class HTMLNamespacesMultiselectField extends HTMLSelectNamespace {
+       public function loadDataFromRequest( $request ) {
+               $value = $request->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 (file)
index 0000000..ffa781f
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace MediaWiki\Widget;
+
+/**
+ * Widget to select multiple namespaces.
+ *
+ * @copyright 2017 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license MIT
+ */
+class NamespacesMultiselectWidget extends TagMultiselectWidget {
+
+       /**
+        * @param array $config Configuration options
+        */
+       public function __construct( array $config = [] ) {
+               parent::__construct( $config );
+
+               $this->addClasses( [ 'mw-widgets-namespacesMultiselectWidget' ] );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.NamespacesMultiselectWidget';
+       }
+
+       public function getConfig( &$config ) {
+               return parent::getConfig( $config );
+       }
+
+}
index 1971001..453da79 100644 (file)
@@ -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',
index ccfc726..7b9f71b 100644 (file)
@@ -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 );
 
        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 (file)
index 0000000..e5adc29
--- /dev/null
@@ -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 <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.NamespacesMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
+       OO.mixinClass( mw.widgets.NamespacesMultiselectWidget, OO.ui.mixin.PendingElement );
+
+}() );