HTMLForm: Add HTMLDateTimeField
authorBrad Jorsch <bjorsch@wikimedia.org>
Thu, 22 Sep 2016 17:28:29 +0000 (13:28 -0400)
committerBrad Jorsch <bjorsch@wikimedia.org>
Mon, 26 Sep 2016 16:08:56 +0000 (12:08 -0400)
And to do that, an OOUI PHP widget for
mw.widgets.datetime.DateTimeInputWidget too.

Bug: T146340
Change-Id: Iaa8b5892b6c3a1f3698cef59684cc3cdc9d483ea

autoload.php
includes/htmlform/HTMLForm.php
includes/htmlform/fields/HTMLDateTimeField.php [new file with mode: 0644]
includes/widget/AUTHORS.txt
includes/widget/DateTimeInputWidget.php [new file with mode: 0644]
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js
resources/src/mediawiki/htmlform/datetime.js [new file with mode: 0644]

index 433f907..7a5a3f1 100644 (file)
@@ -527,6 +527,7 @@ $wgAutoloadLocalClasses = [
        'HTMLCheckField' => __DIR__ . '/includes/htmlform/fields/HTMLCheckField.php',
        'HTMLCheckMatrix' => __DIR__ . '/includes/htmlform/fields/HTMLCheckMatrix.php',
        'HTMLComboboxField' => __DIR__ . '/includes/htmlform/fields/HTMLComboboxField.php',
+       'HTMLDateTimeField' => __DIR__ . '/includes/htmlform/fields/HTMLDateTimeField.php',
        'HTMLEditTools' => __DIR__ . '/includes/htmlform/fields/HTMLEditTools.php',
        'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php',
        'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php',
@@ -910,6 +911,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php',
        'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
        'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php',
+       'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php',
        'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
        'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php',
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
index 567e692..c65d97f 100644 (file)
@@ -153,6 +153,9 @@ class HTMLForm extends ContextSource {
                'checkmatrix' => 'HTMLCheckMatrix',
                'cloner' => 'HTMLFormFieldCloner',
                'autocompleteselect' => 'HTMLAutoCompleteSelectField',
+               'date' => 'HTMLDateTimeField',
+               'time' => 'HTMLDateTimeField',
+               'datetime' => 'HTMLDateTimeField',
                // HTMLTextField will output the correct type="" attribute automagically.
                // There are about four zillion other HTML5 input types, like range, but
                // we don't use those at the moment, so no point in adding all of them.
diff --git a/includes/htmlform/fields/HTMLDateTimeField.php b/includes/htmlform/fields/HTMLDateTimeField.php
new file mode 100644 (file)
index 0000000..66f89f9
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * A field that will contain a date and/or time
+ *
+ * Currently recognizes only {YYYY}-{MM}-{DD}T{HH}:{MM}:{SS.S*}Z formatted dates.
+ *
+ * Besides the parameters recognized by HTMLTextField, additional recognized
+ * parameters in the field descriptor array include:
+ *  type - 'date', 'time', or 'datetime'
+ *  min - The minimum date to allow, in any recognized format.
+ *  max - The maximum date to allow, in any recognized format.
+ *  placeholder - The default comes from the htmlform-(date|time|datetime)-placeholder message.
+ *
+ * The result is a formatted date.
+ *
+ * @note This widget is not likely to work well in non-OOUI forms.
+ */
+class HTMLDateTimeField extends HTMLTextField {
+       protected static $patterns = [
+               'date' => '[0-9]{4}-[01][0-9]-[0-3][0-9]',
+               'time' => '[0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?',
+               'datetime' => '[0-9]{4}-[01][0-9]-[0-3][0-9][T ][0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?Z?',
+       ];
+
+       protected $mType = 'datetime';
+
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               $this->mType = array_key_exists( 'type', $params )
+                       ? $params['type']
+                       : 'datetime';
+
+               if ( !in_array( $this->mType, [ 'date', 'time', 'datetime' ] ) ) {
+                       throw new InvalidArgumentException( "Invalid type '$this->mType'" );
+               }
+
+               $this->mClass .= ' mw-htmlform-datetime-field';
+       }
+
+       public function getAttributes( array $list ) {
+               $parentList = array_diff( $list, [ 'min', 'max' ] );
+               $ret = parent::getAttributes( $parentList );
+
+               if ( in_array( 'placeholder', $list ) && !isset( $ret['placeholder'] ) ) {
+                       // Messages: htmlform-date-placeholder htmlform-time-placeholder htmlform-datetime-placeholder
+                       $ret['placeholder'] = $this->msg( "htmlform-{$this->mType}-placeholder" )->text();
+               }
+
+               if ( in_array( 'min', $list ) && isset( $this->mParams['min'] ) ) {
+                       $min = $this->parseDate( $this->mParams['min'] );
+                       if ( $min ) {
+                               $ret['min'] = $this->formatDate( $min );
+                               // Because Html::expandAttributes filters it out
+                               $ret['data-min'] = $ret['min'];
+                       }
+               }
+               if ( in_array( 'max', $list ) && isset( $this->mParams['max'] ) ) {
+                       $max = $this->parseDate( $this->mParams['max'] );
+                       if ( $max ) {
+                               $ret['max'] = $this->formatDate( $max );
+                               // Because Html::expandAttributes filters it out
+                               $ret['data-max'] = $ret['max'];
+                       }
+               }
+
+               $ret['step'] = 1;
+               // Because Html::expandAttributes filters it out
+               $ret['data-step'] = 1;
+
+               $ret['type'] = $this->mType;
+               $ret['pattern'] = static::$patterns[$this->mType];
+
+               return $ret;
+       }
+
+       function loadDataFromRequest( $request ) {
+               if ( !$request->getCheck( $this->mName ) ) {
+                       return $this->getDefault();
+               }
+
+               $value = $request->getText( $this->mName );
+               $date = $this->parseDate( $value );
+               return $date ? $this->formatDate( $date ) : $value;
+       }
+
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               if ( $value === '' ) {
+                       // required was already checked by parent::validate
+                       return true;
+               }
+
+               $date = $this->parseDate( $value );
+               if ( !$date ) {
+                       // Messages: htmlform-date-invalid htmlform-time-invalid htmlform-datetime-invalid
+                       return $this->msg( "htmlform-{$this->mType}-invalid" )->parseAsBlock();
+               }
+
+               if ( isset( $this->mParams['min'] ) ) {
+                       $min = $this->parseDate( $this->mParams['min'] );
+                       if ( $min && $date < $min ) {
+                               // Messages: htmlform-date-toolow htmlform-time-toolow htmlform-datetime-toolow
+                               return $this->msg( "htmlform-{$this->mType}-toolow", $this->formatDate( $min ) )
+                                       ->parseAsBlock();
+                       }
+               }
+
+               if ( isset( $this->mParams['max'] ) ) {
+                       $max = $this->parseDate( $this->mParams['max'] );
+                       if ( $max && $date > $max ) {
+                               // Messages: htmlform-date-toohigh htmlform-time-toohigh htmlform-datetime-toohigh
+                               return $this->msg( "htmlform-{$this->mType}-toohigh", $this->formatDate( $max ) )
+                                       ->parseAsBlock();
+                       }
+               }
+
+               return true;
+       }
+
+       protected function parseDate( $value ) {
+               $value = trim( $value );
+
+               if ( $this->mType === 'date' ) {
+                       $value .= ' T00:00:00+0000';
+               }
+               if ( $this->mType === 'time' ) {
+                       $value = '1970-01-01 ' . $value . '+0000';
+               }
+
+               try {
+                       $date = new DateTime( $value, new DateTimeZone( 'GMT' ) );
+                       return $date->getTimestamp();
+               } catch ( Exception $ex ) {
+                       return 0;
+               }
+       }
+
+       protected function formatDate( $value ) {
+               switch ( $this->mType ) {
+                       case 'date':
+                               return gmdate( 'Y-m-d', $value );
+
+                       case 'time':
+                               return gmdate( 'H:i:s', $value );
+
+                       case 'datetime':
+                               return gmdate( 'Y-m-d\\TH:i:s\\Z', $value );
+               }
+       }
+
+       public function getInputOOUI( $value ) {
+               $params = [
+                       'type' => $this->mType,
+                       'value' => $value,
+                       'name' => $this->mName,
+                       'id' => $this->mID,
+               ];
+
+               if ( isset( $this->mParams['min'] ) ) {
+                       $min = $this->parseDate( $this->mParams['min'] );
+                       if ( $min ) {
+                               $params['min'] = $this->formatDate( $min );
+                       }
+               }
+               if ( isset( $this->mParams['max'] ) ) {
+                       $max = $this->parseDate( $this->mParams['max'] );
+                       if ( $max ) {
+                               $params['max'] = $this->formatDate( $max );
+                       }
+               }
+
+               return new MediaWiki\Widget\DateTimeInputWidget( $params );
+       }
+
+       protected function getOOUIModules() {
+               return [ 'mediawiki.widgets.datetime' ];
+       }
+
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
+
+}
index b91596b..ca188ba 100644 (file)
@@ -2,6 +2,7 @@ Authors (alphabetically)
 
 Alex Monk <krenair@wikimedia.org>
 Bartosz Dziewoński <bdziewonski@wikimedia.org>
+Brad Jorsch <bjorsch@wikimedia.org>
 Ed Sanders <esanders@wikimedia.org>
 Florian Schmidt <florian.schmidt.welzow@t-online.de>
 James D. Forrester <jforrester@wikimedia.org>
diff --git a/includes/widget/DateTimeInputWidget.php b/includes/widget/DateTimeInputWidget.php
new file mode 100644 (file)
index 0000000..f0d5cdb
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * MediaWiki Widgets – DateTimeInputWidget class.
+ *
+ * @copyright 2016 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+namespace MediaWiki\Widget;
+
+use OOUI\Tag;
+
+/**
+ * Date-time input widget.
+ */
+class DateTimeInputWidget extends \OOUI\InputWidget {
+
+       protected $type = null;
+       protected $min = null;
+       protected $max = null;
+       protected $clearable = null;
+
+       /**
+        * @param array $config Configuration options
+        * @param string $config['type'] 'date', 'time', or 'datetime'
+        * @param string $config['min'] Minimum date, time, or datetime
+        * @param string $config['max'] Maximum date, time, or datetime
+        * @param bool $config['clearable'] Whether to provide for blanking the value.
+        */
+       public function __construct( array $config = [] ) {
+               // We need $this->type set before calling the parent constructor
+               if ( isset( $config['type'] ) ) {
+                       $this->type = $config['type'];
+               } else {
+                       throw new \InvalidArgumentException( '$config[\'type\'] must be specified' );
+               }
+
+               // Parent constructor
+               parent::__construct( $config );
+
+               // Properties, which are ignored in PHP and just shipped back to JS
+               if ( isset( $config['min'] ) ) {
+                       $this->min = $config['min'];
+               }
+               if ( isset( $config['max'] ) ) {
+                       $this->max = $config['max'];
+               }
+               if ( isset( $config['clearable'] ) ) {
+                       $this->clearable = $config['clearable'];
+               }
+
+               // Initialization
+               $this->addClasses( [ 'mw-widgets-datetime-dateTimeInputWidget' ] );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.datetime.DateTimeInputWidget';
+       }
+
+       public function getConfig( &$config ) {
+               $config['type'] = $this->type;
+               if ( $this->min !== null ) {
+                       $config['min'] = $this->min;
+               }
+               if ( $this->max !== null ) {
+                       $config['max'] = $this->max;
+               }
+               if ( $this->clearable !== null ) {
+                       $config['clearable'] = $this->clearable;
+               }
+               return parent::getConfig( $config );
+       }
+
+       protected function getInputElement( $config ) {
+               return ( new Tag( 'input' ) )->setAttributes( [ 'type' => $this->type ] );
+       }
+}
index 338d650..0056883 100644 (file)
        "htmlform-cloner-create": "Add more",
        "htmlform-cloner-delete": "Remove",
        "htmlform-cloner-required": "At least one value is required.",
+       "htmlform-date-placeholder": "YYYY-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "YYYY-MM-DD HH:MM:SS",
+       "htmlform-date-invalid": "The value you specified is not a recognized date. Try using YYYY-MM-DD format.",
+       "htmlform-time-invalid": "The value you specified is not a recognized time. Try using HH:MM:SS format.",
+       "htmlform-datetime-invalid": "The value you specified is not a recognized date and time. Try using YYYY-MM-DD HH:MM:SS format.",
+       "htmlform-date-toolow": "The value you specified is before the earliest allowed date of $1.",
+       "htmlform-date-toohigh": "The value you specified is after the latest allowed date of $1.",
+       "htmlform-time-toolow": "The value you specified is before the earliest allowed time of $1.",
+       "htmlform-time-toohigh": "The value you specified is after the latest allowed time of $1.",
+       "htmlform-datetime-toolow": "The value you specified is before the earliest allowed date and time of $1.",
+       "htmlform-datetime-toohigh": "The value you specified is after the latest allowed date and time of $1.",
        "htmlform-title-badnamespace": "[[:$1]] is not in the \"{{ns:$2}}\" namespace.",
        "htmlform-title-not-creatable": "\"$1\" is not a creatable page title",
        "htmlform-title-not-exists": "$1 does not exist.",
index 4ec5cec..9b29e30 100644 (file)
        "htmlform-cloner-create": "Used as the text for the button that adds a row to a multi-input HTML form element.\n\nSee also:\n* {{msg-mw|htmlform-cloner-delete}}\n* {{msg-mw|htmlform-cloner-required}}",
        "htmlform-cloner-delete": "Used as the text for the button that removes a row from a multi-input HTML form element\n\nSee also:\n* {{msg-mw|htmlform-cloner-create}}\n* {{msg-mw|htmlform-cloner-required}}\n{{Identical|Remove}}",
        "htmlform-cloner-required": "Used as an error message in HTML forms.\n\nSee also:\n* {{msg-mw|htmlform-required}}\n* {{msg-mw|htmlform-cloner-create}}\n* {{msg-mw|htmlform-cloner-delete}}",
+       "htmlform-date-placeholder": "Used as initial placeholder text in \"date\" input boxes. This date MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters. You can localise the letters to your language or script, but you should not change the format.",
+       "htmlform-date-invalid": "Used as error message in HTML forms. This date MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-date-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toohigh}}\n* {{msg-mw|Htmlform-required}}",
+       "htmlform-date-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-date-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toohigh}}",
+       "htmlform-date-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-date-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toolow}}",
+       "htmlform-time-placeholder": "Used as initial placeholder text in \"time\" input boxes. This time MUST be formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.",
+       "htmlform-time-invalid": "Used as error message in HTML forms. This time MUST be formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-time-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toohigh}}\n* {{msg-mw|Htmlform-required}}",
+       "htmlform-time-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-time-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toohigh}}",
+       "htmlform-time-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-time-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toolow}}",
+       "htmlform-datetime-placeholder": "Used as initial placeholder text in \"datetime\" input boxes. This date and time MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters, followed by a space (or the letter 'T'), followed by a time formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.",
+       "htmlform-datetime-invalid": "Used as error message in HTML forms. This date and time MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters, followed by a space (or the letter 'T'), followed by a time formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toohigh}}\n* {{msg-mw|Htmlform-required}}",
+       "htmlform-datetime-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toohigh}}",
+       "htmlform-datetime-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toolow}}",
        "htmlform-title-badnamespace": "Error message shown if the page title provided by the user is not in the required namespace. $1 is the page, $2 is the numerical namespace index.",
        "htmlform-title-not-creatable": "Error message shown if the page title provided by the user is not creatable (a special page). $1 is the page title.",
        "htmlform-title-not-exists": "Error message shown if the page title provided by the user does not exist. $1 is the page title.",
index 89168db..295f99a 100644 (file)
@@ -1083,6 +1083,7 @@ return [
                        'resources/src/mediawiki/htmlform/autocomplete.js',
                        'resources/src/mediawiki/htmlform/autoinfuse.js',
                        'resources/src/mediawiki/htmlform/checkmatrix.js',
+                       'resources/src/mediawiki/htmlform/datetime.js',
                        'resources/src/mediawiki/htmlform/cloner.js',
                        'resources/src/mediawiki/htmlform/hide-if.js',
                        'resources/src/mediawiki/htmlform/multiselect.js',
index 01d3442..91f797d 100644 (file)
                        } );
                }
 
+               // Our form input *should* be type="hidden". But if we're infusing from
+               // PHP, it's not.
+               if ( this.$input.attr( 'type' ) !== 'hidden' ) {
+                       try {
+                               this.$input.attr( 'type', 'hidden' );
+                       } catch ( e ) {
+                       }
+                       // IE <= 8, and IE 9 in quirks mode, doesn't allow changing the
+                       // type, so just hide the field with CSS. IE 9 in quirks mode
+                       // doesn't even throw an error, so do that unconditionally. Sigh.
+                       this.$input.css( 'display', 'none' );
+               }
+
                // Initialization
                this.setTabIndex( -1 );
 
diff --git a/resources/src/mediawiki/htmlform/datetime.js b/resources/src/mediawiki/htmlform/datetime.js
new file mode 100644 (file)
index 0000000..2fd2396
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * HTMLForm enhancements:
+ * Add minimal help for date and time fields
+ */
+( function ( mw ) {
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var supported = {};
+
+               $root
+                       .find( 'input.mw-htmlform-datetime-field' )
+                       .each( function () {
+                               var input,
+                                       type = this.getAttribute( 'type' );
+
+                               if ( type !== 'date' && type !== 'time' && type !== 'datetime' ) {
+                                       // WTF?
+                                       return;
+                               }
+
+                               if ( supported[ type ] === undefined ) {
+                                       // Assume that if the browser implements validation (so it
+                                       // rejects "bogus" as a value) then it supports a proper UI too.
+                                       input = document.createElement( 'input' );
+                                       input.setAttribute( 'type', type );
+                                       input.value = 'bogus';
+                                       supported[ type ] = ( input.value !== 'bogus' );
+                               }
+
+                               if ( supported[ type ] ) {
+                                       if ( !this.getAttribute( 'min' ) ) {
+                                               this.setAttribute( 'min', this.getAttribute( 'data-min' ) );
+                                       }
+                                       if ( !this.getAttribute( 'max' ) ) {
+                                               this.setAttribute( 'max', this.getAttribute( 'data-max' ) );
+                                       }
+                                       if ( !this.getAttribute( 'step' ) ) {
+                                               this.setAttribute( 'step', this.getAttribute( 'data-step' ) );
+                                       }
+                               }
+                       } );
+       } );
+
+}( mediaWiki ) );